自己划分、自己评测,得到的数字只对自己有意义。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 已属不错;
- 公开基准排名不能替代自家数据上的验证。