ADMET模型训练策略与性能提升指南 - 第3节

数据收集与预处理:从源头把控模型质量

做ADMET模型这么多年,我踩过最大的坑是什么?
不是模型结构不够深,不是超参数没调好——而是训练数据本身就一堆问题。

坦白讲,用脏数据训出来的模型,指标再好看都是假的。
你辛辛苦苦调了一周,迁移到新分子上直接崩盘。原因在哪?
数据源没洗干净,标准化没做透,重复化合物没去干净。

这一章,我就把数据收集与预处理的实战流程掰开揉碎讲给你听。
从数据库怎么挑,到清洗、标准化、去重,每一步都是血泪换来的。

3.1 三大公共数据库:我常用的选择

做ADMET研究,ChEMBL、PubChem和DrugBank是三大主力。
我个人的习惯是:ChEMBL优先,PubChem做补充,DrugBank用于验证已知药。

数据库特点适用场景常见坑
ChEMBL手工精选,活性数据质量高ADMET定量建模数据量相对小,更新慢
PubChem海量,包含高通量筛选结果大规模虚拟筛选实验条件不统一,噪声多
DrugBank已上市/临床药物,附带靶点药代性质预测验证只有药物分子,偏少
我的建议:如果做通用ADMET,把ChEMBL和PubChem合并使用。
但千万别一股脑全下载——先看数据量级,再按实验类型(IC50,Ki,% inhibition)拆开处理。

3.2 数据清洗:先把垃圾筛出去

拿到的原始数据长什么样?
嗯,千奇百怪。我遇到过过半数分子没有SMILES,还有那种pIC50填成“>5.0”的字符串。

3.2.1 缺失值处理

  • SMILES缺失:直接删除这条记录。没法补。
  • 活性值缺失:如果只是少量(<5%),我倾向于删除。超过20%就别用了。
  • 实验条件缺失(pH、温度):做回归模型时需要保留,但标记为NaN。

3.2.2 异常值过滤

  • 范围检查:pIC50正常范围0-14,超出直接标红。
  • 重复测量方差过大:同一化合物测了五次,标准差>0.5 log单位?我建议剔除该化合物。
  • 结构异常:SMILES不能解析、原子数过多(比如超过200个重原子)一律过滤。
我曾经踩过的坑:有一次建Caco-2渗透性模型,训练集R² 0.89,测试集掉到0.3。
查了半天发现,训练集里有几个大分子(分子量>2000)是错误录入的聚合物——它们天然就过不了膜。
从那以后,我每次在清洗阶段都会加一条规则:重原子数<50,且不含高分子标记。

3.3 数据标准化:让可比性复活

从不同数据库扒下来的数据,量纲和格式千差万别。
举个简单的例子:ChEMBL里pIC50是-logIC50,PubChem里可能直接给IC50(μM)。
不标准化,模型就乱了。

3.3.1 活性值标准化

  • 统一转为pIC50(或pKi,pEC50)
  • 若原始是%inhibition,保留百分比或换算为活性分类
  • 去除“>”“<”等不等号,这类值不能直接用于回归

3.3.2 分子表征标准化

  • SMILES:用RDKit进行规范SMILES转换
  • 去除盐、溶剂:使用结构抽取函数获取“最大片段”
  • 立体化学标记保留:对于手性敏感的ADMET性质(如CYP代谢),不能粗暴去除手性信息
from rdkit import Chem
from rdkit.Chem import SaltRemover

def standardize_smiles(smiles):
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return None
    # 去除盐/溶剂
    remover = SaltRemover.SaltRemover()
    mol = remover.StripMol(mol, dontRemoveEverything=True)
    # 规范SMILES
    canonical = Chem.MolToSmiles(mol, canonical=True)
    return canonical

# 示例
print(standardize_smiles("CCO.C1=CC=CC=C1"))  # 输出:c1ccccc1(只保留苯)
print(standardize_smiles("[Na+].CC(=O)[O-]")) # 输出:CC(=O)[O-](保留乙酸根)

3.4 去重策略:同一化合物不能出现两次

去重听着简单,做起来门道很多。
我遇到过同一个化合物在不同批次里pIC50差了1.2个log——到底保留哪个?

3.4.1 结构去重

  • 使用InChIKey(第一层)作为唯一标识,快速去重
  • 优先保留最新、实验条件最完整的记录
  • 若多个值差异不大(标准差<0.3 log),建议取中位数整合

3.4.2 实验条件去重

  • 即使结构相同,但pH、温度、细胞系不同——应视为不同数据点
  • 模型训练时建议保留,并作为条件特征(如pH值)输入
import pandas as pd
from rdkit import Chem

def canonical_inchikey(smiles):
    mol = Chem.MolFromSmiles(smiles)
    if mol:
        return Chem.MolToInchiKey(mol)
    return None

df['InChIKey'] = df['smiles'].apply(canonical_inchikey)

# 按照结构+实验条件聚合,取活性中位数
grouped = df.groupby(['InChIKey', 'assay_type', 'pH', 'temp'])

median_df = grouped['pIC50'].median().reset_index()
print(median_df.head())

3.5 整体工作流

下面这张图概括了我个人常用的一条数据预处理管线。
你对照着走一遍,基本能避开90%的脏数据问题。

ADMET 数据预处理管线 ChEMBL / PubChem / DrugBank 原始化合物数据 数据清洗 缺失值 / 异常值 / 范围 结构去重 InChIKey + 条件聚合 标准化输出 关键动作 • 多源合并前先统一SMILES格式 • 保留实验条件元数据 避坑提醒 • 不要直接字符串去重 • 注意Tautomer(互变异构) • 手性信息要谨慎处理
总结一下我个人的最佳实践:
  • 用ChEMBL做主体,PubChem做补充,DrugBank用于验证
  • 清洗阶段严格检查SMILES解析能力和活性值范围
  • 统一为pIC50 / 规范SMILES / InChIKey
  • 去重时务必携带实验条件,不要盲目合并

数据预处理这一步,很多新手觉得简单,跳过去直接建模。
我当年也犯过这种错——直到后来模型在外部测试集上翻车,才明白数据质量是一切的上限。
你花在清洗与标准化上的每一分钟,都会在模型泛化能力上得到回报。