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

做ADMET预测,第一个要解决的问题就是:怎么让计算机看懂分子结构。

你想想看,一个化合物,对咱们来说是一张二维结构式。但对计算机来说,它只是一堆原子怎么连、电子怎么排的抽象信息。所以,必须找到一种方式,把这堆信息“翻译”成机器能处理的东西。

这个翻译过程,我把它概括为三种核心表示法:SMILES、分子图、分子指纹。它们各自解决了不同层面的问题。今天咱们就把这三样东西彻底搞明白。

一句话总结:SMILES是给人类和数据库用的“行内简写”,分子图是给图神经网络用的“拓扑关系”,分子指纹是给传统机器学习模型用的“特征向量”。三者层层递进,缺一不可。

一、SMILES:一行字符串里的化学世界

SMILES,全称是Simplified Molecular Input Line Entry System。听起来很绕口,说白了就是一个“化学结构速记法”。

我记得在我刚入行那会儿,做化合物库筛选,几百个分子要我一个个画出来再存成文件?那效率简直没法看。后来学会用SMILES,一行字符串就能代表一个完整分子,瞬间解放了双手。

它怎么工作的?看个例子就懂了。

化合物 SMILES表示 说明
乙醇 CCO C-C-O,三个原子直连
苯 c1ccccc1 小写c代表芳香碳,数字1标记环起点
对硝基苯甲酸 O=C(O)c1ccc(cc1)[N+](=O)[O-] 包含分支、环、电荷标记

核心规则其实没几条:

  • 原子用元素符号表示:大写字母开头,比如C、O、N。特殊状态用小写,比如芳香碳是c。
  • 单键不写,双键用=,三键用#:比如二氧化碳 O=C=O。
  • 环用数字标记:第一个环开口处标1,另一个开口也标1。
  • 分支用括号括起来:比如异丙醇 CC(C)O。
  • 电荷用方括号加+或-:比如钠离子 [Na+]。

这里有个坑,我以前踩过:SMILES没有唯一的表示法。同一个分子,不同软件生成的SMILES字符串可能不一样。比如甲苯,有的软件生成Cc1ccccc1,有的生成C1=CC=C(C=C1)C。这在做数据库匹配时会出大问题。

规避指南:我曾因为SMILES不统一,导致两个化合物库的分子对不上号。解决方案是使用Canonical SMILES(规范SMILES)。像RDKit、OpenBabel这些工具,都内置了规范化算法。入库前一律跑一趟标准化,就不会翻车。

SMILES的最大优点,是极致的简洁。一行字符串可以存进文本文件,也可以直接放进数据库的字段里。它把二维结构压缩成了一维字符串,节省大量存储空间。

但缺点也很明显:它丢失了拓扑结构信息。虽然人能看懂,机器也能做简单的子结构匹配,但要对整个分子做图级别的运算——比如图神经网络的前向传播——SMILES就力不从心了。

二、分子图:还原本质的结构表示

我常说,分子天生就是一张图。原子是节点,化学键是边。节点上带有元素类型、电荷、杂化状态等属性,边上带有键级、是否芳香等属性。

这种表示法,对图神经网络来说简直是量身定做。现在的ADMET预测模型,用得最多的就是图神经网络。为什么呢?因为它能完整保留分子的拓扑结构,不会丢失任何连接关系。

让我用代码展示一下RDKit怎么构建分子图:

from rdkit import Chem

# 从SMILES生成分子对象
mol = Chem.MolFromSmiles('CCO')

# 获取原子信息
for atom in mol.GetAtoms():
    print(f'原子索引: {atom.GetIdx()}, 元素: {atom.GetSymbol()}, 价态: {atom.GetTotalValence()}')

# 获取化学键信息
for bond in mol.GetBonds():
    begin = bond.GetBeginAtomIdx()
    end = bond.GetEndAtomIdx()
    bond_type = bond.GetBondType()
    print(f'键: {begin} - {end}, 类型: {bond_type}')

输出结果:

原子索引: 0, 元素: C, 价态: 4
原子索引: 1, 元素: C, 价态: 4
原子索引: 2, 元素: O, 价态: 2
键: 0 - 1, 类型: SINGLE
键: 1 - 2, 类型: SINGLE

你看,一个乙醇分子,在RDKit内部就是以图的形式存在的。它的邻接矩阵就是一个3x3的稀疏矩阵,节点特征矩阵则是3行N列(N是特征维度)。

在我实际做ADMET项目时,遇到的第一个问题就是图的大小不一致。小分子只有几个原子,大分子可能有上百个。怎么办?一般采用填充或截断的策略:设定一个最大原子数(比如50),不足的补零,超出的截断。经验告诉我,大部分药物候选分子的原子数都在30到50之间,所以这个值一般设50就够用了。

