分子结构表示基础:SMILES、InChI、分子图与常用分子格式

做QSAR建模的你想想看,手上一堆化合物,第一步要干什么?

把分子结构变成机器能读的格式。这件事说起来简单,但坑不少。我在刚接触这行时,就因为在SMILES里少写了一个括号,整个模型训出来全是乱的。嗯,从那天起我对分子表示就特别较真。

今天咱们把三种主流的分子表示方式聊透:SMILES、InChI、分子图。顺便把常用文件格式和互转工具也捋一遍。这些东西是QSAR建模的“地基”,地基不稳,上面再漂亮的模型也是白搭。

核心观点: 分子结构表示的本质,是把化学结构翻译成计算机能计算的离散符号或矩阵。翻译质量直接影响后续所有建模步骤。

分子结构表示知识体系 分子结构表示 SMILES InChI 分子图 线性编码 原子与键规则 层级结构 标准化标识 邻接矩阵 特征矩阵 QSAR 建模输入

1. SMILES:最流行的线性表示法

SMILES 全称是 Simplified Molecular Input Line Entry System。说白了,就是用一串ASCII字符表示一个分子。简单、紧凑、人眼也能读。

基本规则不复杂:

  • 原子:用元素符号表示,有机元素可省略氢(C、O、N等),其他元素用方括号括起来,比如 [Au] 表示金。
  • 键:单键不写,双键写 =,三键写 #。
  • 分支:用圆括号 (...) 表示支链。
  • 环:用数字标记环的起始和终点。
  • 芳香性:小写字母表示芳香原子,比如 c1ccccc1 表示苯环。

举个例子:乙醇的SMILES是 CCO。对,就这么简单。但换到复杂分子就考验人了。

我曾经被芳香性表示坑过。 当时用RDKit读一批化合物,部分分子的指纹算出来全是零。查了半天,发现原始数据里苯环用的是Kekulé形式(大写C,单双键交替),而不是芳香形式(小写c)。RDKit默认把Kekulé形式当作脂肪族处理,指纹特征完全对不上。从那以后,我所有SMILES都会先过一遍 Chem.MolFromSmiles() 再转成标准形式。

看一下常见分子的SMILES表示:

化合物SMILES说明
甲烷C单个碳原子,氢省略
乙醇CCO直链,OH 写作 O
苯c1ccccc1芳香环,小写 c
阿司匹林CC(=O)Oc1ccccc1C(=O)O含分支和环的典型例子

用RDKit生成SMILES很简单:

from rdkit import Chem

mol = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)O")
smiles_canonical = Chem.MolToSmiles(mol, canonical=True)
print(smiles_canonical)
# 输出: CC(=O)Oc1ccccc1C(=O)O

我个人习惯: 所有入库的SMILES都走一遍 canonical=True 标准化。不同人写的SMILES可能风格不同,标准化之后才能保证同一个分子只对应一个字符串。这在构建训练集时尤其重要。

2. InChI:标准化分子身份证

SMILES虽然好用,但有个致命问题——同一个分子可以有多种写法。比如硝基苯,有人写 c1ccc(cc1)[N+](=O)[O-],有人写 [O-][N+](=O)c1ccccc1。在数据库里这就成了两个“不同”的分子,你说头疼不头疼?

InChI(International Chemical Identifier)就是来解决这个问题的。它由IUPAC制定,是一种分层级的标准化标识符。同一个分子,不管从哪个工具生成,只要符合标准,结果完全一样。

InChI的结构长这样:

InChI=1S/C2H6O/c1-2-1/h1-2H,3H2

这个字符串里包含了:

  • 版本号:1S 表示标准版
  • 分子式:C2H6O
  • 连接层:/c1-2-1 表示骨架连接关系
  • 氢原子层:/h1-2H,3H2 描述氢的位置和数量

更常用的是 InChIKey——对InChI做哈希压缩得到的27字符定长字符串。适合做数据库索引和网络检索。

InChIKey 示例:LFQSCWFLJHTTHZ-UHFFFAOYSA-N(乙醇)

前14个字符来自连接层和化学式的哈希,中间8个字符来自其余层的哈希,最后5个字符表示版本和校验位。

用RDKit生成InChI也很直接:

from rdkit import Chem
from rdkit.Chem import rdMolDescriptors

mol = Chem.MolFromSmiles("CCO")
inchi = rdMolDescriptors.MolToInchi(mol)
inchikey = rdMolDescriptors.MolToInchiKey(mol)
print(inchi)      # InChI=1S/C2H6O/c1-2-1/h1-2H,3H2
print(inchikey)   # LFQSCWFLJHTTHZ-UHFFFAOYSA-N

注意: 不同版本的InChI库可能生成略有差异的字符串。我遇到过OpenBabel和RDKit对某个含配位键的分子给出的InChI不一样,后来发现是配位键的处理规则在各自的实现上有分歧。所以跨工具比对时,最好统一用一种库生成InChI。

