3、分子表示方法:SMILES、InChI、分子图
咱们聊聊分子表示这回事。
做虚拟筛选,第一步就是把分子“喂”给计算机。可分子是三维的,有原子、有键、有电荷,计算机怎么懂?所以我们需要一套语言,把化学结构翻译成机器能读懂的字符串或数据结构。这就是分子表示方法要解决的问题。
我刚开始接触这个领域时,也觉得SMILES、InChI这些只是些格式,后来才发现,选错表示方法,模型性能能差出一大截。你想想看,一个字符串写错了,整个筛选流程就全歪了。嗯,这节咱们就把三种主流方法彻底捋清楚。
3.1 SMILES:最流行的线性表示
SMILES,全称是Simplified Molecular Input Line Entry System。说白了,就是用一串ASCII字符把分子结构描述出来。
它怎么做到的?核心思路是深度优先遍历分子图。我从某个原子出发,把沿途的原子符号、键的类型、分支、环都编码成字符串。
3.1.1 基本规则
- 原子:用元素符号表示,有机元素通常大写,比如C、O、N。特殊原子用方括号,比如[Fe]、[Cu+2]。
- 键:单键省略,双键用
=,三键用#,芳香键用小写字母,比如c1ccccc1表示苯环。 - 分支:用圆括号表示,括号里的内容是挂在前面原子上的侧链。
- 环:用数字标记环的打开和闭合。
举个例子,乙醇的SMILES是CCO。很简单对吧?但如果是复杂分子,比如紫杉醇,它的SMILES能写满一整行。我在项目中遇到过用正则表达式解析SMILES结构的场景,那叫一个痛苦。
3.1.2 SMILES的局限性
SMILES有个大问题:不唯一。同一个分子,从不同原子开始遍历,会生成不同的字符串。比如乙醇,有人写CCO,有人写OCC。虽然化学含义一样,但字符串不同,这在模型训练时就很麻烦。模型没法直接比对两个字符串是否代表同一个分子。
from rdkit import Chem
mol = Chem.MolFromSmiles('CCO')
canonical_smiles = Chem.MolToSmiles(mol, canonical=True)
print(canonical_smiles) # 输出总是 'CCO'
3.2 InChI:面向国际的标准
InChI,全称International Chemical Identifier。它是由IUPAC开发的,目标是成为全球统一的分子标识符。
和SMILES不同,InChI更加结构化。它把分子信息分层编码,每一层都有明确的含义。
3.2.1 InChI的层次结构
一个典型的InChI字符串长这样:InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3。我们来拆解一下:
- 版本号:
1S表示标准InChI版本1。 - 分子式层:
C2H6O,这是经验式。 - 连接层:
/c1-2-3,描述了原子的连接顺序。 - 氢原子层:
/h3H,2H2,1H3,指定了氢的位置和数量。
这种分层设计的优势在于,你可以根据需要选择不同的层次进行比较。比如只需要分子式层次,就能快速判断两个分子是否具有相同的元素组成。InChI还有一个哈希版本叫InChIKey,固定27个字符,非常适合做数据库索引。
3.2.2 什么时候用InChI?
我在做大规模数据库去重时,特别喜欢用InChIKey。它比Canonical SMILES更稳定。记得有个项目,需要合并来自三个不同供应商的分子库,SMILES格式差异很大,但用InChIKey一匹配,重复分子全出来了。
3.3 分子图:从离散到连续
字符串表示虽然方便,但对于深度学习模型来说,它们需要的是向量或图结构。分子图应运而生。说白了,就是把原子当作节点,化学键当作边,构建一个图神经网络能吃的输入。
3.3.1 图的定义
一个分子图可以形式化表示为 G = (V, E)。其中V是节点集合,每个节点携带一个特征向量,比如原子类型、电荷、杂化状态等。E是边集合,每条边携带一个特征向量,比如键的类型、是否在环中等。
3.3.2 从SMILES到分子图
实际中,我们通常先用rdkit把SMILES转成Mol对象,再手动提取原子和键的特征,构建邻接矩阵和特征矩阵。
from rdkit import Chem
import numpy as np
def mol_to_graph(smiles):
mol = Chem.MolFromSmiles(smiles)
n_atoms = mol.GetNumAtoms()
# 节点特征:每个原子用一个one-hot向量表示
atom_features = []
for atom in mol.GetAtoms():
feature = [0] * 10 # 假设有10种原子类型
symbol = atom.GetSymbol()
if symbol == 'C':
feature[0] = 1
elif symbol == 'O':
feature[1] = 1
# ... 其他类型
atom_features.append(feature)
# 邻接矩阵
adj = np.zeros((n_atoms, n_atoms))
for bond in mol.GetBonds():
i = bond.GetBeginAtomIdx()
j = bond.GetEndAtomIdx()
adj[i, j] = 1
adj[j, i] = 1
return np.array(atom_features), adj
graph = mol_to_graph('CCO')
print(graph[0].shape) # 节点特征矩阵形状
这段代码虽然简单,但实际项目中特征工程要复杂得多。我曾经就因为忘记处理氢原子,导致图里全是孤立的碳原子,模型训练直接崩了。
3.4 三种表示方法的对比
| 特性 | SMILES | InChI | 分子图 |
|---|---|---|---|
| 表达形式 | 线性字符串 | 分层字符串 | 图数据结构 |
| 唯一性 | 不唯一(但可用Canonical) | 唯一 | 依赖生成方式 |
| 可读性 | 较高 | 较差 | 需要可视化工具 |
| 计算效率 | 高 | 中等 | 低(图构建开销) |
| 适合模型 | RNN、Transformer | 不适合直接训练 | GNN、GAT |
| 存储空间 | 小 | 较大 | 大 |
为什么会有这些差异?SMILES最简单直接,但它的线性结构会丢失拓扑信息。InChI标准化程度最高,但字符串冗长。分子图保留了完整的结构信息,但构建和存储成本高。在虚拟筛选的实际工作中,我建议初学者先把SMILES和分子图画扎实,InChI可以后面再补。
3.5 知识体系概览
下面这张图总结了三者之间的关系和各自的应用场景。你可以把SMILES和InChI看作是分子在“文本世界”里的名片,而分子图是分子在“数学世界”里的身世。
总结一下,这三种表示方法没有绝对的好坏。SMILES适合快速处理和序列模型,InChI适合做标准化索引,分子图则是深度学习的首选。我个人习惯在数据预处理阶段,把SMILES作为原始数据,构建分子图的同时计算InChIKey做去重。这样既能保证模型输入质量,又能避免重复计算。