分子指纹概述:从比特串里看分子长什么样
我个人习惯把分子指纹理解成「分子的身份证号」——只不过这张身份证不是一串数字,而是一组固定长度的比特位。每个位代表某个结构特征是否存在。你想想看,做跨批次SMILES一致性分析,本质就是比对两批分子到底是不是同一个东西。光靠字符串硬比,遇到标准化差异就歇菜。这个时候,分子指纹就是最好的中介。
分子指纹的类型很多,但常用的其实就是那么几种。我分门别类给你捋一遍。
结构指纹:最直白的暴力编码
结构指纹的做法非常直接——枚举分子中的每一个子结构片段,然后把它映射到一个大位图上。RDKit里最基础的结构指纹实现就是RDKit Fingerprint,也叫Daylight-like指纹。
它的核心逻辑是:
- 遍历分子的每一对、每三个、每四个……相连的原子
- 对每个片段做哈希
- 用余数确定该片段「落」在哪个位上
- 有重叠怎么办?不管,直接按位或
所以结构指纹的位不是唯一的。同一个位可能对应多个不同的子结构。这本身不是问题,反倒让编码有了一定的鲁棒性。
我初学那会儿踩过一个坑:默认参数下RDKit结构指纹只有2048位,但分子里的片段数可能几千个,碰撞率很高。说白了就是不同分子也可能撞出相似的指纹。
nBitsPerHash=2 降低碰撞,但位图变大很多。我是实际跑过几十万分子才发现,nBits=2048 对大部分日常任务足够了,除非你做的是超细粒度的相似性搜索。
生成一段结构指纹也很简单:
from rdkit import Chem
from rdkit.Chem import rdMolDescriptors
mol = Chem.MolFromSmiles("CCO")
fp = rdMolDescriptors.RDKFingerprint(mol, nBitsPerHash=2, minPath=1, maxPath=7)
print(len(fp))
# 输出:2048
路径指纹:带着拓扑走一遍
路径指纹和结构指纹的区别在哪?结构指纹枚举的是子结构本身,路径指纹枚举的是原子之间的拓扑路径。说白了,每一段路径就是一条「从原子A到原子B,经过几根键」的走法记录。
RDKit里的路径指纹就是 RDKFingerprint 自己,只不过参数 minPath 和 maxPath 控制的是路径长度。比如 minPath=1, maxPath=5,那就枚举所有长度 1 到 5 的路径。
路径越长,信息越多,但计算量也指数级上升。我做过一次实验:把 maxPath 从7改成10,指纹生成时间翻了8倍,搜索质量并没有明显提升。所以我的建议是,一般任务 maxPath=7 就够了。
为什么是7?因为大多数药物分子的有效药效团直径不超过7根键。超过这个范围,路径里掺入的无关结构信息就太多了。
fp_path = rdMolDescriptors.RDKFingerprint(
mol, minPath=1, maxPath=7, nBitsPerHash=2
)
print(len(fp_path))
# 也返回2048位,但编码的内容不一样
MACCS指纹:166个预定义问题的Yes/No答卷
MACCS(Molecular ACCess System)指纹本质上是一张固定问卷,一共166个问题。每个问题对应一个特定的结构模式,比如「分子中有没有苯环?」、「有没有羰基?」、「有没有至少3个卤素?」。回答是Yes,对应位就置1。
RDKit里用起来不废话:
from rdkit.Chem import MACCSkeys
maccs = MACCSkeys.GenMACCSKeys(mol)
print(len(maccs))
# 输出:166
优点很明显:位含义直接对应化学问题,可解释性强。缺点也很明显——分辨率太低。166个特征对于现代虚拟筛选来说太粗糙了。我当年做第一版配体相似性过滤时,直接用MACCS去筛,结果把两个活性完全不同的分子判成了相似。为什么?MACCS只关心「有没有苯环」和「有没有羰基」,不关心这两个片段的相对位置。
ECFP:环形扩散的局部感知器
ECFP(Extended-Connectivity Fingerprint)是目前用得最广的分子指纹之一,尤其在药物化学和跨批次分析里。它的核心思路是「环形扩散」——从每个原子出发,不断向邻居扩展,生成一系列圆形子结构。
ECFP的版本号(比如ECFP4、ECFP6)代表半径。ECFP4的直径是4根键,也就是说每个子结构最多覆盖半径2根键的范围。ECFP6的直径是6根键,覆盖范围更大。
生成过程的核心步骤:
- 给每个原子分配一个初始标识(原子序数、度数、电荷等)
- 迭代扩展:把当前原子的标识和它邻居的标识拼起来,然后哈希
- 生成的每个唯一标识对应一个特征
- 把这些特征折叠到固定长度的位图里
RDKit实现非常成熟:
from rdkit.Chem import AllChem
ecfp4 = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
ecfp6 = AllChem.GetMorganFingerprintAsBitVect(mol, radius=3, nBits=2048)
这里 radius=2 对应ECFP4。我之前遇到过一个问题:两个SMILES写法不一样但实际结构相同的分子,MACCS指纹差异很大,ECFP4指纹的Tanimoto系数却高达0.98。这就解释了为什么ECFP更适合做跨批次一致性判断——它对拓扑细节更敏感,又对标准化差异不敏感。
四种指纹的对比
| 指纹类型 | 编码方式 | 典型位长 | 信息粒度 | 适用场景 |
|---|---|---|---|---|
| 结构指纹 | 枚举子结构片段,哈希到位图 | 1024–4096 | 中等 | 通用相似性搜索 |
| 路径指纹 | 枚举原子间拓扑路径 | 1024–4096 | 高 | 构象无关的精细比对 |
| MACCS | 166个预定义结构模式的问答 | 166 | 粗 | 粗过滤 / 快速分类 |
| ECFP | 环形扩散 + 哈希折叠 | 1024–4096 | 高 | 跨批次一致性 / 活性预测 |
分子指纹分类关系图
下面这张图把四种指纹的关系和选择路径整理清楚了:
怎么选?我个人的决策习惯
做跨批次SMILES一致性分析,你面对的核心挑战是:两个结构完全相同的分子,SMILES字符串可能写得不一样。这时候指纹的作用就是「结构正则化」——把不同写法映射到同一位图空间。
- 如果你要快速判断两批分子的主体结构是否一致:ECFP4 + 2048位,Tanimoto ≥ 0.85 可以作为阈值。这是我跑过多个项目验证的。
- 如果你要对不一致的点做细粒度排查:结构指纹或路径指纹更合适,因为它们保留了更丰富的拓扑信息。
- MACCS只做辅助验证:我习惯在ECFP判定一致后,用MACCS做二次确认。如果MACCS也一致,那基本板上钉钉。