333

训练 Chemprop 活性预测模型:从数据到结果

Chemprop 是分子性质预测最该先打的强基线。这篇给出从数据到结果的完整命令、必开选项与结果解读方式。

数据清洗完、划分定好之后,就该训模型了。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 实测标准差」,差太多说明模型只是在猜均值。

延伸资源