4. 基于分子指纹的药物相似性计算实践(RDKit)

分子指纹,说白了就是把一个复杂的分子结构,压缩成一段固定长度的二进制或计数向量。听起来有点像给分子做“条形码”对吧?我刚开始接触药物研发的时候,总觉得这玩意太粗糙,丢掉了太多三维信息。后来做了一批分子筛选实验,才发现指纹这东西虽然简单,但在大规模虚拟筛选中,效率和准确性的平衡做得非常好。

RDKit 这个库,几乎成了药物化学家做相似性计算的标配工具。它内置了十几种指纹生成算法,而且计算速度特别快。我记得有一次处理一个百万级分子的库,用摩根指纹做相似性搜索,RDKit 只花了不到两分钟就完成了全部比对排序。

4.1 分子指纹是什么

先聊聊指纹的核心逻辑。一个分子被转换成指纹,本质上是将它的结构特征映射到一组位(bits)上。常见的指纹类型包括:

  • 摩根指纹(Morgan/ECFP):基于半径的子结构枚举,是目前最主流的指纹。
  • MACCS 指纹:166 个预定义的结构片段,更像一个“检查清单”。
  • 拓扑指纹/路径指纹:基于原子之间的路径,不考虑原子类型。
  • RDKit 自身指纹(FP):默认的 Daylight 式指纹,适合通用场景。

每种指纹有各自的脾气。摩根指纹(ECFP4)对局部结构很敏感,很适合做活性相似性搜索。而 MACCS 指纹呢,它覆盖的片段更宏观,我习惯用它来做粗筛或去重。

4.2 指纹之间的相似性怎么算

有了指纹向量,接下来就是“量距离”了。最常用的就是 Tanimoto 系数,也叫 Jaccard 指数。公式其实很简单:交集大小除以并集大小。

Tanimoto 系数在二值指纹上的表现特别稳定。我个人习惯用 0.6 作为阈值——超过 0.6 的两个分子,基本可以认为是同一化学空间内的“近邻”。低于 0.3 的话,基本上就是两个世界了。

除了 Tanimoto,还有 Dice、Cosine、Tversky 等系数。我过去做过一个骨架跃迁的项目,Tversky 比 Tanimoto 表现更好,因为它可以不对称地强调查询分子或目标分子的特征。所以你看,没有万能药,得看场景选工具。

常用相似性系数对比
指标 公式特点 适用场景
Tanimoto 对称,对公共位敏感 通用分子相似性
Dice 对称,放大公共位权重 指纹稀疏时更灵敏
Tversky 不对称,可调 α/β 当你想强调查询或目标特征
Cosine 倾向于长向量 计数指纹或向量空间模型

4.3 实操:用 RDKit 计算分子相似性

下面这段代码是我在项目里反复用的模板。我会从加载分子开始,一步步生成指纹,然后计算 Tanimoto 相似度。

from rdkit import Chem
from rdkit.Chem import AllChem, MACCSkeys
from rdkit.DataStructs import TanimotoSimilarity, DiceSimilarity

# 加载两个分子
mol1 = Chem.MolFromSmiles('CC1=CC=C(C=C1)NC(=O)C2=CC=CS2')  # 替尼达普类似物
mol2 = Chem.MolFromSmiles('CC1=CC=C(C=C1)NC(=O)C3=CC=CC=C3') # 对乙酰氨基酚类似物

# 生成摩根指纹(ECFP4)
fp1 = AllChem.GetMorganFingerprintAsBitVect(mol1, 2, nBits=2048)
fp2 = AllChem.GetMorganFingerprintAsBitVect(mol2, 2, nBits=2048)

# 计算 Tanimoto 相似度
tanimoto = TanimotoSimilarity(fp1, fp2)
print(f"Morgan/ECFP4 Tanimoto: {tanimoto:.4f}")

# 计算 Dice 相似度
dice = DiceSimilarity(fp1, fp2)
print(f"Morgan/ECFP4 Dice: {dice:.4f}")

# 生成 MACCS 指纹
maccs1 = MACCSkeys.GenMACCSKeys(mol1)
maccs2 = MACCSkeys.GenMACCSKeys(mol2)
tanimoto_maccs = TanimotoSimilarity(maccs1, maccs2)
print(f"MACCS Tanimoto: {tanimoto_maccs:.4f}")

你看,不到二十行代码,就完成了两种指纹的计算和比对。运行结果会告诉你,这两个分子在化学空间里到底有多“像”。

