第2章 分子表示方法:SMILES、SELFIES与分子图
做分子生成,第一道坎就是——怎么让机器"看懂"分子结构。
你想想看,化学家画个苯环一目了然,但深度学习模型只认识数字和向量。所以我们必须把分子结构转成机器能处理的格式。说白了,这就是分子表示要解决的问题。
目前主流的有三种:SMILES、SELFIES 和 分子图。每种都有各自的脾气,选不对,后面训练全白搭。我在这三种表示上踩过不少坑,今天一并讲清楚。
2.1 SMILES:最流行的分子语言
SMILES 全称是 Simplified Molecular-Input Line-Entry System,简单说就是用一串 ASCII 字符来描述分子结构。
举个最简单的例子,乙醇的 SMILES 就是 CCO。苯环是 c1ccccc1。你看,原子符号、键、分支、环,全都压在一行字符串里。
CCO # 乙醇
c1ccccc1 # 苯
CC(=O)Oc1ccccc1C(=O)O # 阿司匹林
SMILES 的优点很明显:简洁、人类可读、生态完善。RDKit、OpenBabel 等工具全都支持,数据集也大多是 SMILES 格式。
但问题也很要命——同一个分子可以有多个有效的 SMILES。比如乙醇,你可以写成 CCO,也可以写成 OCC。这直接导致生成模型学到的映射关系不唯一。
所以,如果你要用 SMILES 做生成,一定要做规范化(canonicalization),把每个分子转成唯一的标准形式。
from rdkit import Chem
smiles_list = ["CCO", "OCC", "C(O)C"]
canonical = set()
for s in smiles_list:
mol = Chem.MolFromSmiles(s)
canonical.add(Chem.MolToSmiles(mol))
print(canonical) # 输出: {'CCO'}
2.2 SELFIES:解决SMILES的痛点
SELFIES(SELF-referencIng Embedded Strings)是专门为解决 SMILES 歧义问题设计的。它的核心思路是:每个符号都是自描述的,语法完全完备。
乙醇的 SELFIES 是 [C][C][O],苯环是 [C][=C][C][=C][C][=C][Ring1][Branch1]。
[C][C][O] # 乙醇
[C][=C][C][=C][C][=C][Ring1][Branch1] # 苯
你看,每个符号都明确表达了它是什么、怎么连接,不存在"多种写法"的问题。
我个人习惯在分子生成任务中优先用 SELFIES。为什么?因为 100% 语法有效。你随便生成一串 SELFIES,只要符合语法规则,它就对应一个真实分子。这对生成任务来说太重要了。
selfies 库就行。
import selfies as sf
smiles = "CCO"
selfies = sf.encoder(smiles)
print(selfies) # 输出: [C][C][O]
# 转回来
smiles_back = sf.decoder(selfies)
print(smiles_back) # 输出: CCO
SELFIES 的缺点是字符串更长,人类阅读不直观。另外,一些老工具不支持 SELFIES,需要额外转换。
2.3 分子图:保留拓扑结构
序列表示(SMILES、SELFIES)本质上是把分子压成了一维,丢失了空间拓扑信息。而分子图直接保留了原子之间的连接关系。
在分子图中:
- 节点 = 原子(带有原子类型、电荷、手性等特征)
- 边 = 化学键(带有键型、共轭状态等特征)
这种方式最贴近化学家对分子的理解,也最适合做属性预测和结构分析。
import torch
from torch_geometric.data import Data
from rdkit import Chem
smiles = "CCO"
mol = Chem.MolFromSmiles(smiles)
# 提取原子特征(简化版)
atom_features = []
for atom in mol.GetAtoms():
atom_features.append([atom.GetAtomicNum()])
x = torch.tensor(atom_features, dtype=torch.float)
# 提取边索引
edge_indices = []
for bond in mol.GetBonds():
i, j = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
edge_indices.append([i, j])
edge_indices.append([j, i])
edge_index = torch.tensor(edge_indices, dtype=torch.long).t().contiguous()
# 构建 PyG 图数据
data = Data(x=x, edge_index=edge_index)
print(data)
我记得有次做分子属性预测,用 SMILES 做输入,R² 死活到不了 0.85。换成分子图 + GNN,直接飙到 0.93。为什么?因为图结构把原子间的连接关系完整保留了下来,模型能学到更本质的化学特征。
当然,分子图也有代价:计算开销更大,而且不同分子的图结构不同,需要做批处理(batch)时得用一些技巧(比如虚拟节点)。
2.4 三种表示方法对比
| 维度 | SMILES | SELFIES | 分子图 |
|---|---|---|---|
| 表示类型 | 线性字符串 | 线性字符串 | 图结构 |
| 语法完备性 | 有歧义 | 完全完备 | 天然完备 |
| 人类可读性 | 高 | 低 | 中 |
| 生成任务适配度 | 中(需规范化) | 高 | 高 |
| 属性预测适配度 | 低 | 低 | 高 |
| 计算效率 | 高 | 中 | 低 |
| 工具生态 | 最成熟 | 发展中 | 成熟 |
2.5 实践建议
三种表示方法各有主场,选哪个取决于你的任务目标。我分享一下自己的选型逻辑:
- 做分子生成(特别是从头生成) → 优先 SELFIES。语法完备性太香了,省去大量后处理过滤。
- 做属性预测/回归任务 → 优先分子图 + GNN。拓扑信息对预测精度提升明显。
- 做数据预处理或大规模筛选 → 用 SMILES。工具生态最成熟,处理速度快。
- 做多模态或混合任务 → 可以同时用分子图 + 序列表示,做特征融合。