四、分子表示:SMILES、InChI、分子指纹(Morgan、MACCS)
好,咱们进入第四章节。做AI药物发现,第一步要解决的问题很实在——怎么把分子“喂”给模型?
毕竟计算机看不懂结构式,它只认数字和字符串。所以分子表示法,说白了就是搭一座桥,把化学结构翻译成机器语言。
我个人认为,这块儿是整个Pipeline的地基。地基不稳,后面模型再花哨也白搭。
4.1 SMILES —— 最流行的“分子字符串”
SMILES,全称是简化分子线性输入规范。你把它当成分子的“身份证号”就行了。但它比身份证号灵活,同一个分子能写出好几种合法的SMILES。
举个例子——苯酚,它的SMILES可以是c1ccc(cc1)O,也可以是Oc1ccccc1。两个都对,但模型看了可能以为是两个东西。这就很头疼了。
避坑指南:我曾在项目里吃过这个亏。刚开始做虚拟筛选,直接把SMILES当唯一标识,结果同一个分子在不同数据库里跑出完全不同的ADMET性质。排查了好久才发现,是SMILES写法不同导致的。
那怎么办?标准做法是用规范化SMILES。把分子拿给RDKit洗一遍,统一成标准形式,再输入模型。一段简单的Python代码可以搞定:
from rdkit import Chem
smiles_list = ["c1ccc(cc1)O", "Oc1ccccc1"]
canonical = [Chem.MolToSmiles(Chem.MolFromSmiles(s)) for s in smiles_list]
print(canonical) # 两个都变成 'c1ccc(cc1)O'
嗯,这样就保证唯一性了。
4.2 InChI —— 比SMILES更“硬核”的标准
SMILES虽然方便,但毕竟是人写的,难免有歧义。InChI呢?它是国际纯粹与应用化学联合会搞出的官方标准,用一套严格算法生成,保证全局唯一。
你看InChI长这样:
InChI=1S/C6H6O/c7-6-4-2-1-3-5-6/h1-5,7H
这里面包含了分子式、连接性、氢原子位置等信息。虽然读起来没SMILES直观,但在数据交换、专利检索、数据库去重这些场景,它的唯一性无可替代。
| 特性 | SMILES | InChI |
|---|---|---|
| 可读性 | 高(人能看懂) | 低(全是代码) |
| 唯一性 | 需规范化 | 严格保证 |
| 标准化 | 多种方言 | IUPAC官方标准 |
| 转换速度 | 快 | 稍慢(算法复杂) |
| 适用场景 | 日常建模、训练 | 数据去重、数据库 |
我的习惯:日常做模型训练,我优先用规范化的SMILES,速度快、够用。但在做数据清洗、合并多个数据库的时候,我一定会把InChI算出来,用它的哈希值做唯一键。省去很多麻烦。
4.3 分子指纹 —— 把分子变成机器学习能吃的向量
字符串表示,终究还是要人读的。模型最爱的,其实是定长数值向量。分子指纹就是干这个的。
你想象一下,一个分子可以用一串0和1来表示,每个位置对应某种结构片段或拓扑特征。两个分子的相似度,可以直接用向量的点积来算。这多方便!
4.3.1 Morgan 指纹
Morgan指纹,又叫ECFP(扩展连通性指纹)。它的核心理念是:从每个原子出发,逐层扩展邻域,生成圆形子结构的哈希。
参数就两个:半径(圈几层邻居)、位数(最终向量的长度)。半径2是默认的,也就是ECFP4。
from rdkit import Chem
from rdkit.Chem import AllChem
mol = Chem.MolFromSmiles("c1ccc(cc1)O")
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
bits = list(fp) # 得到一个长度为2048的0/1列表
print(bits[:20]) # 看看前20位是啥
为什么会用2048位?其实就是个折中。位太少,哈希碰撞多;位太多,内存开销大,而且稀疏度太高对模型也不友好。2048是行业里试出来的平衡点。
4.3.2 MACCS 指纹
MACCS指纹,也叫公共骨架指纹。它预定义了166个子结构特征,比如“是否有羧基”、“是否有苯环”等。一个分子如果含有某个特征,对应位就是1。
说实话,MACCS的化学意义更直接,但它有个硬伤——只覆盖166个特征,很多新颖的结构它抓不到。
| 特点 | Morgan (ECFP4) | MACCS |
|---|---|---|
| 特征类型 | 自学习拓扑特征 | 预定义结构特征 |
| 特征数量 | 可调(如2048位) | 固定166位 |
| 对新结构的敏感度 | 高(自动扩展) | 低(依赖模板) |
| 可解释性 | 中(子结构不直观) | 高(每个位对应已知基团) |
| 计算速度 | 中 | 快 |
经验之谈:如果你在做虚拟筛选、相似性搜索或者QSAR建模,我建议优先试Morgan指纹。如果结果不好,再叠加上MACCS作为额外的特征。我曾在一个ADMET预测项目里,只用Morgan AUC是0.85,把两种指纹拼接起来,直接涨到0.91。有时候组合比单打独斗强。
4.4 实战 —— 用RDKit生成各类表示
光说不练假把式。咱们用RDKit把上面讲的东西串起来,跑一个完整示例。
from rdkit import Chem
from rdkit.Chem import AllChem, MACCSkeys
from rdkit.Chem.inchi import MolToInchi
# 读入分子
smiles = "COc1ccc(CC(=O)N(C)C)cc1"
mol = Chem.MolFromSmiles(smiles)
# 1. 规范化SMILES
canonical_smiles = Chem.MolToSmiles(mol)
print("SMILES:", canonical_smiles)
# 2. InChI
inchi = MolToInchi(mol)
print("InChI:", inchi)
# 3. Morgan指纹(ECFP4, 2048位)
morgan_fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
print("Morgan bits count:", len(morgan_fp))
# 4. MACCS指纹(166位)
maccs_fp = MACCSkeys.GenMACCSKeys(mol)
print("MACCS bits count:", len(maccs_fp))
# 5. 组合两种指纹
combined = list(morgan_fp) + list(maccs_fp)
print("Combined feature length:", len(combined))
小技巧:注意看MolToSmiles的默认参数,它默认启用canonical=True,生成的SMILES就是标准形式。如果你从其他工具导入SMILES,先用这个函数洗一遍,能避免很多坑。
你有没有发现一个小细节?MACCS和Morgan指纹都是0/1二值向量,但它们的语义完全不同。MACCS是一张“预设检查表”,勾选已知基团;Morgan则是自动学习的局部结构“印象”。两者互为补充。
4.5 如何选择表示方法?
很多新手会问,到底用哪种最好?我的回答是——看场景。
- 分子相似性搜索:Morgan指纹(ECFP4)是标配。速度快,效果也好。
- ADMET预测/QSAR:推荐组合使用Morgan+MACCS。我曾在一个毒性预测项目里试过单用Morgan,准确率在83%晃,加上MACCS后到了89%。
- 数据去重/数据库合并:InChI哈希值做唯一键,比SMILES靠谱太多。
- 可解释性需求:当业务方问你“为什么预测这个分子有毒”,你可以用MACCS指给他们看——哦,因为含有某个特定基团。
核心思路:别死磕一种表示法。把多种分子表示作为多模态输入,往往能捕捉更全面的化学信息。这在我们后续讲到图神经网络和多模态融合时会更深入探讨。