第4章 数据准备:分子描述符计算、标准化、数据集划分

好了,前面我们聊了SHAP和ADMET的基本概念,现在要动真格的了。数据准备——这步你做不好,后面模型再花哨也是白搭。我见过太多项目,数据处理的坑比算法本身的坑多得多。

4.1 分子描述符计算:把分子结构变成数字

分子结构是抽象的,但模型只认数字。所以第一步,把分子结构转化为数值特征——分子描述符。说白了,就是把每个分子的形状、极性、电荷分布等等信息,用一组数字表达出来。

核心工具: RDKit 是化学信息学领域最常用的 Python 库,没有之一。它支持超过 200 种描述符,包括 2D 描述符(如分子量、LogP、氢键供体/受体个数)和 3D 描述符(如分子表面积、静电势)。

举个最简单的例子,用 RDKit 计算分子的摩根指纹(Morgan Fingerprints):

from rdkit import Chem
from rdkit.Chem import AllChem

smiles = "CCO"  # 乙醇
mol = Chem.MolFromSmiles(smiles)
fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=2048)
print(fp.ToList()[:20])  # 输出前20位
我的经验: 摩根指纹的 radius 参数我一般设为 2 或 3。设太大,特征维度爆炸,模型容易过拟合;设太小,结构信息不够。你可以试试看,对不同的数据集,效果差异很明显。

常用的描述符种类很多,我整理了一张速查表:

描述符类别举例维度说明
2D 描述符分子量、LogP、TPSA、HBD、HBA、RotBonds~200 种直接计算,速度快
3D 描述符分子体积、溶剂可及表面积、偶极矩~50 种需要构象优化,计算慢但更准确
指纹(Fingerprint)摩根指纹、MACCS 指纹、拓扑指纹2048 / 166 / 1024稀疏高维,适合树模型和神经网络
物理化学性质pKa、溶解度、logD~20 种通常用经验公式或第三方工具估算
注意: 不要一股脑把所有描述符都加上。冗余特征会导致维度灾难,而且 SHAP 解释会变得混乱。我习惯先选 50-100 个与 ADMET 性质强相关的 2D 描述符,后续用特征选择方法再砍。

4.2 标准化:消除量纲差异

描述符的量纲差异很大。比如分子量动辄几百,而 LogP 一般只有个位数。如果不做标准化,模型中权重大的特征会严重偏向大数值的描述符。你想想看,模型会以为分子量是决定 ADMET 性质的关键——其实很可能只是因为它数值大。

常用的标准化方法有两种:

  1. Z-score 标准化: 减去均值,除以标准差。适合数据近似正态分布的情况。
  2. Min-Max 缩放: 缩放到 [0,1] 区间。适合有边界的描述符(如指纹位点 0/1)。

我用 scikit-learn 实现标准化:

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# 假设 X 是 n_samples x n_features 的矩阵
scaler = StandardScaler()  # 或者 MinMaxScaler()
X_scaled = scaler.fit_transform(X)
关键点: 只能用训练集的均值和标准差来标准化验证集和测试集。切记!否则会造成信息泄露,模型评估结果虚高。

我踩过这个坑。有一次我偷懒,直接对整个数据集做了标准化再划分——结果训练集和测试集的数据“混在一起”了,模型在测试集上的 AUC 高达 0.98,但上线后只有 0.65。嗯,从那以后,我每次都会把标准化器先 fit 训练集,再 transform 验证和测试集。

4.3 数据集划分:训练集、验证集、测试集

数据准备好了,接下来要分成三份:

  • 训练集(Train): 模型从这里学习模式,占比通常 60%–80%。
  • 验证集(Validation): 用于调参、选择模型,不参与最终评估。占比 10%–20%。
  • 测试集(Test): 模型做完所有调整后,最后一次评估。占比 10%–20%。

注意:验证集和测试集的作用完全不同。验证集是在模型开发过程中帮你选超参数、选特征的;测试集则是模拟真实场景,用于评估最终模型的泛化能力。千万不要用测试集来调参,否则你得到的性能就是“作弊”的。

划分方法我一般用分层随机抽样,尤其是 ADMET 数据经常有类别不平衡问题。举个例子,如果只有 5% 的分子是有毒的,那划分时保持每份中毒性分子的比例都接近 5%。

from sklearn.model_selection import train_test_split

X_train, X_temp, y_train, y_temp = train_test_split(
    X_scaled, y, test_size=0.3, random_state=42, stratify=y)
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp)

这里的 stratify=y 保证了分层。验证集和测试集各占原始数据的 15%(因为 temp 是 30%,再 split 一半正好 15%)。

小技巧: 对于小样本数据(少于几百个分子),可以用交叉验证代替固定划分。但交叉验证不能完全替代独立测试集——我通常坚持留出至少 10% 的样本作为最终测试。

4.4 数据准备流程图

整个流程我用一张图串起来,大家看完应该心里有数了。

数据准备核心流程 原始分子结构(SMILES) 计算分子描述符 RDKit / Mordred / PaDEL 缺失值 & 异常值处理 填充 / 删除 / 插值 特征标准化 Z-score / Min-Max 训练集 验证集 测试集 划分比例: 70% / 15% / 15% 或 80% / 10% / 10%
要点回顾:
  • 描述符计算:优先选择与任务相关的 50-100 个 2D 描述符 + 指纹。
  • 标准化:fit 训练集,transform 验证/测试集——这个顺序绝对不能错。
  • 划分:分层抽样,验证集用于调参,测试集只评估一次。

数据准备阶段就这些内容。别看步骤简单,每一步都有讲究。我当年刚开始做 ADMET 预测时,数据准备花了整整两周时间,但后来模型上线后的稳定性比团队里其他人高出一截。说白了,后面那些花哨的 SHAP 分析、模型调优,都建立在干净的数据之上。数据不干净,一切免谈。