Practical Cheminformatics(Pat Walters 的博客)在 AI 制药的学习资源中位置特殊:它不教方法,而是教「什么时候不该相信方法」。这种基于长期工业实践的批判性视角,是教科书与论文里学不到的。
反复出现的核心主题
| 主题 | 核心观点 |
|---|---|
| 基线的重要性 | 先跑简单基线,很多复杂方法打不过它 |
| 数据划分 | 随机划分会严重高估性能(见 051《Scaffold Split》) |
| 统计严谨性 | 报告置信区间;单次运行不足信 |
| 数据质量 | 清洗与标准化的价值高于换模型 |
| 评价指标 | 指标要对应实际决策(见 155《AI 活性预测模型》) |
| 论文的批判性阅读 | 识别不公平比较与夸大宣称(见 169《Benchmark 陷阱》) |
| 可视化 | 看数据比看指标更能发现问题 |
| 化学直觉 | 模型输出必须经得起化学检验 |
「先跑基线」的具体含义
# 【这是整个博客最核心的实践建议】
#
# 在评估任何新方法之前,先建立基线:
import numpy as np
import lightgbm as lgb
from rdkit import Chem
from rdkit.Chem import Descriptors, rdFingerprintGenerator
gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
DESC_NAMES = [d[0] for d in Descriptors._descList]
def baseline_features(smiles_list, n_desc=50):
"""ECFP + 常用描述符"""
X = []
for smi in smiles_list:
m = Chem.MolFromSmiles(smi)
if m is None:
X.append(np.zeros(2048 + n_desc))
continue
fp = np.array(gen.GetFingerprint(m))
desc = []
for name in DESC_NAMES[:n_desc]:
try:
desc.append(getattr(Descriptors, name)(m))
except Exception:
desc.append(0.0)
X.append(np.concatenate([fp, np.nan_to_num(desc)]))
return np.array(X)
def baseline_model(X, y, seeds=range(5)):
"""多种子训练,返回均值与标准差"""
return [lgb.LGBMRegressor(n_estimators=800, learning_rate=0.05,
num_leaves=31, subsample=0.8,
colsample_bytree=0.8, random_state=s,
verbose=-1).fit(X, y)
for s in seeds]
# 【这个基线的特点】:
# - 训练几分钟
# - 不需要 GPU
# - 部署简单(一个 pickle 文件)
# - 结果稳定可复现
#
# 【判断标准】:
# 新方法必须【显著】超过它才值得采用
# 「显著」= 提升 > 2 倍种子间标准差
# 且要考虑增加的复杂度与部署成本
统计严谨性:常被忽略的部分
# 【博客反复强调的一点】:
# 分子数据集小,结果的方差大
# → 【不报告不确定性的结果没有意义】
import numpy as np
from scipy import stats
def bootstrap_ci(y_true, y_pred, metric_fn, n_boot=1000, alpha=0.05, seed=0):
"""用自助法给指标算置信区间"""
rng = np.random.default_rng(seed)
n = len(y_true)
scores = []
for _ in range(n_boot):
idx = rng.integers(0, n, n)
try:
scores.append(metric_fn(y_true[idx], y_pred[idx]))
except Exception:
continue
lo, hi = np.percentile(scores, [100 * alpha / 2, 100 * (1 - alpha / 2)])
return float(np.mean(scores)), float(lo), float(hi)
def compare_models(y_true, pred_a, pred_b, metric_fn, n_boot=1000, seed=0):
"""比较两个模型:差值的置信区间是否包含 0"""
rng = np.random.default_rng(seed)
n = len(y_true)
diffs = []
for _ in range(n_boot):
idx = rng.integers(0, n, n)
diffs.append(metric_fn(y_true[idx], pred_a[idx])
- metric_fn(y_true[idx], pred_b[idx]))
lo, hi = np.percentile(diffs, [2.5, 97.5])
significant = not (lo <= 0 <= hi)
print(f"差值 {np.mean(diffs):.4f} [{lo:.4f}, {hi:.4f}] "
f"{'显著' if significant else '【不显著】'}")
return significant
# 【实际经验】:
# 很多论文报告的「提升」,
# 用这个方法检验后是不显著的
# → 【在自己的工作中先做这个检验,
# 能避免向团队汇报不存在的提升】
「看数据」的重要性
- 博客的一个反复建议:不要只看汇总指标,要看具体的分子;
- 具体做法:
- 把预测误差最大的 20 个分子画出来看——常常能发现数据问题(错误的结构、异常的标签);
- 画预测值 vs 实测值的散点图,而非只看 R²;
- 按化学系列分组看性能,而非只看整体;
- 用 mols2grid 等工具批量查看分子(见 217《Mols2grid》)。
- 为什么有效:汇总指标会掩盖系统性问题。R² = 0.7 可能是「大部分预测得不错」,也可能是「一个系列很准、另一个系列完全错」——后者在实际使用中是灾难性的。
from rdkit import Chem
from rdkit.Chem import Draw
import numpy as np
def inspect_worst_predictions(smiles, y_true, y_pred, n=20):
"""看看模型错得最离谱的分子"""
errors = np.abs(np.asarray(y_true) - np.asarray(y_pred))
worst = np.argsort(-errors)[:n]
mols, legends = [], []
for i in worst:
m = Chem.MolFromSmiles(smiles[i])
if m is None:
print(f"【无效结构】: {smiles[i]}") # ← 常常在这里发现问题
continue
mols.append(m)
legends.append(f"真值 {y_true[i]:.2f} 预测 {y_pred[i]:.2f}")
return Draw.MolsToGridImage(mols, molsPerRow=4, legends=legends,
subImgSize=(280, 220))
# 【常见的发现】:
# - 结构解析失败的分子
# - 标签明显异常的数据点(录入错误)
# - 某一类化学结构系统性地预测偏差
# - 训练集中根本没有类似结构(适用域外,见 166)
学习方式建议
| 做法 | 说明 |
|---|---|
| 跟着跑 Notebook | 博客配有可运行的代码 |
| 在自己的数据上重做 | 这是真正的学习 |
| 对照检查自己的项目 | 用博客提出的问题自查 |
| 读它对具体论文的评论 | 学习批判性阅读的方法 |
| 关注方法学的讨论 | 而非只看结论 |
它能补上的认知空白
- 教科书不讲的实践陷阱:数据泄漏、不公平基线、指标误用;
- 论文不会说的局限:作者通常不会强调自己方法的弱点;
- 工业与学术的差异:学术追求指标提升,工业关心的是「这能不能改变决策」;
- 化学与计算的结合:作者兼具药化与计算背景,能指出纯计算背景的人看不到的问题;
- 合理的怀疑态度:对领域内的热门宣称保持审慎,而非全盘接受或全盘否定。
关键要点
- 核心建议是先跑简单基线——ECFP + LightGBM,几分钟、无需 GPU、结果稳定;
- 不报告不确定性的结果没有意义——用自助法给指标算置信区间;
- 看具体的分子而非只看汇总指标——误差最大的 20 个分子常常暴露数据问题;
- 它补的是「什么时候不该相信方法」,这是教科书与论文里学不到的。
延伸资源
- 学习路线:003《AI 药物发现学习路线》;Benchmark 陷阱:169《Benchmark 陷阱》;论文阅读框架:170《AI 制药论文阅读框架》;
- Chemprop:131《Chemprop / D-MPNN 论文精读》;Scaffold Split:051《Scaffold Split》;不确定性:166《不确定性估计》。