技巧:分子图里节点的特征,我通常设置成[原子类型, 度(连接数), 形式电荷, 杂化状态, 是否芳香, 氢原子数, 质量数]。边特征设置为[键级编码, 是否共轭, 是否在环内]。这套组合拳,能在图神经网络里取得不错的效果。

分子图的优点是任何结构信息都不会丢失。但代价也很直接——计算开销大。每一层图卷积都要做矩阵乘法,图越大越慢。而且训练图神经网络需要GPU,不像传统机器学习那么好伺候。

三、分子指纹:从结构到特征的压缩

如果觉得图神经网络太重了,想用随机森林或XGBoost搭一个ADMET预测模型怎么办?这时候,分子指纹就派上用场了。

分子指纹的本质,就是把分子的结构信息压缩成一个固定长度的二进制向量。好比给分子拍了一张“素颜照”,把关键特征提取出来,变成一串0和1。

我最早接触的是MACCS指纹,它只有166位。说白了就是预定义了166个结构片段,每个片段对应一个位,有就置1,没有就置0。简单粗暴,但在很多场景下已经足够好。

后来发现更强大的是ECFP(Extended-Connectivity Fingerprints),也叫Morgan指纹。它的原理是:从每个原子开始,不断向外扩展半径,把每个半径范围里的子结构哈希成一个整数,映射到指纹的某个位上。半径越大,覆盖的结构信息越丰富。

看代码:

from rdkit import Chem
from rdkit.Chem import AllChem

mol = Chem.MolFromSmiles('CCO')

# 生成ECFP4指纹(半径=2,位长=2048)
ecfp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)

# 转为numpy数组
import numpy as np
arr = np.array(ecfp)
print(f'指纹长度: {len(arr)}')
print(f'前20位: {arr[:20]}')
print(f'置1的位总数: {sum(arr)}')

输出:

指纹长度: 2048
前20位: [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
置1的位总数: 8

可以看到,一个大分子经过指纹化后,只剩下几个位是1。这就像给分子打了一个“指纹印记”,虽然不能还原出原始结构,但可以用于相似性比较和机器学习。

下面这个表格,是我在实际项目中比较过的主流指纹:

指纹类型 位长 特点 适用场景
MACCS 166 固定片段集,速度快,但信息量有限 快速筛选、粗略分类
ECFP4 1024 / 2048 基于原子邻域,信息丰富,ADMET预测首选 回归模型、精确分类
FCFP 1024 / 2048 与ECFP类似,但基于特征 药效团匹配
RDKit 自定义 基于路径片段,可解释性强 特征分析、结构-活性关系

分子指纹的最大优点是高效。不管分子多复杂,最后都变成几百或几千维的位向量。这可以直接喂给逻辑回归、随机森林、XGBoost这些传统模型。而且计算指纹的速度极快,百万级分子的库,几分钟就能算完。

但缺点也要心里有数:指纹是有损压缩。两个不同的分子,可能生成相同的指纹(虽然概率很低)。另外,指纹解释性差——你知道某一位是1,但说不清它代表哪个具体的子结构。

四、三种表示法的关联与选择

这三者不是互斥的,而是层层递进的关系。我习惯用下面这张图来展示它们之间的关联:

分子结构三种表示法的关联与选择 原始分子结构 SMILES字符串 分子图 (Graph) 分子指纹 (Fingerprint) 一维字符串 存储友好,有歧义 适合数据库、子结构匹配 二维拓扑结构 信息完整,无损失 适合图神经网络 固定长度位向量 有损压缩,速度快 适合传统机器学习 推荐:SMILES → 分子图(用于GNN),或 SMILES → 指纹(用于ML)

看了这张图,你应该能感觉到我的推荐路径了:原始结构存成SMILES(方便数据交换),做图神经网络时转成分子图(信息无损),做传统机器学习时转成分子指纹(高效稳定)。

举个实际项目的例子。我之前做一个CYP450抑制的预测项目,数据量不大,只有2000个分子。数据表里存的是SMILES。我同时试了两条路:

  • 路线A:SMILES直接算ECFP4指纹,然后用XGBoost训练。训练时间不到30秒,测试AUC 0.88。
  • 路线B:SMILES转分子图,用图卷积网络训练。训练时间2小时,测试AUC 0.90。

你看,虽然GNN效果好一丢丢,但为了这点提升多花两个小时,值不值?这就要看你的项目需求了。快速出结论的场景我用指纹,追求极致精度的场景我用图。

经验:如果你刚开始做ADMET预测,我建议先上指纹+传统模型。这套组合下限高、调参快,能让你快速验证特征工程是否有效。等模型成熟了再考虑用GNN去挖掘更深层的结构关系。千万别一上来就上GNN,那玩意调起参来能让你怀疑人生。