rdkit.Chem.MolStandardize 是分子标准化的标准工具。它的各个组件行为明确,但默认设置不一定符合你的需求——理解每个组件在做什么,才能判断该开哪些、该怎么调。
组件清单
from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize
| 组件 | 行为 | 建模时是否需要 |
|---|---|---|
Cleanup |
价键规范化、金属断键、官能团规范写法 | 必须 |
Normalize |
按规则转换特定官能团写法 | 包含在 Cleanup 中 |
MetalDisconnector |
断开共价金属键 | 处理金属配合物时 |
LargestFragmentChooser |
保留最大片段(去盐) | 通常需要 |
FragmentRemover |
按盐列表删除已知盐 | 需精确控制时 |
Uncharger |
中和可中和电荷 | 通常需要 |
Reionizer |
按酸性强度重新分配质子 | 处理多电离位点时 |
TautomerEnumerator |
枚举或规范化互变异构体 | 去重与 2D 建模时 |
逐组件演示
# 1) Cleanup:规范化写法
m = Chem.MolFromSmiles("C[N+](C)(C)CC(=O)[O-]")
print(Chem.MolToSmiles(rdMolStandardize.Cleanup(m)))
# 2) 去盐:LargestFragmentChooser vs FragmentRemover
lfc = rdMolStandardize.LargestFragmentChooser()
m = Chem.MolFromSmiles("CCN.Cl.Cl")
print(Chem.MolToSmiles(lfc.choose(m))) # CCN
# 注意:LargestFragmentChooser 对共价二聚体等会误删!
m = Chem.MolFromSmiles("c1ccccc1.c1ccccc1") # 两个相同片段
print(Chem.MolToSmiles(lfc.choose(m))) # 只留一个
# 3) Uncharger:中和
unc = rdMolStandardize.Uncharger()
m = Chem.MolFromSmiles("CC(=O)[O-]")
print(Chem.MolToSmiles(unc.uncharge(m))) # CC(=O)O
# 但季铵盐等永久电荷不会被中和(正确行为)
m = Chem.MolFromSmiles("C[N+](C)(C)C")
print(Chem.MolToSmiles(unc.uncharge(m))) # 保持带电
# 4) 互变异构体规范化
te = rdMolStandardize.TautomerEnumerator()
for s in ["CC(=O)CC(=O)C", "CC(O)=CC(=O)C"]: # 酮式与烯醇式
print(s, "→", Chem.MolToSmiles(te.Canonicalize(Chem.MolFromSmiles(s))))
# 两者输出相同 —— 这正是去重需要的
需要自定义的场景
# 场景一:调整互变异构枚举的上限(大分子可能超时)
params = rdMolStandardize.CleanupParameters()
params.maxTautomers = 1000 # 默认可能过大
params.maxTransforms = 1000
te = rdMolStandardize.TautomerEnumerator(params)
# 场景二:保留特定的盐/共晶组分
# LargestFragmentChooser 会无差别删掉小片段。
# 若某些共晶组分有意义,应改用 FragmentRemover 配自定义盐表,
# 或先判断片段数与组成再决定是否处理。
def safe_desalt(mol, min_heavy_atoms=4):
frags = Chem.GetMolFrags(mol, asMols=True, sanitizeFrags=True)
if len(frags) == 1:
return mol
# 只保留重原子数达标的片段中最大的那个
valid = [f for f in frags if f.GetNumHeavyAtoms() >= min_heavy_atoms]
if not valid:
return None
if len(valid) > 1:
# 有多个「大」片段 —— 可能是共价二聚体或真实共晶,需人工确认
pass
return max(valid, key=lambda f: f.GetNumHeavyAtoms())
版本一致性:容易被忽视的坑
不同 RDKit 版本的标准化行为有过变化(互变异构规则、Uncharger 的处理逻辑等都调整过)。后果是:
- 半年前清洗的数据集,用新版本重跑得到不同的标准化 SMILES;
- 历史模型的特征与新数据的特征对不上;
- 去重结果不一致,可能引入泄漏。
# 应对:在数据集中记录处理时的版本
import rdkit
import json, hashlib
meta = {
"rdkit_version": rdkit.__version__,
"pipeline": ["Cleanup", "LargestFragmentChooser",
"Uncharger", "TautomerCanonicalize"],
"n_input": n_in, "n_output": n_out,
}
json.dump(meta, open("dataset_meta.json", "w"), indent=2)
# 并在环境中锁定版本
# requirements.txt: rdkit==2024.03.5
标准化后的验证
# 抽样人工检查 —— 这一步不能省
import random
sample = random.sample(list(zip(orig_smiles, clean_smiles)), 20)
for o, c in sample:
if o != c:
print(f"{o}\n → {c}\n")
# 重点看:有没有把重要结构改掉?立体化学是否保持?
# 自动检查:分子式是否只在预期的方式上变化
from rdkit.Chem.rdMolDescriptors import CalcMolFormula
# 去盐会改变分子式(正常),但重原子骨架不应变化
常见坑与提示
LargestFragmentChooser会无差别删小片段,共价二聚体和有意义的共晶组分要特别处理;- 互变异构规范化保证一致性而非生理正确性;
- 锁定并记录 RDKit 版本,跨版本标准化结果可能不同;
- 标准化后务必抽样人工检查,自动流程不能盲信。
延伸资源
- 上一步:327《清洗 SMILES》;下一步:329《去盐、去重复、去异常结构》;
- 工具详解:171《RDKit》、218《Datamol》;标识符:033《InChI 与 InChIKey》。