2. 分子表示工程
在生成式分子设计项目中,分子表示不是“数据预处理的小事”,而是决定模型能生成什么、生成结果是否合法、下游能否直接使用的底层工程决策。同一个分子可以被写成 SMILES 字符串、SELFIES 字符串、分子图、3D 构象坐标,甚至 InChI 或分子指纹。选错表示,往往在训练后期才暴露问题:生成大量非法 SMILES、无法还原手性、图模型与序列模型对不齐、3D 生成结果与化学键冲突。本节围绕“选型—转换—存储”三条主线,给出可落地的工程方案。
一、四种表示的本质差异
SMILES 是线性字符串,用字符序列描述原子与键,例如乙醇为 CCO,苯为 c1ccccc1。它紧凑、易被 Transformer 处理,但存在两个工程痛点:一是同一分子有多种合法写法(规范化问题),二是模型逐字符生成时极易产生括号不匹配、价键超限等非法串。
SELFIES 是“自指”字符串,其设计目标是任意 SELFIES 串都能解码为合法分子。它把每个符号绑定到带语义的化学状态上,例如 [C][C][O] 对应乙醇。这使生成模型不必再为“合法性”单独设计惩罚项,但代价是字符串更长、与 SMILES 的直观性有差距,且并非所有 SMILES 都能无损转成 SELFIES(少数特殊价态、芳香性表达需注意)。
分子图 用节点(原子)和边(化学键)显式表达拓扑,天然适合 GNN、图扩散模型和等变网络。它保留了键级、环、手性中心等结构信息,但需要处理节点数可变、批处理对齐、邻接矩阵稀疏等问题。
3D 构象 在图上叠加原子坐标,是分子对接、构象生成、性质预测(如能量、药效团)的必要输入。它信息最完整,但获取成本最高:需要 RDKit 嵌入、ETKDG 采样或量子化学优化,且同一分子对应多个构象,存储与对齐复杂度显著上升。
二、选型决策:按任务目标反推表示
选型不应从“哪个表示更先进”出发,而应从生成目标与下游用途反推。若项目目标是“大规模生成类药分子并快速筛选”,序列模型 + SELFIES 是稳妥起点:SELFIES 保证解码合法,模型只需学习化学分布,无需额外语法约束。若目标是“生成具有特定骨架或官能团的分子”,图模型更合适,因为可以在节点/边上施加子结构约束。若目标是“生成可对接的 3D 分子”,则必须引入构象层,通常采用“图生成 + 构象生成”两阶段,或直接使用等变扩散模型。
一个常见的工程折中是:训练用 SELFIES,评估与落库用 SMILES + RDKit Mol。这样既享受 SELFIES 的合法性红利,又保证下游工具链(对接、性质计算、可视化)使用标准 SMILES。需要提醒的是,SELFIES 与 SMILES 的转换并非总是双向无损,遇到芳香性、自由基、特殊价态时,应在转换后做一次 RDKit 规范化与合法性校验。
三、转换链路与关键代码
工程上建议把 RDKit Mol 对象作为统一中间层:任何表示先转成 Mol,再从 Mol 转成目标表示。这样转换逻辑集中、可测试、可缓存。以下示例展示 SMILES → SELFIES → SMILES 的往返,以及图与 3D 构象的生成。
from rdkit import Chem
from rdkit.Chem import AllChem
import selfies as sf
smi = "CC(=O)Oc1ccccc1C(=O)O" # 阿司匹林
mol = Chem.MolFromSmiles(smi)
assert mol is not None, "SMILES 解析失败"
# SMILES -> SELFIES
selfies_str = sf.encoder(smi)
print(selfies_str) # [C][C][=Branch1][C][=O][O][C][=C][C][=C][C][=C][Ring1][=Branch1]...
# SELFIES -> SMILES(往返校验)
smi_back = sf.decoder(selfies_str)
mol_back = Chem.MolFromSmiles(smi_back)
print(Chem.MolToSmiles(mol_back) == Chem.MolToSmiles(mol)) # True
# 生成 3D 构象(ETKDG + MMFF 优化)
mol_h = Chem.AddHs(mol)
params = AllChem.ETKDGv3()
params.randomSeed = 42
cid = AllChem.EmbedMolecule(mol_h, params)
AllChem.MMFFOptimizeMolecule(mol_h, maxIters=500)
conf = mol_h.GetConformer()
coords = conf.GetPositions() # shape: (N_atoms, 3)
print(coords.shape)
这段代码体现了三个工程要点:第一,解析后必须校验,MolFromSmiles 返回 None 说明输入非法;第二,往返转换要比较规范化 SMILES,而不是原始字符串,因为同一分子写法可能不同;第三,3D 构象生成要固定随机种子,否则同一分子每次嵌入结果不同,影响可复现性。
四、存储方案:分层与格式选择
分子数据通常包含文本、图、坐标三类信息,建议分层存储而非塞进单一文件。原始层保存 SMILES/SELFIES 文本与元数据(来源、时间、批次),用 Parquet 或 JSONL,便于流式读取与版本管理。结构层保存图张量(节点特征、边索引、边特征),用 HDF5 或 PyTorch Geometric 的 InMemoryDataset,支持随机访问与批处理。构象层保存坐标、能量、生成方法,用 NPZ 或 LMDB,因为构象数量大、单条记录小,LMDB 的键值访问更高效。
需要特别注意的是手性、芳香性、质子化状态的存储一致性。SMILES 中 @ 表示手性,c 表示芳香碳;若在转换或存储中丢失这些标记,下游性质预测会出现系统性偏差。建议在入库前统一执行 Chem.MolToSmiles(mol, isomericSmiles=True),并在元数据中记录 RDKit 版本与规范化参数。