3. 特征工程:分子描述符、指纹、图特征
老实说,特征工程才是ADMET预测模型里最见功力的环节。模型架构可以抄,超参数可以调,但特征选不好,数据质量差,后面再怎么折腾也是白搭。我在这个领域摸爬滚打了好几年,踩过的坑比吃过的盐还多,今天就把这些经验掰开了揉碎了讲给你听。
3.1 分子描述符:传统但不过时
分子描述符是什么?说白了,就是把分子的化学信息转化成数字。比如分子量、LogP、氢键供体/受体个数、可旋转键数等等。这些数字听着简单,但它们直接反映了分子的基本物理化学性质。
我记得刚入行时做过一个Caco-2渗透性预测项目。当时选了一堆描述符,什么拓扑极性表面积、摩尔折射率,一股脑全扔进模型。结果模型在训练集上表现还不错,一到验证集就崩。后来发现是描述符之间存在严重的共线性,比如分子量和体积高度相关,导致模型过拟合。
常见的描述符可以分为几类:
| 描述符类型 | 例子 | 特点 |
|---|---|---|
| 物理化学性质 | LogP, 分子量, TPSA | 直观, 易解释, 但信息量有限 |
| 拓扑描述符 | Zagreb指数, Wiener指数 | 反映分子骨架结构, 计算快 |
| 量子化学描述符 | HOMO/LUMO能级, 偶极矩 | 电子层面信息丰富, 但计算昂贵 |
| 3D描述符 | 分子体积, 溶剂可及表面积 | 考虑构象, 对结合模式敏感 |
我个人习惯用RDKit来计算描述符。它开源、高效,而且社区活跃。下面是一段常用代码:
from rdkit import Chem
from rdkit.Chem import Descriptors
mol = Chem.MolFromSmiles('CCO')
mw = Descriptors.MolWt(mol)
logp = Descriptors.MolLogP(mol)
hbd = Descriptors.NumHDonors(mol)
hba = Descriptors.NumHAcceptors(mol)
tpsa = Descriptors.TPSA(mol)
print(f"MW: {mw:.2f}, LogP: {logp:.2f}, HBD: {hbd}, HBA: {hba}, TPSA: {tpsa}")
3.2 分子指纹:降维打击式的表达
分子指纹可以看成是分子结构的「数字化编码」。它把分子映射成一个固定长度的二进制向量,每个bit代表一个子结构是否存在。为什么说它是降维打击?因为指纹可以自动捕捉大量的局部特征,而且计算效率极高。
有次我在做hERG毒性预测,用了简单的MACCS指纹。你猜怎么着?模型AUC直接提升了0.08。原因很简单——指纹能捕捉到那些微小但关键的毒性基团,比如叔胺、芳环上的特定取代模式。而这些信息,用传统的1024个描述符根本抓不住。
常见的指纹类型包括:
- MACCS keys: 166个预定义子结构,简单粗暴但很有效
- Morgan/Circular指纹: 基于ECFP,半径可调,能捕捉更大范围的局部环境
- Atom pair指纹: 编码原子间距离关系,对分子大小不敏感
- Topological torsion指纹: 编码路径上的原子类型序列
我个人建议多试试Morgan指纹,特别是ECFP4。它在很多ADMET任务上表现都很稳定。下面是如何生成指纹的代码:
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles('CCO')
# MACCS keys
maccs = AllChem.GetMACCSKeysFingerprint(mol)
# ECFP4 (Morgan radius=2)
ecfp4 = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
print(f"MACCS: {maccs.ToBitString()[:50]}...")
print(f"ECFP4: {ecfp4.ToBitString()[:50]}...")
3.3 图特征:分子的自然语言
其实分子本身就是一个图结构——原子是节点,化学键是边。用图特征来建模,某种程度上是「让模型学会看懂分子的本来面貌」。我觉得这是近年来ADMET预测领域最激动人心的进展之一。
为什么要用图特征?举个例子:传统描述符和指纹都无法区分R-和S-对映体,但图神经网络可以,因为图可以编码原子的立体构型。有次我做一个CYP酶抑制预测,用GCN直接在图结构上做消息传递,效果比用ECFP4加XGBoost还好。
常见的图特征方法:
- 节点特征: 原子类型、杂化状态、形式电荷、是否为芳香原子等
- 边特征: 键类型、是否在环中、是否共轭等
- 图池化: 全局加和/均值/注意力,把节点特征聚合成整图表示
- GNN变体: GCN、GAT、GIN、MPNN等,每种有各自的聚合策略
import torch
from torch_geometric.data import Data
from rdkit import Chem
mol = Chem.MolFromSmiles('CCO')
# 构造节点特征
atom_features = []
for atom in mol.GetAtoms():
feat = [
atom.GetAtomicNum(),
atom.GetDegree(),
atom.GetFormalCharge(),
1 if atom.GetIsAromatic() else 0,
]
atom_features.append(feat)
x = torch.tensor(atom_features, dtype=torch.float)
# 构造边索引
edge_index = []
for bond in mol.GetBonds():
i = bond.GetBeginAtomIdx()
j = bond.GetEndAtomIdx()
edge_index.append([i, j])
edge_index.append([j, i])
edge_index = torch.tensor(edge_index, dtype=torch.long).t().contiguous()
data = Data(x=x, edge_index=edge_index)
print(data)
3.4 三种特征方法如何选?
讲完了三种方法,怎么选?为什么要这么麻烦?其实没有绝对的「最优特征」,只有「最适合当前任务的特征」。
下面这张图是我自己总结的特征选择路径,做完几十个项目后慢慢形成的判断框架:
你可能会问:这三种特征能组合使用吗?当然可以。我自己的经验是,把描述符和指纹拼接起来(concatenate),然后一起喂给树模型,在很多ADMET任务上都是「性价比最高」的方案。图特征目前更适合那些数据量大、任务本身对分子拓扑敏感的场景。
1. 新任务先用描述符+MACCS打底,快速了解数据情况
2. 然后加上ECFP4,看效果有没有明显提升
3. 如果数据超过5000,尝试GIN或MPNN
4. 所有选择都要用五折交叉验证来拍板
说到底,特征工程没有银弹。我见过不少团队一上来就上GNN,结果还不如用LightGBM跑描述符。你想想看,同样的时间,花在理解数据、清洗数据、做特征分析上,回报率其实更高。嗯,这里要注意,别被「新技术」的炫目感带偏了,打牢基础比什么都重要。