3. 分子图:从化学结构到数学表达

SMILES和InChI都是字符串,但QSAR模型真正需要的是数值化的输入。分子图就是把分子转成图结构——原子是节点,化学键是边。

一个分子图 G = (V, E) 包含:

  • V:节点集合,每个节点对应一个原子,附带原子类型、电荷、杂化状态等特征。
  • E:边集合,每条边对应一个化学键,附带键类型(单、双、三、芳香)等特征。

分子图最常见的数值化形式有两种:

表示方式维度说明
邻接矩阵 An × nA[i][j] = 1 表示原子 i 和 j 之间有键,否则为 0。加权时可用键类型数值。
特征矩阵 Xn × d每个原子的 d 维特征向量,包含原子序数、价电子数、形式电荷、是否在环中等信息。

用RDKit提取分子图信息:

from rdkit import Chem
from rdkit.Chem import rdmolops
import numpy as np

mol = Chem.MolFromSmiles("CCO")
n_atoms = mol.GetNumAtoms()
adj = rdmolops.GetAdjacencyMatrix(mol)
print("邻接矩阵:\n", adj)

# 提取原子特征
for atom in mol.GetAtoms():
    print(f"原子 {atom.GetIdx()}: 原子序数={atom.GetAtomicNum()}, "
          f"形式电荷={atom.GetFormalCharge()}, 度={atom.GetDegree()}")

在图神经网络(GNN)兴起之前,分子图主要用于计算分子描述符。现在GNN直接在图上做卷积,效果普遍比传统指纹好。我在几个公开数据集上对比过,GNN的R²比ECFP4平均高出0.05~0.08。不过图模型的训练成本也高,小数据集上指纹还是首选。

4. 常用分子格式与互转

实际项目中,分子数据不会乖乖地只存在一种格式。我经手的项目里,从ChEMBL下载的是SDF,从PubChem拿到的是SMILES,合作方给的又是MOL2。格式互转是基本功。

几种最常见的格式:

格式特点典型用途
SDF支持多个分子,含属性字段数据库导出、虚拟筛选结果存储
MOL2包含原子类型、电荷、杂化状态分子对接、力场参数化
PDB三维坐标为主,含蛋白质/配体复合物结构生物学、分子动力学
MOLSDF的前身,单分子格式单个分子结构存储
InChI文本格式,标准化标识跨数据库分子匹配

RDKit 几乎能搞定所有常见格式的互转:

from rdkit import Chem
from rdkit.Chem import AllChem

# SMILES -> SDF
mol = Chem.MolFromSmiles("CCO")
Chem.MolToMolFile(mol, "ethanol.mol")

# SDF -> SMILES
supplier = Chem.SDMolSupplier("compounds.sdf")
for mol in supplier:
    if mol is not None:
        print(Chem.MolToSmiles(mol))

# SMILES -> MOL2 (需要三维坐标)
mol = Chem.AddHs(mol)
AllChem.EmbedMolecule(mol, AllChem.ETKDG())
Chem.MolToMol2File(mol, "ethanol.mol2")

我曾经在SDF转SMILES时踩过一个坑: SDF里如果记录了多个构象,RDKit默认只读第一个。有一次我转完才发现活性数据对应的其实是第二个构象。后来我每次读取SDF都会显式指定 removeHs=False 并检查构象索引,确保没张冠李戴。

5. 格式选择的小建议

关于格式选择,我个人的经验是:

  • 数据交换:优先用SMILES + InChIKey 双保险。SMILES方便人读,InChIKey方便去重和比对。
  • 存储三维结构:用SDF或MOL2。SMILES不含坐标,InChI也不含。做分子对接或构象分析时,必须用含坐标的格式。
  • 长期归档:建议同时保存SDF(含属性)和InChI(标准化标识)。几年前我有个项目的数据只用SMILES存,换了RDKit版本后标准化结果变了,差点丢了一批化合物的匹配。吃一堑长一智,现在我都双备份。
  • 序列化/缓存:用Python的 pickle 直接存RDKit的 Mol 对象,读写最快,但不跨语言。

核心要点回顾:

  • SMILES 是线性字符串,简洁但存在同分异构体多表示的问题
  • InChI/InChIKey 是标准化标识,适合做分子去重和跨库匹配
  • 分子图是图结构,适合GNN和深度学习方法
  • SDF/MOL2/PDB 是含坐标的格式,用于三维相关任务
  • 所有格式互转都能用RDKit一行搞定,但要注意版本和参数差异

三种表示方式各有各的用武之地。SMILES胜在轻量,InChI赢在标准,分子图强在可以直接喂给模型。具体选哪个,看你的下游任务。但有一点我特别想强调:不管用哪种,入库前一定要做标准化和校验。这是我在无数个debug夜晚之后得出的最朴素的结论。