分子相似性度量:Tanimoto系数,Dice系数,余弦相似度

讲完了SMILES的语法规范、标准化校验和哈希编码,下一个核心问题来了——怎么量化两条SMILES的相似程度?我刚开始做分子库聚类的项目时,脑子里全是“结构像不像”这种模糊感觉,可计算机不认识感觉,它只认识数字。

这一章我们就来拆解三个最常用的分子相似性指标:Tanimoto系数、Dice系数和余弦相似度。说白了,它们就是把你手里的分子指纹(比如MACCS、ECFP4)当成两个向量或集合,然后算一个距离或重合度。

Tanimoto系数——分子相似度的“黄金标准”

Tanimoto系数,也叫Jaccard相似系数,起源于生态学。后来被化学信息学“拿来主义”,结果成了目前最主流的分子指纹相似度算法。我个人习惯在大多数场景下优先用Tanimoto,因为它在0到1之间分布均匀,阈值好定。

定义很简单:两个集合A和B,Tanimoto系数 = |A∩B| / |A∪B|。

放到二进制指纹里,就是:

  • A与B共同为1的比特数(称为 a)
  • A中为1、B中为0的比特数(b)
  • B中为1、A中为0的比特数(c)

公式:Tc = a / (a + b + c)

我踩过的一个坑:早期我直接拿RDKit的DataStructs.TanimotoSimilarity()出来就用,但没注意指纹的稀疏程度。如果一个分子指纹只有2个1,另一个有200个1,Tanimoto就会偏低。这种情况我会改看Dice或带权重的余弦。

示例代码:

from rdkit import Chem
from rdkit.Chem import MACCSkeys
from rdkit.DataStructs import TanimotoSimilarity

smiles1 = "CC(=O)Oc1ccccc1C(=O)O"
smiles2 = "CC(=O)Oc1ccccc1"

mol1 = Chem.MolFromSmiles(smiles1)
mol2 = Chem.MolFromSmiles(smiles2)

fp1 = MACCSkeys.GenMACCSKeys(mol1)
fp2 = MACCSkeys.GenMACCSKeys(mol2)

tc = TanimotoSimilarity(fp1, fp2)
print(f"Tanimoto系数: {tc:.4f}")

Dice系数——给共同比特更高权重

Dice系数长得和Tanimoto很像,但分子多乘了一个2:Dice = 2a / (2a + b + c)。为什么这么改?你想想看:当两个指纹的共同比特数a很小,但各自独立比特b/c也都很小时,Tanimoto会把它们压得很低,而Dice因为分子翻倍,结果会更高一些。

我在做活性相似性搜索时发现,Dice对“局部高相似”的命中更敏感。比如两个分子都有同一个药效团但却长在不同的骨架上,这时Dice会识别出它们的共同模式,而Tanimoto可能因为骨架差异给出平庸的分数。

不过注意:Dice系数的值域也是[0,1],但它的数学分布偏向高值区域。嗯,这意味着你用0.7做阈值时,Dice过滤出来的分子集会比Tanimoto更膨胀一些。

余弦相似度——向量空间的视角

如果指纹不是二进制的,而是计数指纹(比如ECFP的计数版本),或者用PCA降维后的连续向量,Tanimoto和Dice就有点水土不服了。这时我带出余弦相似度:cosθ = (A·B) / (||A||·||B||)。

余弦相似度其实衡量的是两个向量的方向一致性,而不是模长。这在化学信息学里对应什么场景?我记得有一次做基于分子形状描述符的检索,分子大小差异很大,但我们只关心形状相似,用余弦就比用Tanimoto合理得多——它忽略了总量,只看分布模式。

小心零向量:如果一个分子指纹全是0(比如空分子或解析失败),余弦相似度直接除零报错。我建议在调用前先检查向量非零模长。

示例:

import numpy as np

def cosine_similarity(vec1, vec2):
    dot = np.dot(vec1, vec2)
    norm1 = np.linalg.norm(vec1)
    norm2 = np.linalg.norm(vec2)
    if norm1 == 0 or norm2 == 0:
        return 0.0
    return dot / (norm1 * norm2)

# 假设我们已经将指纹转为numpy数组
fp1_arr = np.array([1,0,1,1,0,1,0,0])
fp2_arr = np.array([1,0,0,1,0,0,0,0])
print(cosine_similarity(fp1_arr, fp2_arr))

三个指标怎么选?一张表搞定

指标 输入类型 数学含义 适用场景 我看过的主要缺点
Tanimoto 二进制指纹(推荐) 交并比 通用分子相似性搜索,活性虚拟筛选 稀疏指纹易偏低;对指纹长度敏感
Dice 二进制指纹(尤其稀疏场景) 加权交并比 药效团匹配、局部高相似搜索 值域偏高,阈值需重新标定;对噪声略敏感
余弦相似度 连续向量 / 计数指纹 向量夹角余弦 分子形状描述符、PCA降维后比较、词袋模型指纹 不反映模长差异;零向量异常

实际项目中我通常会同时跑三个指标,然后做交叉验证。比如我的一次经验:用Tanimoto跑出Top 100后,再按Dice重新排序,找出那些Tanimoto中等但Dice很高的分子——候选库里经常能捞到骨架不同但活性相似的“飞跃”结构。

分子相似性度量知识结构图 Tanimoto系数 T_c = a/(a+b+c) Dice系数 D_c = 2a/(2a+b+c) 余弦相似度 cosθ=A·B/(||A||·||B||) 分子指纹(二进制/计数) 选择建议 • 二进制指纹通用场景 → Tanimoto • 稀疏指纹/药效团匹配 → Dice • 连续向量/计数指纹 → 余弦相似度 • 交叉验证提升召回率:同时计算三个指标再排序
实战小贴士:在跨批次SMILES一致性分析中,我们经常用Tanimoto搭配ECFP4作为基线指标,但别忘了检查指纹的长度一致性。不同批次的分子库可能生成不同长度的MACCS或ECFP——我踩过这个坑,明明差异很大Tanimoto却很高,后来发现两个指纹的长度都不一样,RDKit内部做了自动padding。

关于阈值怎么设,说实话没有普适答案。我个人习惯先做一批已知相似/不相似的人工标注,然后计算三个指标的ROC曲线,选使你业务指标最优的那个cutoff。比如在虚拟筛选场景,我常用Tanimoto ≥ 0.6配合Dice ≥ 0.7做双层过滤。

好了,这一章的概念到这儿。记住:相似性度量不是你唯一的武器,但它是你分析跨批次SMILES一致性时最基础也最好用的工具。怎么在不同场景下组合使用,就是接下来几讲的实践内容了。