3. 数据准备:分子表示方法
说实话,做ADMET预测这么多年,我最深的一个体会是:模型选得再好,分子表示没搞对,一切都是白搭。你想想看,模型就是个函数近似器,它吃的特征决定了它能学到什么。今天我们就来聊聊这个“数据准备”环节里最关键的一步——分子表示。
3.1 SMILES:最直白,但也最“坑”
SMILES(Simplified Molecular Input Line Entry System)是一种用ASCII字符串表示分子结构的方法。它简单、直观,也是目前公开数据集里最常用的格式。
举个例子,乙醇的SMILES就是CCO,很直接吧?
但我得跟你说,用SMILES直接扔给模型,问题很大。
我曾经就踩过这样一个坑。从PubChem下载了一批数据,里面SMILES五花八门。有的用了芳香大写的C,有的用了小写c;有的环闭合顺序不一样。模型训练时Loss降得还行,但一交叉验证,不同fold之间的AUC波动特别大。查了半天,原来是同一个分子在训练集和验证集里被表示成了不同字符串。
模型以为那是两个不同的分子。
所以,我个人在数据清洗里,一定会做的第一件事就是SMILES标准化。很多工具箱都能做,这里我习惯用RDKit的CanonSmiles函数:
from rdkit import Chem
smiles_raw = "c1ccccc1"
mol = Chem.MolFromSmiles(smiles_raw)
smiles_canon = Chem.MolToSmiles(mol, canonical=True)
print(smiles_canon) # 输出:c1ccccc1
标准化之后,同一个分子不管怎么写,最终都变成同一个字符串。别小看这一步,它直接影响后面交叉验证的公平性。
那SMILES直接作为输入好不好?嗯,这里要注意:对于大多数深度学习模型(比如Transformer),SMILES字符串会被切分成一个一个的token。但分子的拓扑结构是非序列的,而SMILES是一种人为的线性化结果。模型可能学到的是“书写习惯”,而不是真正的化学结构。我个人习惯只在做SMILES数据增强(比如随机生成不同的非规范SMILES)时,才直接用它做输入。平时更推荐下面这种方式。
3.2 分子指纹:特征工程的老祖宗
分子指纹,说白了就是把二维分子结构编码成一个固定长度的二进制向量。每个bit位代表是否存在某个特定的子结构(比如“有苯环”、“有羰基”)。
最经典的是MACCS指纹,它包含166个预定义的化学子结构问询。我遇到很多传统的QSAR模型,都在用这套指纹。
还有更灵活的ECFP(Extended Connectivity Fingerprint),也叫摩根指纹。它不依赖于预定义的碎片库,而是从每个原子出发,按半径扩展去生成特征。你可以控制指纹的长度和半径大小。我个人最常用的是ECFP4,也就是半径等于2,生成的指纹长度通常设为2048 bit。
用RDKit生成ECFP4的代码很简单:
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles("CCO")
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
arr = np.array(fp)
print(arr.shape) # (2048,)
避坑指南:指纹不适用所有场景。我在做突变性(AMES)预测时,ECFP4效果很好。但做水溶性(LogS)预测时,指纹表现反而一般。因为水溶性更依赖全局物化属性(如极性表面积、氢键供体数),而不是局部子结构。所以,我建议你根据任务类型,搭配不同的表示方法。
下表是我根据自己项目经验,对三种常用指纹的总结:
| 指纹类型 | 维度 | 描述 | 适用场景 |
|---|---|---|---|
| MACCS | 166 bit | 预定义子结构,从MACCS II系统移植而来 | 聚类分析、活性悬崖检测 |
| ECFP4 (Morgan) | 1024 - 4096 bit | 圆形环境特征,基于扩展连接性算法 | 分类任务(毒性、CYP抑制) |
| FCFP (Morgan) (Feature based) |
1024 - 4096 bit | 基于原子性质的圆形特征 | 回归任务(LogP, LogS) |
3.3 分子描述符:量化分子的“体检报告”
如果你想让模型理解分子是什么样的,描述符就是最直接的量化方式。它把分子拆解成一个个具体的数字指标:分子量(MolWt)、脂水分配系数(LogP)、氢键供体数(NumHDonors)、极性表面积(TPSA)等等。
RDKit里内置了超过200种描述符。你可以一次性计算所有描述符:
from rdkit.Chem import Descriptors
mol = Chem.MolFromSmiles("CN1C=NC2=C1C(=O)N(C(=O)N2C)C")
# 计算几个常用的
tpsa = Descriptors.TPSA(mol)
logp = Descriptors.MolLogP(mol)
hbd = Descriptors.NumHDonors(mol)
heavy_atom = Descriptors.HeavyAtomCount(mol)
print(f"TPSA: {tpsa:.2f}, LogP: {logp:.2f}, HBD: {hbd}, HeavyAtoms: {heavy_atom}")
我个人觉得,描述符最大的优点是可解释性强。特征重要性分析的时候,你可以直接说“TPSA这个特征对我的交叉验证结果影响最大”。但要注意一个问题:描述符是人工设计的,它依赖我们已有的化学知识。我遇到过一些新型分子骨架,它的某些描述符计算出来会丢失重要信息。
还有,不同描述符的取值范围差异很大。分子量可能几百上千,但氢键供体数只有0~5。如果不做标准化,模型(尤其是SVM、线性回归)很容易被数值大的特征带偏。所以,我通常会在做交叉验证时,把标准化步骤放在每一折内部进行,避免数据泄露。
我个人习惯是在处理ADMET数据时,做一个混合表示。什么意思?把MACCS指纹(166维)和一组核心描述符(~30维,包括LogP、TPSA、HBD、HBA、RotBonds等)拼接起来,形成一个196维的特征向量。这样做,既能保留局部子结构信息,又能引入全局物化属性。在我做过的4~5个项目中,这种混合表示方法的效果普遍优于单一表示方法。
3.4 三种方法的横向对比
你看,SMILES、指纹、描述符各有侧重点。没有绝对的好与坏,关键要看你的任务和数据量。下面的SVG图总结了它们的关系:
3.5 我的一点建议
说了这么多,总结下来就是:不要迷信某一种表示方法。我在不同的ADMET任务上做过对比实验,下面这个表是我个人的经验总结,你可以作为参考:
| 预测任务 | 我推荐的主表示方法 | 补充说明 |
|---|---|---|
| HIA(人体肠道吸收) | 混合(ECFP4 + 描述符) | 描述符中的TPSA和LogP非常关键 |
| PPB(血浆蛋白结合率) | 描述符为主 | 脂溶性、电荷分布相关描述符影响大 |
| AMES(细菌回复突变) | ECFP4指纹 | 局部警示结构敏感性更重要 |
| hERG(心脏毒性) | 混合(FCFP + 描述符) | 碱性胺、芳香环这些子结构很关键 |
| CYP 2D6抑制 | MACCS / ECFP | 特定的代谢位点信息至关重要 |
说白了,数据准备阶段多花点心思,后面交叉验证才能跑得稳。我建议你拿到数据后,先算好这些不同的分子表示方法,然后存好备用。到正式交叉验证时,就可以快速切换对比,找到最适合你任务的那个表示方案。
嗯,这次就聊到这儿。关键记住:SMILES要标准化,指纹要选好参数,描述符要做标准化。这三件事做到了,你离一个靠谱的ADMET模型就不远了。