第4节 分子描述符计算与一致性验证归纳总结

分子描述符计算,说白了就是把分子结构变成数字。这些数字就是模型的“食材”——食材不新鲜,菜就做不好。我在ADMET项目中见过太多次了,描述符算错了,后面所有工作全白搭。

这一节,我把分子描述符计算和一致性验证的核心逻辑做个归纳。咱们不聊虚的,只讲你实际干活时用得上的东西。

1. 描述符计算的三个核心维度

我个人习惯把描述符计算拆成三个维度来看。你想想看,任何一个描述符计算任务,只要这三个维度没问题,结果基本靠谱。

维度 关注点 常见问题
化学空间覆盖 描述符能否区分不同分子? 全是零或常数值,白算
计算可重复性 同一分子两次算的结果一样吗? 工具版本不同结果不同
物理意义可解释性 这个数字到底代表什么? 负的logP、超过100的TPSA
我的经验: 拿到一批描述符,先扫一遍最大值、最小值、方差。如果某个描述符所有分子算出来都一样,直接删掉——它不提供任何信息量。

2. 主流工具与差异分析

目前常用的工具就那几个:RDKit、PaDEL、Mordred。我在项目里试过把它们全跑一遍,结果发现差异比想象中大。

工具 描述符数量 速度 维护状态
RDKit ~200(2D) 快 活跃,社区大
PaDEL ~1800(包含3D) 中等 维护放缓
Mordred ~1800 快 活跃

为什么会这样?同一个logP,RDKit算的是基于原子贡献的ClogP,PaDEL算的可能包括不同校正项。你直接拿两个工具的结果做混合建模,不出问题才怪。

注意: 同一工具的不同版本也可能产生差异。RDKit从2020.03到2023.09,至少有5个描述符的计算方式做过调整。固定版本号,别随便升级。

3. 一致性验证的三种方法

验证方法不用太花哨,实用就行。我常用的就三种,覆盖了从单个分子到整批数据的场景。

方法一:多工具交叉验证

选10~20个结构差异大的分子,用两个工具分别算,对比相同描述符的数值。

# 示例:RDKit vs Mordred 交叉比对脚本片段
from rdkit import Chem
from mordred import Calculator, descriptors

def cross_validate(smiles_list, desc_name='SLogP'):
    rdkit_vals = []
    mordred_vals = []
    for smi in smiles_list:
        mol = Chem.MolFromSmiles(smi)
        # RDKit 计算
        from rdkit.Chem import Descriptors
        rdkit_vals.append(Descriptors.MolLogP(mol))
        # Mordred 计算
        calc = Calculator(descriptors.SLogP)
        mordred_vals.append(calc(mol))
    return rdkit_vals, mordred_vals

方法二:批处理统计检验

对整批数据,计算每个描述符的均值、标准差、缺失率。两次计算之间的差异超过阈值就报警。

检查项 阈值参考 触发动作
均值漂移 超过 5% 检查工具版本
缺失率变化 超过 1% 检查分子标准化
极值异常 超出 3σ 单独复核分子结构

方法三:边界值与异常检测

故意构造一些边界分子——比如极端大环、含重金属原子、带电荷的片段。这些分子容易暴露出描述符计算的bug。

核心逻辑: 一致性验证不是为了证明“两个工具算得一样”,而是为了发现“哪里不一样,为什么不一样,影响有多大”。

分子描述符计算与一致性验证流程 分子输入 (SMILES/SDF) 描述符计算 (RDKit / PaDEL / Mordred) 原始描述符集 一致性验证 (交叉比对 / 统计检验) 验证通过 → 用于建模 验证失败 → 排查修复 核心目标 确保描述符可重复、可解释 消除工具与版本差异

4. 常见问题与排查经验

这部分全是真金白银的实战教训。我自己踩过的坑,给你列出来,别再走一遍。

问题1:版本差异导致描述符漂移

我曾经在同一个项目里,用RDKit 2020.03算了一批描述符,半年后用2022.09重算,发现SLogP有系统性的0.1偏移。虽然不大,但对某些高精度模型来说,这0.1足以改变排序。

解决方案:固定工具版本,并在项目文档里写明。

# 记录工具版本信息
# environment.yaml 示例
name: admet_desc
channels:
  - conda-forge
dependencies:
  - rdkit=2023.09
  - pandas=2.1.0
  - numpy=1.25.0

问题2:分子标准化不一致

同一个SMILES,有的工具自动去盐,有的保留盐,有的做tautomer归一化。结果算出来的描述符能差20%。

我的做法:在计算之前,统一走一遍分子标准化管线。

def standardize_mol(smiles):
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return None
    # 去盐
    from rdkit.Chem import SaltRemover
    remover = SaltRemover.SaltRemover()
    mol = remover.StripMol(mol)
    # 电荷中和
    from rdkit.Chem import rdMolDescriptors
    mol = rdMolDescriptors.RemoveUncharged(mol)
    return mol

问题3:缺失值与异常值处理

有些描述符对特定结构算不出来,返回NaN或inf。如果你不处理,模型直接报错。

描述符类型 NaN原因 处理方式
3D描述符 构象生成失败 回退到2D描述符
带电描述符 分子不含可电离基团 填充0或中位数
拓扑描述符 分子包含非标准原子 标记后剔除或替换
特别提醒: 不要用0填充所有NaN。我见过有人把logD的NaN填成0,结果模型学出了一个“logD=0等于没数据”的假规律。要分情况处理。

5. 与SVA验证的衔接

SVA验证需要可靠的输入。如果描述符计算这一步有抖动,SVA的结论就是空中楼阁。

我在项目中是这样衔接的:

  • 描述符计算完成后,先跑一致性验证,通过后再交给SVA管线
  • SVA的“输入验证”环节,加入描述符范围检查(比如logP是否在合理区间)
  • 如果SVA验证失败,先回溯到描述符计算,排除数据来源问题
小技巧: 在SVA的验证报告中,单独开一个"数据质量"小节,把描述符一致性验证的结果附上去。这能让审阅者一眼看出你的数据是可靠的。

归纳一下,分子描述符计算不是跑个脚本就完事的事。工具选型、版本管理、标准化管线、一致性验证,这几个环节缺一不可。把这些基础打牢了,后面的ADMET预测和SVA验证才能站得住脚。