分子指纹概述:从比特串里看分子长什么样

我个人习惯把分子指纹理解成「分子的身份证号」——只不过这张身份证不是一串数字,而是一组固定长度的比特位。每个位代表某个结构特征是否存在。你想想看,做跨批次SMILES一致性分析,本质就是比对两批分子到底是不是同一个东西。光靠字符串硬比,遇到标准化差异就歇菜。这个时候,分子指纹就是最好的中介。

分子指纹的类型很多,但常用的其实就是那么几种。我分门别类给你捋一遍。

结构指纹:最直白的暴力编码

结构指纹的做法非常直接——枚举分子中的每一个子结构片段,然后把它映射到一个大位图上。RDKit里最基础的结构指纹实现就是RDKit Fingerprint,也叫Daylight-like指纹。

它的核心逻辑是:

  1. 遍历分子的每一对、每三个、每四个……相连的原子
  2. 对每个片段做哈希
  3. 用余数确定该片段「落」在哪个位上
  4. 有重叠怎么办?不管,直接按位或

所以结构指纹的位不是唯一的。同一个位可能对应多个不同的子结构。这本身不是问题,反倒让编码有了一定的鲁棒性。

我初学那会儿踩过一个坑:默认参数下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只关心「有没有苯环」和「有没有羰基」,不关心这两个片段的相对位置。

避坑指南:MACCS只适合做粗过滤,不适合做精细一致性判定。跨批次SMILES分析中,如果你只靠MACCS判断两批分子是否一致,很可能漏掉结构差异但指纹相似的负样本。

ECFP:环形扩散的局部感知器

ECFP(Extended-Connectivity Fingerprint)是目前用得最广的分子指纹之一,尤其在药物化学和跨批次分析里。它的核心思路是「环形扩散」——从每个原子出发,不断向邻居扩展,生成一系列圆形子结构。

ECFP的版本号(比如ECFP4、ECFP6)代表半径。ECFP4的直径是4根键,也就是说每个子结构最多覆盖半径2根键的范围。ECFP6的直径是6根键,覆盖范围更大。

生成过程的核心步骤:

  1. 给每个原子分配一个初始标识(原子序数、度数、电荷等)
  2. 迭代扩展:把当前原子的标识和它邻居的标识拼起来,然后哈希
  3. 生成的每个唯一标识对应一个特征
  4. 把这些特征折叠到固定长度的位图里

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更适合做跨批次一致性判断——它对拓扑细节更敏感,又对标准化差异不敏感。

关键认知:ECFP是目前跨批次SMILES一致性分析中「准确率与计算成本」平衡最好的指纹。我建议把ECFP4作为默认选择。ECFP6信息更丰富但位图碰撞更高,需要更长的位长来补偿。

四种指纹的对比

指纹类型 编码方式 典型位长 信息粒度 适用场景
结构指纹 枚举子结构片段,哈希到位图 1024–4096 中等 通用相似性搜索
路径指纹 枚举原子间拓扑路径 1024–4096 高 构象无关的精细比对
MACCS 166个预定义结构模式的问答 166 粗 粗过滤 / 快速分类
ECFP 环形扩散 + 哈希折叠 1024–4096 高 跨批次一致性 / 活性预测

分子指纹分类关系图

下面这张图把四种指纹的关系和选择路径整理清楚了:

分子指纹分类与选择路径 分子指纹 结构指纹 枚举子结构片段 1024–4096位 通用相似性搜索 路径指纹 枚举拓扑路径 1024–4096位 精细拓扑比对 MACCS 166个预定义模式 166位固定 粗过滤 / 快速分类 ECFP 环形扩散 + 哈希 1024–4096位 跨批次一致性 推荐选择路径 需要精细拓扑比对 → 结构指纹 / 路径指纹 | 跨批次一致性 → ECFP4(默认)

怎么选?我个人的决策习惯

做跨批次SMILES一致性分析,你面对的核心挑战是:两个结构完全相同的分子,SMILES字符串可能写得不一样。这时候指纹的作用就是「结构正则化」——把不同写法映射到同一位图空间。

  • 如果你要快速判断两批分子的主体结构是否一致:ECFP4 + 2048位,Tanimoto ≥ 0.85 可以作为阈值。这是我跑过多个项目验证的。
  • 如果你要对不一致的点做细粒度排查:结构指纹或路径指纹更合适,因为它们保留了更丰富的拓扑信息。
  • MACCS只做辅助验证:我习惯在ECFP判定一致后,用MACCS做二次确认。如果MACCS也一致,那基本板上钉钉。
一个实际经验:有一次我接了一批旧数据,两个批次里都标着"阿司匹林",ECFP4算出来Tanimoto=0.97。但我手动看才发现,一批是游离酸,另一批是钠盐。ECFP对盐的形式不敏感,因为盐在溶剂里解离了。所以光靠指纹还不够,需要结合原子组合分析和SMILES层级匹配。这个话题我们在后面章节详细展开。