035

分子指纹是什么:从结构编码到相似性搜索

分子指纹把结构编码成向量,是相似性搜索与建模的基础。这篇讲清各类指纹的原理与选择依据。

分子指纹把分子结构编码成固定长度的向量,让「比较分子」变成「比较向量」。它是化学信息学中最实用的工具之一——而且在很多任务上,认真调优的指纹基线至今仍难以被深度学习超越。

三大类指纹

类型 原理 代表 特点
子结构键(keys) 预定义的子结构列表,有则置 1 MACCS(见 038《MACCS Keys》 可解释;覆盖有限
拓扑/路径 枚举分子中的路径 RDKit FP、Daylight 通用
环境/圆形 枚举每个原子的邻域环境 ECFP/Morgan(见 036《Morgan Fingerprint 入门》037《ECFP 指纹详解》 最常用
药效团指纹 特征对的距离 Pharm2D、ErG 更抽象,利于骨架跃迁
三维指纹 基于构象 USR、E3FP 依赖构象质量
物化描述符 计算的分子性质 RDKit 描述符 全局性质;与指纹互补

各类指纹的生成

from rdkit import Chem
from rdkit.Chem import rdFingerprintGenerator, MACCSkeys, Descriptors
import numpy as np

mol = Chem.MolFromSmiles("CC(=O)Nc1ccc(O)cc1")

# ---- Morgan / ECFP(最常用)----
morgan_gen = rdFingerprintGenerator.GetMorganGenerator(
    radius=2, fpSize=2048)
fp_morgan = morgan_gen.GetFingerprint(mol)          # 位向量
fp_count = morgan_gen.GetCountFingerprint(mol)      # 【计数版本】

# ---- FCFP(用药效团特征而非原子类型)----
inv_gen = rdFingerprintGenerator.GetMorganFeatureAtomInvGen()
fcfp_gen = rdFingerprintGenerator.GetMorganGenerator(
    radius=2, fpSize=2048, atomInvariantsGenerator=inv_gen)
fp_fcfp = fcfp_gen.GetFingerprint(mol)
# 【FCFP 更抽象】:把「氢键供体」这类特征而非具体元素编码
# → 更容易发现骨架不同但功能相似的分子

# ---- RDKit 拓扑指纹 ----
rdkit_gen = rdFingerprintGenerator.GetRDKitFPGenerator(
    minPath=1, maxPath=7, fpSize=2048)
fp_rdkit = rdkit_gen.GetFingerprint(mol)

# ---- 原子对指纹 ----
ap_gen = rdFingerprintGenerator.GetAtomPairGenerator(fpSize=2048)
fp_ap = ap_gen.GetFingerprint(mol)
# 编码「原子对 + 拓扑距离」→ 【对分子形状敏感】

# ---- 拓扑扭转指纹 ----
tt_gen = rdFingerprintGenerator.GetTopologicalTorsionGenerator(fpSize=2048)
fp_tt = tt_gen.GetFingerprint(mol)

# ---- MACCS(167 位,可解释)----
fp_maccs = MACCSkeys.GenMACCSKeys(mol)

# ---- 物化描述符 ----
desc = Descriptors.CalcMolDescriptors(mol)          # 返回 dict

# ---- 转成 numpy 数组 ----
def fp_to_array(fp):
    arr = np.zeros((fp.GetNumBits(),), dtype=np.int8)
    from rdkit import DataStructs
    DataStructs.ConvertToNumpyArray(fp, arr)
    return arr

位向量 vs 计数指纹

# 【位向量】(binary):
#   某个子结构【出现过】就置 1
#   丢失了「出现几次」的信息
#
# 【计数指纹】(count):
#   记录每个子结构出现的次数
#
# 【什么时候用计数】:
#   - 子结构的数量与性质相关时
#     如:分子中有几个羟基,影响溶解度
#   - 【用树模型时通常计数更好】
#
# 【什么时候用位向量】:
#   - 相似性比较(Tanimoto 有标准定义)
#   - 需要节省内存
#   - 【大规模筛选时位运算更快】
#
# 【实践建议】:
#   相似性搜索 → 位向量
#   机器学习 → 【两种都试】,常常计数略好

关键参数的影响

参数 影响 建议
半径 / 路径长度 编码的邻域大小 ECFP4(radius=2)是标准
位数(fpSize) 哈希碰撞的概率 1024~4096;越大碰撞越少
是否包含手性 能否区分对映体 手性相关任务要开
是否用特征而非原子类型 抽象程度 FCFP 利于骨架跃迁
计数 vs 二值 是否保留数量信息 见上
# 【半径的影响】:
#   radius=1(ECFP2):只看直接邻居 → 太局部
#   radius=2(ECFP4):【标准选择】
#   radius=3(ECFP6):更大的邻域 → 更特异但更稀疏
#
#   【经验】:
#     相似性搜索:radius=2
#     机器学习:radius=2 或 3,可以试
#     【半径越大,位向量越稀疏,小数据上越容易过拟合】

# 【位数的影响】:
#   位数太小 → 哈希碰撞多 → 不同的子结构映射到同一位
#   → 信息损失

# 检查碰撞率
from rdkit.Chem import rdFingerprintGenerator

def collision_rate(mols, radius=2, fpSize=2048):
    gen = rdFingerprintGenerator.GetMorganGenerator(
        radius=radius, fpSize=fpSize)
    ao = rdFingerprintGenerator.AdditionalOutput()
    ao.AllocateBitInfoMap()
    unique_envs, used_bits = set(), set()
    for m in mols:
        gen.GetFingerprint(m, additionalOutput=ao)
        info = ao.GetBitInfoMap()
        for bit, envs in info.items():
            used_bits.add(bit)
            for env in envs:
                unique_envs.add((m.GetAtomWithIdx(env[0]).GetSymbol(), env))
    return len(used_bits), fpSize

# 【经验】:
#   2048 位对多数场景够用
#   化学空间很大时用 4096 或更多

指纹的选择依据

# 【按任务选择】
#
# 相似性搜索(找相似的分子):
#   → 【ECFP4 位向量 + Tanimoto】(见 039)
#     这是事实标准
#
# 骨架跃迁(找结构不同但功能相似的):
#   → 【FCFP】或【药效团指纹】
#     更抽象,不拘泥于具体元素
#
# 机器学习(性质预测):
#   → 【ECFP 计数版 + RDKit 描述符】
#     指纹提供局部结构,描述符提供全局性质
#     → 【这个组合是最实用的基线】(见 131)
#
# 需要可解释:
#   → MACCS(每一位有明确含义,见 038)
#   → 或 ECFP + 位到子结构的映射
#
# 关注分子形状:
#   → 原子对指纹、拓扑扭转指纹
#
# 三维相关任务:
#   → 需要构象的三维指纹(见 049)
#
# 【一个实用的组合特征】:
def combined_features(smiles_list):
    import numpy as np
    from rdkit import Chem
    from rdkit.Chem import rdFingerprintGenerator, Descriptors

    morgan = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
    DESC = ["MolWt", "MolLogP", "TPSA", "NumHDonors", "NumHAcceptors",
            "NumRotatableBonds", "RingCount", "FractionCSP3",
            "NumAromaticRings", "HeavyAtomCount", "NHOHCount", "NOCount"]
    X = []
    for s in smiles_list:
        m = Chem.MolFromSmiles(s)
        if m is None:
            X.append(np.zeros(2048 + len(DESC)))
            continue
        fp = np.array(morgan.GetCountFingerprint(m).ToList())
        d = np.array([getattr(Descriptors, n)(m) for n in DESC])
        X.append(np.concatenate([fp, np.nan_to_num(d)]))
    return np.array(X)

关键要点

  • ECFP4(radius=2)+ 2048 位是相似性搜索的事实标准;
  • 机器学习用「ECFP 计数版 + RDKit 描述符」——局部结构与全局性质互补;
  • FCFP 用药效团特征替代具体元素,更利于发现骨架不同但功能相似的分子;
  • 位数太小会有哈希碰撞导致信息损失;半径越大越稀疏,小数据上越易过拟合。

延伸资源