分子指纹把分子结构编码成固定长度的向量,让「比较分子」变成「比较向量」。它是化学信息学中最实用的工具之一——而且在很多任务上,认真调优的指纹基线至今仍难以被深度学习超越。
三大类指纹
| 类型 | 原理 | 代表 | 特点 |
|---|---|---|---|
| 子结构键(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 用药效团特征替代具体元素,更利于发现骨架不同但功能相似的分子;
- 位数太小会有哈希碰撞导致信息损失;半径越大越稀疏,小数据上越易过拟合。
延伸资源
- Morgan 指纹:036《Morgan Fingerprint 入门》;ECFP 详解:037《ECFP 指纹详解》;MACCS:038《MACCS Keys》;
- Tanimoto:039《Tanimoto 相似性》;分子图:034《分子图表示》;Chemprop:131《Chemprop / D-MPNN 论文精读》。