3. 分子数据获取与清洗

分子性质图网络预测的第一道工程门槛并不在模型结构,而在数据。一个分子性质预测任务能否复现、能否泛化,往往在数据进入模型之前就已经被决定。本节围绕“从公开数据集到可训练图样本”的完整链路,讲清楚每个环节该做什么、怎么做,以及哪些错误会直接导致指标虚高或模型失效。

一、公开数据集:先明确任务边界再选数据

分子性质预测常用的公开数据源包括:MoleculeNet 系列(ESOL、FreeSolv、Lipophilicity、BBBP、Tox21、QM7/QM8/QM9 等)、PubChem、ChEMBL、ZINC,以及物理化学实验数据库。选择数据集时,先确认三件事:预测目标(回归还是分类)、标签来源(实验测定、计算模拟还是文献汇总)、规模与分布(样本量、性质取值范围、是否高度不平衡)。

例如 ESOL 是约 1128 条水溶性回归数据,标签为 logS;BBBP 是约 2000 条血脑屏障穿透二分类数据。二者都适合做教学基线,但若把 ESOL 的回归流程直接套到 BBBP 上,评价指标和损失函数都会错。因此数据获取的第一步不是下载,而是写清楚任务卡:目标列、单位、任务类型、允许的输入表示。

二、SMILES 解析:把字符串变成可计算的分子对象

SMILES 是分子的线性编码,但它是“语法”而非“语义完整”的表示。解析时推荐使用 RDKit:

from rdkit import Chem
from rdkit.Chem import Descriptors

mol = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)O")  # 阿司匹林
if mol is None:
    print("解析失败")
else:
    mol = Chem.AddHs(mol)          # 补氢,便于后续图构建
    print(Chem.MolToSmiles(mol))   # 规范化 SMILES
    print(Descriptors.MolWt(mol))  # 分子量

解析失败通常来自非法价键、未闭合环、错误芳香性标记或编码字符问题。工程上必须把 MolFromSmiles 返回 None 的记录单独落盘,而不是直接丢弃,便于回溯数据质量。解析成功后,分子对象可进一步导出原子、键、电荷、手性、芳香性等图结构信息,这是图神经网络输入的原始素材。

三、去重与标准化:同一分子的多种写法

同一分子可能有多个 SMILES 写法(如互变异构、芳香/凯库勒式、原子顺序不同)。若不去重,训练集与测试集会出现“同一分子不同写法”,造成信息泄漏。标准做法是:

  • 规范化 SMILES:用 Chem.MolToSmiles(mol, canonical=True) 生成唯一表示。
  • 去盐与去溶剂:用 rdMolStandardize 的 LargestFragmentChooser 保留最大片段,去除盐离子和溶剂分子。
  • 互变异构标准化:用 TautomerEnumerator 选择规范互变异构体,避免同一分子因互变被当成两条样本。
  • 去重:以规范化 SMILES 或 InChIKey 为键,保留标签一致或按规则合并冲突标签。

标准化后应记录“原始 SMILES → 规范 SMILES”的映射表,便于审计。若同一规范分子对应多个不同标签,需按任务决定取均值、取中位数或标记为冲突样本。

四、缺失值处理:区分“缺失”与“无效”

分子数据中的缺失通常有三类:标签缺失、描述符计算失败、结构信息不完整。处理原则是先分类再处理:

  • 标签缺失:回归任务可删除或插补,但插补会引入偏差,建议优先删除并记录删除比例;分类任务若正负样本缺失严重,需重新评估数据源。
  • 描述符缺失:如某分子无法计算 LogP,可尝试替代描述符或标记为缺失指示变量,而不是简单填 0。
  • 结构缺失:解析失败或原子数异常,应直接剔除并记录原因。

缺失值处理必须保留“处理日志”:删除了多少条、插补了多少条、插补方法是什么。否则后续指标无法解释。

五、数据泄漏风险:最容易被忽视的指标杀手

分子数据泄漏常见于四种场景:

  1. 重复分子跨集:同一分子同时出现在训练集和测试集,模型只是记忆。
  2. 相似分子跨集:骨架高度相似的分子被随机划分,导致测试集“过于容易”。
  3. 标签泄漏:把与标签强相关的描述符(如直接由标签计算得到的量)作为输入。
  4. 预处理泄漏:在划分训练/测试之前,用全量数据做标准化、特征选择或缺失值插补。

