分子结构表示基础:SMILES、InChI、分子图与常用分子格式
做QSAR建模的你想想看,手上一堆化合物,第一步要干什么?
把分子结构变成机器能读的格式。这件事说起来简单,但坑不少。我在刚接触这行时,就因为在SMILES里少写了一个括号,整个模型训出来全是乱的。嗯,从那天起我对分子表示就特别较真。
今天咱们把三种主流的分子表示方式聊透: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:边集合,每条边对应一个化学键,附带键类型(单、双、三、芳香)等特征。
分子图最常见的数值化形式有两种:
| 表示方式 | 维度 | 说明 |
|---|---|---|
| 邻接矩阵 A | n × n | A[i][j] = 1 表示原子 i 和 j 之间有键,否则为 0。加权时可用键类型数值。 |
| 特征矩阵 X | n × 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 | 三维坐标为主,含蛋白质/配体复合物 | 结构生物学、分子动力学 |
| MOL | SDF的前身,单分子格式 | 单个分子结构存储 |
| 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夜晚之后得出的最朴素的结论。