分子表示基础:SMILES、分子图与分子指纹

做计算化学的人,天天跟分子打交道。但计算机不认识结构式,它只认数字和符号。所以呢,我们需要一套方法,把分子“翻译”给机器看。

这就引出了三种最主流的分子表示方式:SMILES字符串、分子图和分子指纹。说白了,它们是同一分子的不同“语言版本”。

SMILES字符串:化学结构的“直线代码”

SMILES全称是Simplified Molecular Input Line Entry System。我个人习惯把它叫做“一行式分子描述”。它用一个字符串,把原子、键、分支、环全部编码进去。

举个例子,乙醇长这样:

CCO

苯环呢?

c1ccccc1

你看,数字1表示成环的起点和终点。小写c表示芳香碳,大写C表示脂肪碳。规则就这么直接。

我在项目中遇到过最头疼的事——SMILES的写法不唯一。同一个分子,不同工具生成的结果可能有差异。比如水杨酸,有人写OC(=O)c1ccccc1O,有人写成c1ccccc1C(=O)OO。这时候你需要正则化或者用Canonical SMILES统一格式。

小技巧:RDKit提供了 MolToSmiles(mol, canonical=True),能帮你获得标准化字符串。我每次处理SMILES前,都会先跑一遍正则化,省掉一大堆后续匹配问题。

分子图:原子的“社交网络”

你想想看,一个分子不就是一张图吗?原子是节点,化学键是边。这就是分子图的概念。每个节点携带特征(原子类型、电荷、芳香性等),每条边也带特征(键型、共轭状态等)。

为什么需要分子图?因为图神经网络(GNN)直接吃这个结构。SMILES只是一维文本,对GNN来说信息太稀薄了。

拿苯酚举例,它的分子图有三个关键要素:

要素 含义 苯酚示例
节点特征 原子种类、度、电荷、芳香性 C(sp2)、O(sp3)、H
边特征 键级、共轭、环内 单键、芳香键
邻接矩阵 哪个原子和哪个原子相连 6元环+1个侧链

构建分子图时,记得做标准化。我曾经因为没处理氢原子,导致图里有大量度为1的氢节点,GNN模型训练起来又慢又笨重。后来我改成了分子图显式无氢模式,模型收敛速度直接翻倍。

避坑指南:有些数据库保存的分子图是“隐式氢”,有些是“显式氢”。不统一的话,图卷积的结果就会跑偏。你最好在数据预处理阶段就定好策略,要么全显式,要么全去除。我一般默认不去氢,但特征里会加一个“度”字段,让模型自己学会该关注什么。

分子指纹:从结构到二进制向量

如果说SMILES是文字描述,分子图是拓扑结构,那分子指纹就是一套哈希后的特征向量。它的核心思路是:把分子结构“压缩”成一串固定长度的二进制位(比如1024位),每一位代表某种子结构是否存在。

最常见的两种指纹:

  1. Morgan指纹(ECFP):基于原子半径的圆形子结构枚举。半径2的ECFP4应用最广。
  2. 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适合存储和交换,分子图适合深度学习模型,分子指纹适合快速相似性计算。在MoleculeNet数据集中,三者都会用到,我一般把它们组合使用。
三种分子表示的关系 SMILES 字符串 CCO / c1ccccc1 分子图 节点+边+邻接矩阵 分子指纹 二进制位向量 SMILES 适用场景 • 数据存储与交换(SDF/CSV) • 文件传输与数据库索引 • 人机可读的分子标识 分子图 适用场景 • 图神经网络(GNN)输入 • 反应预测与逆合成 • 分子动力学粗粒化 指纹 适用场景 • 相似性搜索 • 机器学习基线 • 聚类与虚拟筛选 RDKit: MolFromSmiles(smi) RDKit: GetMorganFingerprintAsBitVect() SMILES 是人类与机器对话的桥梁 | 分子图保留完整拓扑信息 | 指纹实现快速大规模计算

三种表示的实战对比

选哪种?要看具体任务。我整理了个表格,帮你快速决策:

维度 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就要果断过滤掉,不要硬转。

做到了这一步,你就掌握了分子表示的核心工具。这也是后面所有分子性质预测、图网络建模的基石。