ADMET模型训练策略与性能提升指南 - 第3节
数据收集与预处理:从源头把控模型质量
做ADMET模型这么多年,我踩过最大的坑是什么?
不是模型结构不够深,不是超参数没调好——而是训练数据本身就一堆问题。
坦白讲,用脏数据训出来的模型,指标再好看都是假的。
你辛辛苦苦调了一周,迁移到新分子上直接崩盘。原因在哪?
数据源没洗干净,标准化没做透,重复化合物没去干净。
这一章,我就把数据收集与预处理的实战流程掰开揉碎讲给你听。
从数据库怎么挑,到清洗、标准化、去重,每一步都是血泪换来的。
3.1 三大公共数据库:我常用的选择
做ADMET研究,ChEMBL、PubChem和DrugBank是三大主力。
我个人的习惯是:ChEMBL优先,PubChem做补充,DrugBank用于验证已知药。
| 数据库 | 特点 | 适用场景 | 常见坑 |
|---|---|---|---|
| ChEMBL | 手工精选,活性数据质量高 | ADMET定量建模 | 数据量相对小,更新慢 |
| PubChem | 海量,包含高通量筛选结果 | 大规模虚拟筛选 | 实验条件不统一,噪声多 |
| DrugBank | 已上市/临床药物,附带靶点 | 药代性质预测验证 | 只有药物分子,偏少 |
我的建议:如果做通用ADMET,把ChEMBL和PubChem合并使用。
但千万别一股脑全下载——先看数据量级,再按实验类型(IC50,Ki,% inhibition)拆开处理。
但千万别一股脑全下载——先看数据量级,再按实验类型(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,且不含高分子标记。
查了半天发现,训练集里有几个大分子(分子量>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%的脏数据问题。
总结一下我个人的最佳实践:
- 用ChEMBL做主体,PubChem做补充,DrugBank用于验证
- 清洗阶段严格检查SMILES解析能力和活性值范围
- 统一为pIC50 / 规范SMILES / InChIKey
- 去重时务必携带实验条件,不要盲目合并
数据预处理这一步,很多新手觉得简单,跳过去直接建模。
我当年也犯过这种错——直到后来模型在外部测试集上翻车,才明白数据质量是一切的上限。
你花在清洗与标准化上的每一分钟,都会在模型泛化能力上得到回报。