工程上推荐使用 骨架划分(scaffold split) 或 聚类划分,而不是简单随机划分。骨架划分按 Murcko 骨架分组,保证测试集分子骨架与训练集不同,更接近真实药物发现场景。若必须随机划分,至少先按规范化 SMILES 去重,并检查训练/测试的相似度分布。

六、工程化数据管道:从原始文件到图样本

一个可复现的分子数据管道应包含以下阶段:

  1. 原始层:保存下载的 CSV/SDF 原始文件,不修改。
  2. 解析层:SMILES → RDKit Mol,记录解析失败。
  3. 标准化层:去盐、去溶剂、互变异构标准化、规范化 SMILES。
  4. 去重层:按规范 SMILES/InChIKey 去重,处理标签冲突。
  5. 划分层:按骨架或聚类划分训练/验证/测试,固定随机种子。
  6. 图构建层:原子特征(元素、度、电荷、杂化、芳香性、手性)、键特征(键型、共轭、环)→ 图对象。
  7. 缓存层:将图样本序列化为 PyG/DGL 可读格式,避免每次训练重复解析。

下面这张图展示了从原始数据到图样本的完整数据流,以及各阶段的关键检查点:

分子数据工程管道:从原始数据到图样本 原始数据层 CSV / SDF / SMILES 只读保存 SMILES 解析 MolFromSmiles 失败记录落盘 标准化 去盐 / 去溶剂 互变异构规范化 去重 规范 SMILES / InChIKey 标签冲突处理 划分 骨架 / 聚类 固定种子 图构建 原子特征 + 键特征 PyG / DGL 图对象 缓存层 序列化图样本 避免重复解析 训练 / 验证 / 测试 DataLoader 批处理 图网络输入 关键检查点与风险控制 解析失败率 记录原因,不静默丢弃 去重前后数量差 检查重复比例是否异常 标签冲突数 同一分子多标签需裁决 划分泄漏检查 骨架重叠 / 相似度分布 原则:原始数据只读 → 每步可追溯 → 划分前不做全局统计 → 图样本缓存可复现 任何一步的静默丢弃或全局预处理,都可能让测试指标失去意义

七、一个可执行的最小管道示例

下面给出一个简化但完整的处理流程,便于直接改造成项目脚本:

import pandas as pd
from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize

def standardize(smi):
    mol = Chem.MolFromSmiles(smi)
    if mol is None:
        return None
    mol = rdMolStandardize.LargestFragmentChooser().choose(mol)
    mol = rdMolStandardize.TautomerEnumerator().Canonicalize(mol)
    return Chem.MolToSmiles(mol, canonical=True)

df = pd.read_csv("raw.csv")
df["std_smiles"] = df["smiles"].apply(standardize)
df = df.dropna(subset=["std_smiles", "label"])
df = df.drop_duplicates(subset=["std_smiles"], keep="first")
df.to_csv("clean.csv", index=False)

这段代码只覆盖了标准化和去重,实际工程中还需加入骨架划分、图构建和缓存。建议把每一步的输出都保存为独立文件,形成可审计的数据版本链。

常见误区:在划分训练/测试之前,用全量数据计算描述符均值方差做标准化,或先做特征选择再划分。这会让测试集信息泄漏到训练过程,导致指标虚高。正确顺序是:先划分,再在训练集上拟合预处理器,然后应用到验证集和测试集。
实践建议:对每个数据集生成一份“数据质量报告”,至少包含:原始样本数、解析失败数、标准化后样本数、去重后样本数、标签分布、划分后各集样本数、骨架重叠率。这份报告比任何模型调参都更能解释最终指标的可信度。

总结来说,分子数据工程的核心不是“把数据读进来”,而是建立一条可追溯、可复现、防泄漏的管道。公开数据集提供起点,SMILES 解析和标准化保证分子身份一致,去重和缺失值处理保证样本质量,骨架划分和预处理顺序控制泄漏风险,最终以图样本缓存的形式交付给模型。这条管道做扎实,后续的图网络训练才有意义。