数据收集与清洗:你手头的数据真的能用吗?
做ADMET预测,数据集的底子决定了模型的命。模型再花哨,数据是脏的,结果就是废的。我见过太多团队调了半天参数,最后发现是训练集里混了一堆重复结构。所以这一章,咱们聊聊数据从哪来、怎么洗干净。
常见的ADMET公开数据集
先说数据集。我个人习惯把数据集分成两类:综合型(啥都有)和任务型(只测某一项ADMET性质)。下面这几个是我在项目里轮番用过的,你挑着存。
| 数据集名称 | 涵盖性质 | 样本量 | 我的备注 |
|---|---|---|---|
| ChEMBL | 代谢、毒性、吸收、分布等 | ~2M化合物 | 最常用,但性质标签不统一,需要二次清洗 |
| PubChem BioAssay | 各类生化活性、毒性 | ~3M化合物 | 数据量大,但实验条件差异大,容易出异常值 |
| Tox21 | 12种毒性通路 | ~10k化合物 | 权威性好,我用来做毒性对比验证 |
| ADMETlab | 吸收、代谢、毒性、物化 | ~220k化合物 | 已经做过初步清洗,适合快速验证 |
| FDA橙皮书 | 口服生物利用度、代谢 | ~4000药物 | 临床数据,噪声低,但样本量太小 |
数据清洗:别让脏数据毁了模型
拿到原始数据,第一件事不是建模,是洗。你想想看,同一化合物在不同批次里出现了两次,logD值却差了两个单位——这种坑我遇到过,当时差点把异常点当成新规律去解释。
1. 重复结构处理
ADMET数据常有多条记录对应同一个SMILES。我一般按SMILES+性质去重,保留最新或最高置信度的一条。注意:有些数据集会刻意保留同一化合物的不同形态(比如盐),这时候需要按标准规定处理,比如只保留中性分子。
# 伪代码:按 SMILES 去重,保留第一个或置信度最高记录
data.drop_duplicates(subset=['smiles', 'property'], keep='first')
2. 缺失值处理
缺失值的坑比想象的多。有些数据集把缺失值写成0或者-999,这很容易被模型当成有效值。我之前从PubChem下载数据时,发现有约5%的logD值是-999,结果模型训练完直接崩溃——预测值全往负数偏。
- 直接删除:样本量大时(>1万),缺失率低于5%可以直接删行。
- 填充:只有少量缺失且性质有物理意义时,用中位数或根据相似化合物填充。
- 标记为特殊值:比如把缺失的Caco-2渗透率设为-1,并保留一个“is_missing”特征。
3. 标准化分子表示
同一个结构可能有不同的SMILES写法,比如带不带立体化学、是否标准化。我习惯用RDKit做统一标准化:清洗盐、去除溶剂、标准化电荷。
from rdkit import Chem
from rdkit.Chem import SaltRemover
remover = SaltRemover.SaltRemover()
mol = Chem.MolFromSmiles(smiles)
mol = remover.StripMol(mol) # 去盐
canonical_smiles = Chem.MolToSmiles(mol, isomericSmiles=True) # 标准化
异常值处理:该扔就扔,别心疼
异常值不一定是错误,但它一定会拉着模型往奇怪的方向跑。举个真实的例子:我在处理Tox21的细胞毒性数据时,发现有一个化合物的IC50值是0.001 nM——比已知最强抑制剂还强100倍。查原始文献才发现是实验写错了小数位。
常用的检测方法:
- IQR(四分位距):超出Q1-1.5×IQR或Q3+1.5×IQR的点标记为异常。适用于非正态分布。
- Z-score:|Z|>3的点异常。适用于正态或近似正态分布。
- 领域知识阈值:比如logD不可能小于-10,代谢半衰期不可能小于1秒——这类硬阈值更可靠。
import numpy as np
def remove_outliers_iqr(values):
q1, q3 = np.percentile(values, [25, 75])
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
return [v for v in values if lower <= v <= upper]
写在清洗之后
坦白讲,ADMET预测的瓶颈往往不在XGBoost本身,而在于你喂进去的数据。我见过很多初学者直接从网上下载一个数据集就开始调参,结果模型在验证集上飘忽不定——一查,原来是毒性标签混进了药效数据。那感觉就像用菜刀切西瓜,刀是好刀,瓜是坏的。