156

AI ADMET 预测模型:多任务学习有用,但不能掩盖实验体系差异

多任务学习对 ADMET 预测有用,但掩盖不了实验体系的差异。这篇讲清各终点的特点、数据陷阱与务实做法。

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 建模中物化描述符不可省。

延伸资源