046

分子标准化:AI 建模前为什么必须清洗结构

分子标准化是 AI 建模前的必需步骤。这篇给出完整的标准化流程、各步骤的作用与常见的判断。

分子标准化是把不同来源、不同书写方式的分子转换成统一形式的过程。不做标准化,同一个化合物可能被当成多个不同的分子——这会污染训练集、破坏去重、让数据合并出错。

需要处理的问题

问题 例子 后果
盐与共晶 盐酸盐 vs 游离碱 被当成不同分子
电荷形式 COO⁻ vs COOH 指纹不同
互变异构体 酮式 vs 烯醇式 结构表示不同
官能团的书写 硝基的两种画法 子结构匹配失败
芳香性感知 凯库勒式 vs 芳香式 通常可自动处理
立体化学 未指定 vs 明确指定 需要决定策略
同位素标记 氘代化合物 视任务决定是否保留
金属配位 配位键的表示 处理复杂

标准的处理流程

from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize

def standardize(smiles, keep_stereo=True, neutralize=True,
                canonical_tautomer=True):
    """完整的分子标准化流程"""
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return None

    # ---- 1) 基本清理 ----
    #   官能团的规范化(如硝基的表示)
    #   去掉不必要的显式氢
    #   金属离子的处理(断开配位键)
    mol = rdMolStandardize.Cleanup(mol)

    # ---- 2) 去掉盐/溶剂,保留最大的有机片段 ----
    #   CC(=O)[O-].[Na+] → CC(=O)[O-]
    mol = rdMolStandardize.FragmentParent(mol)
    if mol is None:
        return None

    # ---- 3) 中和电荷(可选)----
    if neutralize:
        mol = rdMolStandardize.Uncharger().uncharge(mol)

    # ---- 4) 规范互变异构体(可选,较慢)----
    if canonical_tautomer:
        te = rdMolStandardize.TautomerEnumerator()
        mol = te.Canonicalize(mol)

    # ---- 5) 立体化学的处理 ----
    if not keep_stereo:
        Chem.RemoveStereochemistry(mol)

    # ---- 6) 最终的规范 SMILES ----
    return Chem.MolToSmiles(mol)

# 【或用一步到位的 API】
def standardize_simple(smiles):
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return None
    # 【父结构】:去盐 + 中和 + 去同位素
    parent = rdMolStandardize.ChargeParent(mol)
    return Chem.MolToSmiles(parent) if parent else None

关键的判断:每一步该不该做

# 【去盐:几乎总是要做】
#   例外:如果研究的性质与盐型相关
#     - 溶解度(不同盐型溶解度不同)
#     - 固态性质
#   → 【这种情况下应该保留盐型信息作为额外特征】
#
# 【中和电荷:取决于任务】
#   ✓ 做的理由:
#     统一表示,避免同一化合物的不同电荷形式被分开
#   ✗ 不做的理由:
#     - 【对接需要生理 pH 下的实际电荷】(见 106)
#     - 静电相互作用的建模需要真实电荷
#   → 【建模用中性形式,对接用生理形式】
#     两套数据分别准备
#
# 【互变异构体规范化:谨慎】
#   ✓ 做的理由:
#     酮式与烯醇式是同一化合物
#   ✗ 风险:
#     - 【RDKit 选择的「规范」形式
#       未必是实际占优的形式】
#     - 可能改变氢键供受体的角色
#     - 计算较慢
#   → 【用于去重与匹配时做;
#     用于对接与三维建模时要小心】
#
# 【立体化学:取决于数据】
#   ✓ 保留:数据中明确标注了立体化学
#   ✗ 去掉:
#     - 数据中大部分未标注(避免不一致)
#     - 任务与立体化学无关
#   → 【混合标注与未标注的数据是常见问题】
#     应该统一策略
#
# 【最重要的一条】:
#   【无论选什么策略,都要在整个项目中保持一致,
#    并明确记录】
#   → 不一致的标准化,比不标准化更糟

批量处理与质量检查

import pandas as pd
from collections import Counter

def standardize_dataset(df, smiles_col="smiles", **kwargs):
    """批量标准化并报告统计"""
    results, failures = [], []
    for i, smi in enumerate(df[smiles_col]):
        std = standardize(smi, **kwargs)
        if std is None:
            failures.append((i, smi))
        results.append(std)

    df = df.copy()
    df["std_smiles"] = results

    print(f"总数: {len(df)}")
    print(f"【标准化失败: {len(failures)}】")
    if failures:
        print("  失败示例:", failures[:3])

    valid = df.dropna(subset=["std_smiles"])
    print(f"唯一分子数: {valid['std_smiles'].nunique()}")
    dup = len(valid) - valid["std_smiles"].nunique()
    print(f"【标准化后发现 {dup} 个重复】")

    # 【检查标准化改变了什么】
    changed = valid[valid[smiles_col].apply(
        lambda s: Chem.CanonSmiles(s) if Chem.MolFromSmiles(s) else None
    ) != valid["std_smiles"]]
    print(f"标准化改变了 {len(changed)} 个分子")

    return df, failures

# 【必做的检查】:
#   1) 【标准化前后的分子式是否一致?】
#      去盐会改变分子式(这是预期的)
#      但如果非预期地改变,说明有问题
#
#   2) 【抽样人工检查】
#      随机取 20 个标准化改变了的分子,
#      画出前后对比,确认改变是合理的
#
#   3) 【记录标准化的版本与参数】
#      RDKit 版本、使用的选项
#      → 【不同版本的结果可能不同】

def verify_standardization(before, after, n_sample=20):
    from rdkit.Chem import Draw
    import random
    pairs = [(b, a) for b, a in zip(before, after)
             if a and Chem.CanonSmiles(b) != a]
    sample = random.sample(pairs, min(n_sample, len(pairs)))
    mols, legends = [], []
    for b, a in sample:
        mols += [Chem.MolFromSmiles(b), Chem.MolFromSmiles(a)]
        legends += ["原始", "标准化后"]
    return Draw.MolsToGridImage(mols, molsPerRow=4, legends=legends)

ChEMBL Structure Pipeline

# ChEMBL 团队开源了他们的标准化流程
#   → 与 ChEMBL 数据库的处理一致
#   → 【如果你的数据来自 ChEMBL,用它能保证一致性】

# pip install chembl_structure_pipeline

from chembl_structure_pipeline import standardizer, checker

def chembl_standardize(molblock):
    # 标准化
    std = standardizer.standardize_molblock(molblock)
    # 提取父结构(去盐)
    parent, exclude = standardizer.get_parent_molblock(std)
    return parent, exclude

# 【checker 模块】:检查结构问题
#   issues = checker.check_molblock(molblock)
#   返回问题列表与严重程度
#   → 【可以用来自动标记可疑结构】(见 048)

# 【选择建议】:
#   数据来自 ChEMBL → 用 ChEMBL Structure Pipeline
#   其它来源 → RDKit MolStandardize 通常够用
#   【关键是一致性,而非用哪个工具】

关键要点

  • 建模用中性形式,对接用生理 pH 下的实际电荷——两套数据分别准备;
  • 互变异构体规范化要谨慎——RDKit 选的「规范」形式未必是实际占优的;
  • 无论选什么策略都要全项目一致并记录——不一致比不标准化更糟;
  • 标准化后抽样人工检查改变了的分子,确认改变合理。

延伸资源