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。虽然化学含义一样,但字符串不同,这在模型训练时就很麻烦。模型没法直接比对两个字符串是否代表同一个分子。

我个人习惯用rdkit的Canonical SMILES。它能保证同一个分子只有一个标准字符串。写代码时记得调用这个函数。
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 分子图 Canonical化 序列模型输入 分层结构 数据库去重/索引 图神经网络输入 特征矩阵+邻接矩阵 应用场景:序列模型常用SMILES,数据库管理常用InChI,图模型必选分子图
注意:SMILES中的芳香性标记容易出错。比如甲苯的甲基应该写C而不是c。我曾经犯过把甲基也小写了,导致rdkit解析成吡咯环的错,浪费了一个下午调试。

总结一下,这三种表示方法没有绝对的好坏。SMILES适合快速处理和序列模型,InChI适合做标准化索引,分子图则是深度学习的首选。我个人习惯在数据预处理阶段,把SMILES作为原始数据,构建分子图的同时计算InChIKey做去重。这样既能保证模型输入质量,又能避免重复计算。