335

用 TDC Benchmark 评估模型:让结果可比较

用 TDC Benchmark 评估模型能让结果与他人可比。这篇给出标准评测流程代码、指标解读,以及公开基准与自家数据的关系。

自己划分、自己评测,得到的数字只对自己有意义。TDC Benchmark Group(见 244《TDC ADMET Group》)提供固定划分与官方指标,让结果可以横向比较——做方法研究或选型时,这一步不能省。

标准评测流程

pip install PyTDC
from tdc.benchmark_group import admet_group
import numpy as np

group = admet_group(path="tdc_data/")

# 官方协议要求 5 个随机种子
predictions_list = []
for seed in [1, 2, 3, 4, 5]:
    predictions = {}
    for benchmark in group:
        name = benchmark["name"]
        train_val = benchmark["train_val"]
        test = benchmark["test"]

        # 官方划分:train_val 内部自己切,test 固定不动
        train, valid = group.get_train_valid_split(
            benchmark=name, split_type="scaffold", seed=seed)

        model = train_model(train, valid)          # 你的建模流程
        predictions[name] = model.predict(test["Drug"].tolist())

    predictions_list.append(predictions)

results = group.evaluate_many(predictions_list)
for task, (mean, std) in sorted(results.items()):
    print(f"{task:35s} {mean:.3f} ± {std:.3f}")

evaluate_many 要求 5 个种子的预测,直接返回均值与标准差——TDC 把「必须多种子」写进了评测协议,这是它比 MoleculeNet 严谨的地方。

一个完整的可运行示例

from tdc.benchmark_group import admet_group
from rdkit import Chem
from rdkit.Chem import rdFingerprintGenerator, DataStructs, Descriptors
import numpy as np, lightgbm as lgb

gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
DESC = [Descriptors.MolWt, Descriptors.MolLogP, Descriptors.TPSA,
        Descriptors.NumHDonors, Descriptors.NumHAcceptors,
        Descriptors.NumRotatableBonds, Descriptors.RingCount]

def featurize(smiles):
    feats = []
    for smi in smiles:
        m = Chem.MolFromSmiles(smi)
        if m is None:
            feats.append(np.zeros(2048 + len(DESC)))
            continue
        arr = np.zeros(2048, dtype=np.uint8)
        DataStructs.ConvertToNumpyArray(gen.GetFingerprint(m), arr)
        feats.append(np.concatenate([arr, [f(m) for f in DESC]]))
    return np.nan_to_num(np.array(feats))

group = admet_group(path="tdc_data/")
preds_list = []
for seed in [1, 2, 3, 4, 5]:
    preds = {}
    for bm in group:
        name = bm["name"]
        train, valid = group.get_train_valid_split(
            benchmark=name, split_type="scaffold", seed=seed)
        Xtr, ytr = featurize(train["Drug"]), train["Y"].values
        Xte = featurize(bm["test"]["Drug"])

        # 判断任务类型(TDC 有分类也有回归)
        is_clf = set(np.unique(ytr)) <= {0, 1}
        Model = lgb.LGBMClassifier if is_clf else lgb.LGBMRegressor
        m = Model(n_estimators=500, learning_rate=0.05,
                  num_leaves=31, verbose=-1, random_state=seed)
        m.fit(Xtr, ytr)
        preds[name] = (m.predict_proba(Xte)[:, 1] if is_clf
                       else m.predict(Xte))
    preds_list.append(preds)

for task, (mu, sd) in sorted(group.evaluate_many(preds_list).items()):
    print(f"{task:35s} {mu:.3f} ± {sd:.3f}")

指标怎么读

指标 方向 用于
MAE 越小越好 回归(Caco2、溶解度、LD50)
Spearman 越大越好 回归且关注排序(半衰期、清除率、VDss)
AUROC 越大越好 相对平衡的分类
AUPRC 越大越好 不平衡分类(部分 CYP 任务)

TDC 为每个终点指定了官方指标,不要自己换。半衰期、清除率用 Spearman 而非 MAE,正是因为这些体内参数的绝对预测很难,能排对序就有实用价值。

结果的正确解读

  • 各终点难度差别巨大:hERG、AMES、CYP 抑制这类体外单机制终点,AUROC 常能到 0.85+;半衰期、清除率、生物利用度这类体内综合参数,Spearman 到 0.4~0.5 就算不错。不要期待所有终点表现一致。
  • 看标准差:小数据集上标准差可能达 0.05 以上,此时排行榜上相邻名次的差异没有统计意义。
  • 与简单基线比:先看你的方法能否稳定超过「ECFP4 + LightGBM」这个基线。很多复杂方法在 TDC 上并不能。
  • 数据噪声是天花板:TDC 数据来自不同实验室,测定条件不统一,指标不可能接近完美。

公开基准与自家数据的关系

这是最需要说清楚的一点

  • TDC 上的排名只说明方法在该公开分布上的表现
  • 你的项目面对的是特定系列的类似物,化学空间与 TDC 完全不同;
  • 选型必须在自家数据上重测,公开基准只用于筛掉明显不行的方法;
  • 更有价值的做法是「公开数据预训练 + 内部数据微调」,而不是直接用公开模型预测自家分子。
# 把 TDC 数据当预训练用
# 1) 在 TDC 大数据终点上预训练一个编码器
# 2) 用自家数据微调
# 3) 在自家留出集上评估 —— 这才是选型的依据

# 关键:自家评估同样要用骨架划分与多种子

常见坑与提示

  • 用官方划分与官方指标,否则结果不可比;
  • 必须跑 5 个种子用 evaluate_many,单次跑分没有意义;
  • 体内综合参数(半衰期、清除率)本来就难,Spearman 0.4~0.5 已属不错;
  • 公开基准排名不能替代自家数据上的验证

延伸资源