提示:生成摩根指纹时,第二个参数是半径。半径 2 对应 ECFP4,半径 3 对应 ECFP6。半径越大,感受野就越大,但噪音也可能增加。我个人经验:在活性筛选场景下,ECFP4 通常是 sweet spot。

4.4 批量计算与相似性矩阵

单个分子比不过瘾。实际项目中,我们面对的是成千上万个化合物。RDKit 提供了一个批量计算相似性矩阵的函数 DataStructs.BulkTanimotoSimilarity。

from rdkit.DataStructs import BulkTanimotoSimilarity
from tqdm import tqdm

# 假设我们有一个候选分子列表
mols = [Chem.MolFromSmiles(smi) for smi in smiles_list]
fps = [AllChem.GetMorganFingerprintAsBitVect(m, 2, nBits=2048) for m in tqdm(mols)]

# 用第一个指纹查所有
query_fp = fps[0]
scores = BulkTanimotoSimilarity(query_fp, fps)
sorted_indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)

# 打印 Top-5 相似分子
for idx in sorted_indices[:5]:
    print(f"Index: {idx}, Score: {scores[idx]:.4f}")

这段代码我在构建命中化合物去重流程时用过无数次。批量比对会将查询指纹逐个与数据库中的指纹对比,返回一个分数列表。配合 sorted 排序,Top-N 的最近邻结果就出来了。

注意:批量计算时,内存占用会随着指纹库尺寸线性增长。如果分子数超过 50 万,建议使用分块或基于树的索引(比如 Annoy 或 FAISS)来加速,而不是一次性加载全量指纹到内存。

4.5 指纹相似性的应用场景

实操了代码,我们来看看它到底用在什么地方。我经历过三个主要场景:

  • 虚拟筛选去重:从 HTS 结果中,移除与已报道活性分子高度相似的化合物,避免重复劳动。
  • 骨架分析:结合最大公共子结构(MCS),用指纹相似度做聚类,找到不同活性的化学骨架。
  • SAR 模型特征工程:把 Tanimoto 相似度矩阵作为核矩阵,输入到支持向量机或高斯过程中,构建定量构效关系模型。

这些场景下,指纹相似性计算都不是孤立的。说白了一个好的计算管线,是先把指纹生成好,再配合恰当的相似性指标和聚类算法,最后才能落地到具体的药物研发任务中。

4.6 常见避坑指南

避坑一:指纹长度与稀疏性
我曾经在项目里直接用 1024 位的摩根指纹,结果发现很多分子生成的指纹全是 0 或少数几个 1。后来发现问题在于分子结构太简单,位点不够。建议根据分子的复杂度,适当增加 nBits 到 2048 或 4096。

避坑二:只算 Tanimoto 就够了?
不一定。有些数据集里,两个分子公共特征很少但在关键药效团上高度重叠。这时候 Tanimoto 会低估相似性。我会多算一个 Dice 或 Tversky 指标,对比一下再决策。

避坑三:立体异构带来的偏差
ECFP4 默认不考虑立体化学信息。如果你比的两个分子只是立体构型不同,指纹相似性可能会高达 0.95 以上。但在药物研发里,手性中心有时候是天壤之别。这时候需要结合图匹配或三维构象比对来补足。

4.7 本章知识体系

下面这张 SVG 图总结了基于分子指纹的相似性计算核心流程——从输入到输出,再到应用。

分子指纹相似性计算工作流 分子结构 (SMILES) 指纹生成 摩根指纹 MACCS / 拓扑 相似性计算 Tanimoto / Dice 排序 / Top-N 结果 虚拟筛选 SAR / 聚类 / 去重 关键点 指纹长度、半径选择 对最终结果影响很大

从图中可以直观看到,从 SMILES 输入到最终的应用输出,核心就是指纹+相似性计算这个枢纽。无论你用哪种指纹,最终都要通过相似性系数转化成可排序的分数,然后才能用于下游任务。

4.8 小结

这一节我们走了完整的分子指纹相似性计算流程。从指纹概念、常见类型到 RDKit 的代码实现,再到批量计算和应用场景。我特意把避坑经验放在最后,希望你在实际代码里不再踩这些坑。说白了,指纹技术用了二十年,仍然是大数据驱动的药物筛选领域里最可靠的“第一印象”工具。熟用 RDKit,你的药物筛选起点会稳很多。