132

MoleculeNet 论文精读:分子机器学习基准的价值和过时之处

MoleculeNet 建立了分子机器学习的评测传统,也留下了不少沿用至今的问题。这篇讲清它的贡献与必须注意的过时之处。

MoleculeNet(Wu 等,Chemical Science 2018)是分子机器学习领域第一个系统性的基准套件。它极大地推动了领域发展——但它的一些设计缺陷被后续工作不加检验地沿用,反而成了误导的来源

包含的数据集

类别 数据集 规模
量子力学 QM7、QM8、QM9 7K~134K
物理化学 ESOL、FreeSolv、Lipophilicity 0.6K~4K(很小)
生物物理 PCBA、MUV、HIV、BACE 1.5K~440K
生理学 BBBP、Tox21、ToxCast、SIDER、ClinTox 1.4K~8K

它的真实贡献

  • 统一了评测方式:在此之前,每篇论文用自己的数据与划分,结果无法比较;
  • 提出了骨架划分这是最重要的贡献之一——它明确指出随机划分会高估性能,并提供了基于 Bemis-Murcko 骨架的划分方式;
  • 提供了标准的评价指标:分类用 ROC-AUC/PRC-AUC,回归用 RMSE/MAE;
  • 降低了入门门槛:通过 DeepChem 提供一行代码加载(见 134《DeepChem 平台解读》)。

必须知道的问题

# 问题一:【数据集太小】
#   ESOL: 1128 个分子
#   FreeSolv: 642 个分子
#   BACE: 1513 个分子
#
#   后果:
#     - 不同随机种子的结果差异可能大于方法间的差异
#     - 论文报告的「提升 0.02 AUC」可能纯属噪声
#     - 【必须报告多次运行的均值与标准差】
#
#   检验方法:
#     用同一个方法跑 10 个不同种子,
#     看结果的标准差有多大
#     → 如果标准差 0.03,那么 0.02 的「提升」没有意义

# 问题二:【标签质量参差】
#   BBBP(血脑屏障通透性):
#     标签来自多个文献来源,定义不统一
#     有研究发现其中存在明显的标注错误
#   ClinTox:
#     「临床试验失败」的原因多样,
#     不全是毒性 —— 标签定义模糊
#   Tox21/ToxCast:
#     体外测定,与体内毒性的关系间接
#
#   后果:
#     模型的性能上限被标签噪声限制
#     刷到 0.95 AUC 可能意味着在拟合噪声

# 问题三:【随机划分仍被广泛使用】
#   虽然 MoleculeNet 提出了骨架划分,
#   但很多论文仍然报告随机划分的结果
#   → 随机划分下,测试集分子的近似物在训练集中
#   → 严重高估真实性能(见 167)
#
#   典型差距:
#     同一模型在 BACE 上
#     随机划分 AUC 0.88,骨架划分 AUC 0.78
#     → 【差距巨大】

# 问题四:【与实际决策脱节】
#   预测 ESOL 的溶解度 RMSE 从 0.60 降到 0.55,
#   对实际项目意味着什么?
#   → 论文通常不回答这个问题(见 133)

正确的使用方式

  • 只用骨架划分或时间划分:随机划分的结果不应作为主要结论;
  • 报告多个种子的均值 ± 标准差单次运行的结果在小数据集上没有说服力
  • 与强基线比较:ECFP + 随机森林/LightGBM,且给基线同等调优预算(见 131《Chemprop / D-MPNN 论文精读》);
  • 不要只看排行榜名次:关注提升的绝对幅度与统计显著性;
  • 作为「快速验证代码能跑通」的工具,而非「证明方法优越」的依据。

加载与正确划分

import deepchem as dc

# DeepChem 提供的加载器
tasks, datasets, transformers = dc.molnet.load_bace_classification(
    featurizer="ECFP",
    splitter="scaffold",      # 【不要用 "random"】
    reload=False,
)
train, valid, test = datasets
print(f"训练 {len(train)}, 验证 {len(valid)}, 测试 {len(test)}")

# 或者自己用 RDKit 做骨架划分(更透明可控)
from rdkit import Chem
from rdkit.Chem.Scaffolds import MurckoScaffold
from collections import defaultdict
import numpy as np

def scaffold_split(smiles_list, frac_train=0.8, frac_valid=0.1, seed=0):
    scaffolds = defaultdict(list)
    for i, smi in enumerate(smiles_list):
        mol = Chem.MolFromSmiles(smi)
        if mol is None:
            continue
        scaf = MurckoScaffold.MurckoScaffoldSmiles(mol=mol, includeChirality=False)
        scaffolds[scaf].append(i)

    # 按骨架大小降序排列(大骨架优先进训练集)
    groups = sorted(scaffolds.values(), key=len, reverse=True)

    n = len(smiles_list)
    n_train, n_valid = frac_train * n, frac_valid * n
    train_idx, valid_idx, test_idx = [], [], []
    for g in groups:
        if len(train_idx) + len(g) <= n_train:
            train_idx += g
        elif len(valid_idx) + len(g) <= n_valid:
            valid_idx += g
        else:
            test_idx += g
    return train_idx, valid_idx, test_idx

# 【验证划分的有效性】:
#   计算训练集与测试集之间的最大 Tanimoto 相似度分布
#   如果很多测试分子与某个训练分子相似度 > 0.7,
#   说明划分不够严格

更好的替代基准

基准 优势
TDC(见 133《TDC 论文精读》 任务更贴近研发链条;划分方式更严格
Polaris 强调数据质量与评测严谨性
LIT-PCBA(见 242《LIT-PCBA》 更真实的虚拟筛选场景
自家历史数据 最有说服力的评测

最重要的一条:真正的评测应该在自家数据上做时间划分——用 2023 年前的数据训练,预测 2024 年的实验结果。这才是模型在生产中面对的情形。

关键要点

  • 多个数据集只有 1000 量级——种子间的方差可能大于方法间的差异;
  • BBBP、ClinTox 等的标签质量存在已知问题,性能上限被噪声限制;
  • 随机划分与骨架划分的差距可达 0.1 AUC——只应报告骨架划分结果;
  • 真正有说服力的评测是在自家数据上做时间划分

延伸资源