数据清洗完、划分定好之后,就该训模型了。Chemprop(见 174《Chemprop》)是最该先跑的一个——它的 D-MPNN 是长期难以超越的强基线,且工程完成度高,几条命令就能拿到带不确定性的预测。
准备数据
import pandas as pd
df = pd.read_csv("egfr_cleaned.csv")
df[["clean_smiles", "pIC50"]].rename(
columns={"clean_smiles": "smiles"}
).to_csv("chemprop_data.csv", index=False)
# 格式:第一列 SMILES,其后每列一个任务
# smiles,pIC50
# CC(=O)Oc1ccccc1C(=O)O,6.42
pip install chemprop
chemprop train --help # 确认是 v2 语法
Chemprop v1 与 v2 的命令行差别很大(v1 是 chemprop_train,v2 是 chemprop train)。网上大量教程停留在 v1,跑不通时先确认版本。
训练:必开的四个选项
chemprop train \
--data-path chemprop_data.csv \
--task-type regression \
--split-type scaffold_balanced \
--split-sizes 0.8 0.1 0.1 \
--num-folds 5 \
--ensemble-size 5 \
--epochs 50 \
--descriptors-generator rdkit_2d_normalized \
--metrics rmse mae r2 \
--save-dir ckpt_egfr/ \
--num-workers 8
| 选项 | 为什么必开 |
|---|---|
--split-type scaffold_balanced |
随机划分指标虚高,骨架划分才反映外推能力 |
--num-folds 5 |
多折交叉验证,得到均值与方差 |
--ensemble-size 5 |
小数据下单模型方差大;集成同时提升精度与不确定性质量 |
--descriptors-generator rdkit_2d_normalized |
图表示 + 全局描述符拼接,通常比纯图稳一截 |
预测并拿到不确定性
chemprop predict \
--test-path new_compounds.csv \
--model-path ckpt_egfr/ \
--preds-path predictions.csv \
--uncertainty-method ensemble
import pandas as pd
import numpy as np
pred = pd.read_csv("predictions.csv")
print(pred.head())
# smiles, pIC50, pIC50_ensemble_uncal_var
pred["std"] = np.sqrt(pred["pIC50_ensemble_uncal_var"])
# 用不确定性做可信度过滤 —— 这比压低整体 RMSE 更实用
confident = pred[pred["std"] < 0.5]
print(f"{len(confident)}/{len(pred)} 个预测可信度较高")
print("高不确定性样本(可能落在训练分布外):")
print(pred.nlargest(5, "std")[["smiles", "pIC50", "std"]])
不确定性的实际用法:不是用来提高整体指标,而是用来判断哪些预测值得采信。方差大的分子往往落在训练分布之外,它们的预测不该用于决策。
必做的基线对照
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
import numpy as np
# 用同一套骨架划分跑 ECFP4 + 随机森林(见 330)
rf = RandomForestRegressor(n_estimators=500, n_jobs=-1, random_state=0)
rf.fit(X_train, y_train)
rf_rmse = np.sqrt(mean_squared_error(y_test, rf.predict(X_test)))
print(f"ECFP4 + RF RMSE = {rf_rmse:.3f}")
print(f"Chemprop RMSE = {chemprop_rmse:.3f}")
# 判断:
# Chemprop 明显更好 → 图表示确实有帮助,继续深入
# 两者相当 → 数据量可能不足以发挥深度模型优势
# Chemprop 更差 → 数据有问题或超参不当,回头查数据
这个对照不能省。在几百到几千个分子的典型项目数据上,指纹基线常常与 D-MPNN 打平——这本身就是重要信息,说明瓶颈在数据而非模型。
超参搜索(收益通常有限)
chemprop hpopt \
--data-path chemprop_data.csv \
--task-type regression \
--search-parameter-keywords depth hidden_dim ffn_num_layers dropout \
--raytune-num-samples 30 \
--hyperopt-save-dir hpopt/ \
--split-type scaffold_balanced
常见有效区间:depth 3~6、hidden_dim 300~1200、dropout 0~0.35。但超参调优的收益通常远小于「把数据清干净 + 用对划分 + 做集成」,不要在这里花掉大部分时间。
结果诊断
import matplotlib.pyplot as plt
# 1) 预测 vs 实测散点图
plt.scatter(y_test, y_pred, alpha=0.5)
plt.plot([y.min(), y.max()], [y.min(), y.max()], "k--")
plt.xlabel("实测 pIC50"); plt.ylabel("预测 pIC50")
# 2) 检查是否存在「回归到均值」
# 如果预测值集中在均值附近、跨度远小于实测,
# 说明模型基本没学到 SAR,只是在猜平均值
print(f"实测标准差 {y_test.std():.3f},预测标准差 {y_pred.std():.3f}")
# 预测标准差远小于实测 → 模型欠拟合或数据信噪比太低
# 3) 残差 vs 不确定性
# 好的不确定性估计应与实际误差正相关
residual = np.abs(y_test - y_pred)
print("不确定性与误差的相关系数:", np.corrcoef(residual, pred_std)[0,1])
# > 0.3 说明不确定性估计有意义
常见坑与提示
- 三件必开:骨架划分、多折集成、RDKit 描述符拼接;
- 必须与 ECFP4 + 随机森林对照,赢不过就回去查数据;
- 用集成方差做可信度过滤,比追求整体 RMSE 更实用;
- 检查「预测标准差 vs 实测标准差」,差太多说明模型只是在猜均值。