SMILES 编码:从分子结构到一行字符串
做分子表示学习,你绕不开的一个东西,就是 SMILES。
说白了,它就是把化学结构写成一行 ASCII 字符。
看起来简单,但背后有不少门道。
为什么需要 SMILES?
在计算机里表达分子,最早大家用连接表、矩阵之类的东西。
虽然信息完整,但体积大、难处理、不能直接扔进文本搜索。
比如一个复杂分子,它对应的邻接矩阵可能是几百乘几百的大块头。
我刚开始做化学信息学的时候,最头疼的就是怎么高效存这些分子。
存成 MOL 文件吧,一个分子好几 KB;用 InChI 吧,字符串又长得离谱。
后来发现 SMILES,一行搞定,几 KB 变几十字节,舒服多了。
SMILES 的历史
SMILES 是 1980 年代由 David Weininger 在 US Environmental Protection Agency (EPA) 开发的。
初衷很朴素:找个轻量级的方式让化学家能手动输入分子。
后来 Daylight Chemical Information Systems 把它做成了标准。
现在的 SMILES 大致有三个版本:
| 版本 | 特点 | 我的看法 |
|---|---|---|
| Daylight SMILES | 最原始,规则简单 | 基本够用,但异构体处理弱 |
| OpenSMILES | 开源社区规范 | 我项目里用的就是这套 |
| Canonical SMILES | 确定性的唯一字符串 | 做分子匹配时必用 |
我的建议:如果你在做分子生成或对比,优先用 Canonical SMILES。否则同一个分子可能出来多种写法,那就会导致很多奇怪的 bug。
基本语法规则
SMILES 的核心思想只有一个:用原子符号表示节点,用标点符号表示键和结构。
原子表示
常规原子直接写元素符号,注意大写字母在前:
- 碳:
C - 氧:
O - 氮:
N - 氯:
Cl(两个字母,C 大写)
遇到同位素或化合价异常的情况,就用方括号括起来:
- 带电荷的原子:
[Na+] - 同位素:
[18O] - 异常价态:
[C-](卡宾)
嗯,这里要注意:方括号不只是用来表示离子,只要原子的氢原子数跟默认规则不同,就得显式写出。
我有个学生写苯环时顺手写了个 C1=CC=CC=C1,结果解析出来氢原子数全错了 —— 因为碳默认连 4 个键,但环里默认连两个,剩下的两个氢自动补上。但如果你改成 [CH]1=[CH][CH]=[CH][CH]=[CH]1,意思就不一样了,每个碳显式指定只连一个氢。
键的表示
SMILES 的键表示非常直观:
| 键型 | 符号 | 示例 |
|---|---|---|
| 单键 | - 或省略 |
CC(乙烷) |
| 双键 | = |
C=C(乙烯) |
| 三键 | # |
C#C(乙炔) |
| 芳香键 | : |
c1ccccc1(苯) |
很多人刚学时有个困惑:为什么单键可以省略?
根据 SMILES 默认规则,相邻的两个非氢原子之间默认就是单键。你想想看,如果你每个单键都写个 -,那字符串会变得非常啰嗦。CC 比 C-C 短得多,读起来也快。
关键点:SMILES 的默认键是单键。双键、三键、芳香键必须显式写出。
分支与环
SMILES 用圆括号表示分支:
C(C)C # 丙烷,中间的碳连接一个甲基
CC(C)(C)C # 2,2-二甲基丙烷,中间的碳上连两个甲基
C(=O)O # 甲酸,碳双键氧,再单键连羟基
环用数字标记,相同数字表示成环:
C1CCCCC1 # 环己烷
c1ccccc1 # 苯环(小写 c 表示芳香碳)
C1=CC=CC=C1 # 环己二烯(大写 C 表示脂肪族碳)
我曾经对接过一个分子数据库,里面大量分子用了多个环标签,比如 C1CC2CCCC1C2。
当时脚本解析环标签的逻辑写错了,导致双环分子全部无法正确还原。排查了一整天才发现是环编号解析的 bug,教训很深。
简单分子示例
来看几个例子,感受一下 SMILES 的简洁:
| 分子名称 | 分子结构 | SMILES |
|---|---|---|
| 乙醇 | CH₃CH₂OH | CCO |
| 乙酸 | CH₃COOH | CC(=O)O |
| 苯 | C₆H₆ | c1ccccc1 |
| 甲苯 | C₆H₅CH₃ | Cc1ccccc1 |
| 咖啡因 | C₈H₁₀N₄O₂ | Cn1c(=O)c2c(ncn2C)n(C)c1=O |
乙醇写成 CCO,看着是不是就像在描述“碳-碳-氧”的链条?
乙酸用了括号表示羧基分支。咖啡因那串看起来复杂,但你把它拆开看,就是几个环和甲基的组合。
生成过程中有个细节:写 SMILES 时顺序并不唯一。
比如乙醇你也可以写成 OCC,从氧开始写,语义上也是对的。
但你想想看,如果我们用 OCC 和 CCO 表示同一个分子,做字符串匹配时就对不上。
所以就有了 Canonical SMILES —— 规定一种唯一的写法,方便比较。
避坑指南:我在项目中遇到过一个问题:直接用 SMILES 字符串做分子去重。
同一个分子,不同库提供者给的 SMILES 可能不一样。
比如乙醇可能是 CCO,也可能是 OCC。
解决办法:始终用 RDKit 生成 Canonical SMILES 再比较。
从化学结构到 SMILES 的生成流程
下图是我自己画的一个流程,帮你理解从分子结构到 SMILES 的转换过程:
这个流程你看着也许觉得复杂,
但实际写代码时,RDKit 一个函数就能搞定:
from rdkit import Chem
# 从 SMILES 创建分子对象
mol = Chem.MolFromSmiles("CCO")
# 得到 Canonical SMILES
canonical = Chem.MolToSmiles(mol)
print(canonical) # 输出:CCO
两行代码就完成了从字符串到分子对象、再回到字符串的全过程。
但背后原理你如果不懂,项目里遇到奇怪的 bug 就不好排查了。
比如芳香环识别错误、同位素符号被忽略、手性中心丢光 —— 这些都是我在实际项目中踩过的坑。
注意:SMILES 不记录氢原子个数。氢原子由化合价自动推断。
如果你需要显式处理氢原子,必须使用方括号标记,比如 [H]C([H])([H])[H](甲烷)。
但默认情况下,你写 C 就代表甲烷,系统自动补足 4 个氢。
好了,这一章就到这。
SMILES 看起来简单,但它是一切分子表示学习的起点。
后面你要处理的图神经网络、分子指纹、反应预测,全都需要先理解这串字符串到底在说什么。