分子表示基础:SMILES、分子图与分子指纹
做计算化学的人,天天跟分子打交道。但计算机不认识结构式,它只认数字和符号。所以呢,我们需要一套方法,把分子“翻译”给机器看。
这就引出了三种最主流的分子表示方式:SMILES字符串、分子图和分子指纹。说白了,它们是同一分子的不同“语言版本”。
SMILES字符串:化学结构的“直线代码”
SMILES全称是Simplified Molecular Input Line Entry System。我个人习惯把它叫做“一行式分子描述”。它用一个字符串,把原子、键、分支、环全部编码进去。
举个例子,乙醇长这样:
CCO
苯环呢?
c1ccccc1
你看,数字1表示成环的起点和终点。小写c表示芳香碳,大写C表示脂肪碳。规则就这么直接。
我在项目中遇到过最头疼的事——SMILES的写法不唯一。同一个分子,不同工具生成的结果可能有差异。比如水杨酸,有人写OC(=O)c1ccccc1O,有人写成c1ccccc1C(=O)OO。这时候你需要正则化或者用Canonical SMILES统一格式。
MolToSmiles(mol, canonical=True),能帮你获得标准化字符串。我每次处理SMILES前,都会先跑一遍正则化,省掉一大堆后续匹配问题。
分子图:原子的“社交网络”
你想想看,一个分子不就是一张图吗?原子是节点,化学键是边。这就是分子图的概念。每个节点携带特征(原子类型、电荷、芳香性等),每条边也带特征(键型、共轭状态等)。
为什么需要分子图?因为图神经网络(GNN)直接吃这个结构。SMILES只是一维文本,对GNN来说信息太稀薄了。
拿苯酚举例,它的分子图有三个关键要素:
| 要素 | 含义 | 苯酚示例 |
|---|---|---|
| 节点特征 | 原子种类、度、电荷、芳香性 | C(sp2)、O(sp3)、H |
| 边特征 | 键级、共轭、环内 | 单键、芳香键 |
| 邻接矩阵 | 哪个原子和哪个原子相连 | 6元环+1个侧链 |
构建分子图时,记得做标准化。我曾经因为没处理氢原子,导致图里有大量度为1的氢节点,GNN模型训练起来又慢又笨重。后来我改成了分子图显式无氢模式,模型收敛速度直接翻倍。
分子指纹:从结构到二进制向量
如果说SMILES是文字描述,分子图是拓扑结构,那分子指纹就是一套哈希后的特征向量。它的核心思路是:把分子结构“压缩”成一串固定长度的二进制位(比如1024位),每一位代表某种子结构是否存在。
最常见的两种指纹:
- Morgan指纹(ECFP):基于原子半径的圆形子结构枚举。半径2的ECFP4应用最广。
- MACCS指纹:166个预定义的结构键,专门用于药物化学。
我项目里最常用的是ECFP4,因为它能在相似性搜索和活性预测中给出非常稳定的结果。你可以在RDKit里这样生成:
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles('CCO')
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=1024)
print(fp.ToBitString())
输出是一串0和1,比如:
0000010000100010000000000010000100000000001000000100000000010000000000100000010000000000001000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
你看,大部分位是0,只有少数位置是1。这些1就代表分子中存在的特定子结构。
三种表示的实战对比
选哪种?要看具体任务。我整理了个表格,帮你快速决策:
| 维度 | SMILES | 分子图 | 分子指纹 |
|---|---|---|---|
| 数据维度 | 1D(字符串) | 2D(拓扑图) | 1D(位向量) |
| 可读性 | 人眼可读 | 需要可视化 | 不可读 |
| 存储效率 | 极高(字符串) | 中等(邻接表) | 高(固定位长) |
| 计算相似度 | 慢(需转图) | 慢(图编辑距离) | 极快(Tanimoto) |
| 深度学习输入 | 需Token化 + 嵌入 | 直接给GNN | 需展平或MLP |
嗯,这里要注意。MoleculeNet数据集中大部分任务提供了SMILES,你要自己决定怎么转。我个人习惯是:先保持SMILES原始格式做预处理,需要用图的时候现场构建。因为分子图占用内存太大,一整个数据集转成图会吃掉几个GB。
numpy数组,用scipy.spatial.distance.cdist批量计算。我测试过,1000个分子对300万数据库的Tanimoto搜索,只用不到1秒。
写代码的时候,别忘了检查SMILES是否合法。我见过太多脏数据了,比如一个Cl写成CL、漏了括号、环号不匹配等。用Chem.MolFromSmiles(smi)返回None就要果断过滤掉,不要硬转。
做到了这一步,你就掌握了分子表示的核心工具。这也是后面所有分子性质预测、图网络建模的基石。