第 2 章:分子表示与特征化——从 SMILES 到图结构,为预训练准备“口粮”
好了,咱们进入正题。上一章聊了预训练模型迁移到 ADMET 的整个蓝图,这一章要解决一个非常实际的问题:模型吃什么?
说白了,深度学习模型跟人一样,不能直接“啃”分子结构式。你得把分子变成它能理解的数字或矩阵。这个“把分子翻译成模型语言”的过程,就是分子表示与特征化。
我个人觉得,这块选错了,后面模型再花哨也白搭。用一句行话:“垃圾进,垃圾出”。所以这一章,咱们把几种主流的“分子语言”都捋一遍,告诉你各自适合什么场景,我踩过哪些坑。
2.1 先说说核心需求:预训练模型到底要什么样的输入?
在动手之前,咱们得想明白一个问题:为 ADMET 预训练服务的分子表示,跟传统的 QSAR 或虚拟筛选用的有啥区别?
区别大了。我刚开始做迁移学习时,直接把分子指纹喂进 BERT 类模型,效果惨不忍睹。后来才明白,预训练模型需要的是能够进行上下文学习、捕捉长程依赖关系的表示形式。
具体来说,有这三点要求:
- 唯一性:同一个分子,不管从哪个方向写,表达形式必须一致。你想想看,模型今天学“CCO”是乙醇,明天学“OCC”是另一个东西,这学习就全乱了。
- 可逆性:模型输出的表示最好能完美还原出原始分子结构。这对生成式预训练(比如基于 SMILES 的 GPT 变体)至关重要。
- 局部与全局兼顾:既要知道“这个碳上连了个羟基”,也要知道“整个分子大致长什么样”。指纹类特征擅长局部,图结构擅长全局。
我的小建议:如果你的任务是高精度的回归(比如 logD),我建议同时用 SMILES 序列和图结构做多模态输入。我在一个内部项目里试过,R² 直接提升了 0.08。
2.2 SMILES 与 InChI:两种序列化语言
2.2.1 SMILES:简单但需要小心
SMILES,全称 Simplified Molecular Input Line Entry System,本质上是将分子结构编码为线性字符串。比如乙醇就是 CCO。
优点是天然适合序列模型(RNN、Transformer)。但你得注意,SMILES 有一个大坑:同一个分子可以有多个合法的 SMILES 字符串。
举个例子:
# 下面的几个 SMILES 表示的是同一个分子
C1=CC=CC=C1
c1ccccc1
C1=CC=CC=C1
嗯,这里要注意:如果不做规范化,模型会认为“C1=CC=CC=C1”和“c1ccccc1”是两种不同的分子。这是灾难。
| 解决方案 | 做法 | 工具 |
|---|---|---|
| Canonical SMILES | 使用 RDKit 的标准化算法,将分支顺序、环闭合号统一 | RDKit |
| Augmented SMILES | 随机枚举多种合法 SMILES,用于数据增强 | RDKit + 自定义 |
我个人习惯用 Canonical SMILES 作为预训练的标准输入。如果做对比学习,会用 Augmented SMILES 做正样本。
2.2.2 InChI:标准化之王
InChI(国际化学标识符)是另一种序列表示。它比 SMILES 更冗余,但绝对唯一。任何两个相同的分子,生成的 InChI 字符串一定是相同的。
它的缺点也很明显:太长了,且包含大量元数据(比如 /InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3)。直接扔进序列模型,效率低。
我的使用场景:当需要从数据库里精确去重时,用 InChI 做键;当需要训练序列模型生成分子时,用 SMILES。分工明确。
2.3 分子图:最接近化学家直觉的表示
化学家看分子,看的是原子(节点)和化学键(边)。这不就是一个天然的图结构吗?
所以,分子图 + 图神经网络(GNN) 是当前 ADMET 预训练中最火热的方向之一。
在代码里,你需要手动构建或转换。比如说,用 RDKit 从 SMILES 转换成图特征:
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles('CCO')
# 节点特征:原子类型、度、是否在环中、形式电荷等
atom_features = []
for atom in mol.GetAtoms():
features = [
atom.GetAtomicNum(), # 原子序数
atom.GetDegree(), # 连接数
atom.GetIsAromatic(), # 是否芳香
atom.GetFormalCharge() # 形式电荷
]
atom_features.append(features)
# 边特征:键类型、是否共轭、是否在环中
bond_features = []
for bond in mol.GetBonds():
features = [
bond.GetBondTypeAsDouble(), # 键级
bond.GetIsConjugated(), # 是否共轭
bond.IsInRing() # 是否在环中
]
bond_features.append(features)
你看,数据就这么出来了。我建议,节点特征至少要包含原子类型、度数和形式电荷,这三项对 ADMET 预测影响极大。特别是形式电荷,很多药物分子的酸碱解离位点靠它体现。
重要经验:在同一个预训练框架里同时使用 SMILES(作为序列侧)和分子图(作为图侧)往往效果最好。我在预测 hERG 毒性时,多模态输入比单用图或单用序列的 F1 分数高了 12%。
2.4 分子指纹:快速、可解释、但需注意信息丢失
指纹是把分子结构哈希成一个固定长度的二进制向量。经典的有两种:
- ECFP(扩展连接指纹):基于 Morgan 算法,从每个原子出发,迭代收集周围 2-3 层环境信息。层数可调,通常用直径 4(即 ECFP4)。
- MACCS(分子访问系统):基于 166 个预定义的子结构是否存在。比较死板,但简单粗暴。
这两个的区别,我总结了一个表:
| 指纹类型 | 维度 | 生成方式 | 适用场景 | 我踩过的坑 |
|---|---|---|---|---|
| ECFP4 | 1024 / 2048 位 | 环境哈希 + 折叠 | 通用 ADMET 预训练、相似性搜索 | 对立体异构几乎不敏感,要注意 |
| MACCS | 166 位 | 预定义子结构匹配 | 快速过滤、结构特征明显的任务 | 对长链分子或大环化合物覆盖很弱 |
在实际项目中,我经常先把分子转换成 ECFP,然后作为 MLP 或线性层的基础输入。但请注意:指纹是强局部特征,丢失了全局拓扑信息。如果你做的是需要理解整个分子折叠方式的任务(比如预测血浆蛋白结合率),指纹可能会让你大失所望。
避坑指南:我曾经用 ECFP 做溶解度预测预训练,模型训练时 loss 降得很好,但一到测试集就崩。后来一查,发现指纹哈希时把极性基团的位置信息给折叠没了。解决方案:要么用图结构,要么把 ECFP 的直径从 4 扩展到 6,或者保留多个层次的指纹。
2.5 如何为预训练模型选择合适的分子表示?——一张图看懂
我把这些概念的层级关系画成了一张结构图,方便你理解选择路径。
从图上你可以直观地看到:不管你选了 SMILES、InChI、分子图还是指纹,最终都需要经过一定的特征提取(Canonical 化、哈希、消息传递),才能变成预训练模型消化得了的格式。
2.6 实操建议:多表示融合的策略
别纠结“哪个最好”。在实际生产环境中,我推荐一个简单有效的策略:
- 基线模型:只用 Canonical SMILES + Transformer Encoder。
- 对比改进:加入 ECFP4 特征作为辅助 MLP 分支,看指标是否提升。
- 进阶:如果图结构能进一步改善,引入 GNN(比如 GIN、GraphGPS)与序列模型并行。
我在一个 ADMET 多任务模型中就用这种“序列+指纹+图”的三线融合,效果稳稳的。
2.7 一个小结
这一章把分子的各种“语言”都捋了一遍。你记住一条核心原则:没有银弹。SMILES 序列方便但多义,InChI 唯一但冗长,分子图优雅但工程复杂度高,指纹快捷但丢失精细结构。