ADMET 预测是 AI 在药物发现中应用最广的场景。多任务学习确实有帮助——但它掩盖不了一个更根本的问题:不同来源的 ADMET 数据往往不可直接比较。
主要终点与建模难度
| 终点 | 数据可得性 | 建模难度 | 说明 |
|---|---|---|---|
| 溶解度 | 较好 | 中 | 热力学 vs 动力学溶解度差异大 |
| logP / logD | 好 | 低 | 计算方法已相当成熟 |
| Caco-2 / MDCK 通透性 | 中 | 中 | 细胞系与实验室差异大(见 086《Caco-2 通透性》) |
| 血浆蛋白结合 | 中 | 中 | 高结合率区间难以精确 |
| 肝微粒体稳定性 | 中 | 较难 | 物种差异大 |
| CYP 抑制 | 较好 | 中 | 多个亚型需分别建模 |
| hERG 抑制 | 较好 | 中 | 安全性关键终点 |
| BBB 通透 | 差 | 难 | 标签定义混乱(见 132《MoleculeNet 论文精读》) |
| 生物利用度 | 差 | 很难 | 整合了多个过程 |
| 肝毒性 | 差 | 很难 | 机制多样 |
核心问题:实验体系的差异
# 同一个「终点」,不同来源的数据可能不可比
#
# 例:溶解度
# 动力学溶解度(从 DMSO 储液稀释)
# vs 热力学溶解度(固体平衡)
# → 【可能相差几十倍】
# 缓冲液(水、pH 6.8、FaSSIF)不同结果也不同
#
# 例:Caco-2 通透性
# 不同实验室的细胞代次、单层完整性、
# 转运蛋白表达水平都不同
# → 【同一化合物在不同实验室的 Papp 可能差 2~5 倍】
#
# 例:微粒体稳定性
# 蛋白浓度、NADPH 再生系统、孵育时间、
# 物种(人/大鼠/小鼠/犬)
# → 数值不可直接混用
#
# 例:hERG
# 自动膜片钳 vs 手动膜片钳 vs 结合实验
# → 【结合实验与功能实验的结果可能定性不同】
#
# 【后果】:
# 把不同来源的数据混在一起训练,
# 模型学到的一部分是「数据来源的差异」
# 而非化学结构与性质的关系
#
# → 表现为:
# - 在混合数据的测试集上看起来还行
# - 在自家实验室的新数据上表现明显下降
#
# 【应对方法】:
#
# 1) 【最好的做法:只用自家数据】
# 体系一致,虽然数据量少但质量高
# → 常常比用大量公开数据的模型更实用
#
# 2) 【分层建模】
# 把「数据来源」作为一个特征或随机效应
# → 让模型显式地建模体系差异
#
# 3) 【迁移学习】
# 用公开数据预训练,用自家数据微调
# → 【这是最实用的折中】
#
# 4) 【只建排序模型】
# 不同体系间绝对值不可比,但排序可能一致
# → 改做排序任务(见 155)
多任务学习的价值与边界
- 为什么有用:ADMET 各终点之间有共同的物理化学基础(亲脂性、分子大小、极性表面积、电荷)——共享表示能让数据少的终点受益;
- 典型收益:数据量小的终点(几百个样本)提升明显;数据量大的终点收益有限;
- 负迁移的风险:把不相关的终点放在一起会互相干扰。判断方法:单任务训练与多任务训练逐个比较,有任务变差就说明存在负迁移;
- 任务分组:比「全部一起训练」更好的做法是把相关的终点分组(如吸收类一组、代谢类一组、毒性类一组);
- 不能解决体系差异:多任务学习共享的是化学表示,不能修正数据本身的不一致——这是它的根本边界。
实用的建模流程
import numpy as np
import pandas as pd
from rdkit import Chem
from rdkit.Chem import Descriptors, rdFingerprintGenerator
import lightgbm as lgb
# ---- 1) 特征:指纹 + 物化描述符 ----
gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
DESC = ["MolWt", "MolLogP", "TPSA", "NumHDonors", "NumHAcceptors",
"NumRotatableBonds", "RingCount", "FractionCSP3",
"NumAromaticRings", "HeavyAtomCount", "NHOHCount", "NOCount"]
def featurize(smiles_list):
X = []
for smi in smiles_list:
m = Chem.MolFromSmiles(smi)
if m is None:
X.append(np.zeros(2048 + len(DESC)))
continue
fp = np.array(gen.GetFingerprint(m))
desc = np.array([getattr(Descriptors, d)(m) for d in DESC])
X.append(np.concatenate([fp, desc]))
return np.array(X)
# 【ADMET 预测中,物化描述符尤其重要】
# 因为 ADMET 性质很大程度上由这些全局性质决定
# → 不要只用指纹
# ---- 2) 用对数尺度 ----
# 溶解度: logS
# 通透性: log Papp
# 清除率: log CL
# → 这些量跨越数量级,必须取对数
# ---- 3) 训练 + 不确定性 ----
def train_with_uncertainty(X, y, n_models=5):
"""用不同种子的集成估计不确定性"""
models = []
for seed in range(n_models):
m = lgb.LGBMRegressor(n_estimators=800, learning_rate=0.05,
num_leaves=31, subsample=0.8,
colsample_bytree=0.8, random_state=seed,
verbose=-1)
m.fit(X, y)
models.append(m)
return models
def predict_with_uncertainty(models, X):
preds = np.array([m.predict(X) for m in models])
return preds.mean(axis=0), preds.std(axis=0)
# ---- 4) 适用域检查 ----
def applicability(smiles, train_fps, threshold=0.4):
from rdkit import DataStructs
m = Chem.MolFromSmiles(smiles)
if m is None:
return 0.0
fp = gen.GetFingerprint(m)
sims = DataStructs.BulkTanimotoSimilarity(fp, train_fps)
return max(sims)
# ---- 5) 分位数回归(给出区间而非点估计)----
q_models = {}
for q in [0.1, 0.5, 0.9]:
m = lgb.LGBMRegressor(objective="quantile", alpha=q,
n_estimators=800, verbose=-1)
m.fit(X_train, y_train)
q_models[q] = m
# 【预测区间比点估计对决策更有用】
ADMET 模型的正确用法
| 用途 | 可行性 |
|---|---|
| 大规模筛选中排除明显有问题的分子 | 可行——主要价值所在 |
| 在类似物中排序 | 可行 |
| 指导结构改造方向 | 有限——需结合化学知识 |
| 替代实验测定 | 不可行 |
| 预测精确数值 | 不可行 |
| 对新化学类型的预测 | 不可靠——需检查适用域 |
核心定位:ADMET 模型的价值在于「早期排除」而非「精确预测」。在合成之前排除掉明显会有溶解度或 hERG 问题的分子,就已经创造了很大价值——不需要精确到具体数值。
关键要点
- 不同来源的 ADMET 数据往往不可直接比较——混合训练会让模型学习「数据来源」;
- 最实用的折中是公开数据预训练 + 自家数据微调;
- 多任务学习共享化学表示,但不能修正数据本身的不一致;
- 价值在早期排除而非精确预测;ADMET 建模中物化描述符不可省。
延伸资源
- 活性预测:155《AI 活性预测模型》;不确定性:166《不确定性估计》;模型外推性:167《模型外推性》;
- Caco-2:086《Caco-2 通透性》;ADME 级联:082《ADME Cascade》;MoleculeNet:132《MoleculeNet 论文精读》。