简介:《化学中的机器学习现在与未来》是由阿斯利康研究员Jon Paul Janet制作的学术报告PDF,面向化学、药学及计算科学背景的科研人员和学生,系统梳理了机器学习在化学领域的应用图景与未来趋势。报告以AlphaFold在CASP13/14蛋白折叠预测竞赛中的突破为切入点,结合阿斯利康药物发现中的多目标设计真实案例,深入讲解了分子结构预测、功能材料设计、化学反应预测及数据驱动实验设计等前沿方向,帮助读者理解AI方法如何加速药物筛选、催化剂优化与合成路线规划。资源为单个PDF文档,共1个文件,压缩包大小约41.44MB,幻灯片排版清晰、图表完整,适合直接阅读、打印或作为组会分享的参考资料。目前已有222人学习下载,对于希望快速把握机器学习与化学交叉领域核心框架的读者,是一份兼具广度与深度的入门进阶素材。
1. 机器学习在化学里不是玄学:这篇文章要讲的路径
当看到《Machine Learning in Chemistry now and in the future.pdf》这个标题时,第一反应可能是“又是一篇综述”。但我更愿意把它当成一张作战地图:现在的机器学习早已不是化学研究里的背景板,而是能直接缩短实验周期的工具。这篇文章面向那些已经被数据重复性折磨过的化学、材料从业者——手里有反应产率、光谱或催化测试数据,想用模型预测下一步,又不确定该从哪条路径进场。我会先把数据表示和选型逻辑捋清楚,再用可复现的最小实验展示流程,最后把未来趋势落到主动学习和自动化闭环。适合有一定基础、准备动手跑模型的读者。
2. 化学数据的机器可读化:分子表示与数据集构建
机器学习模型吃的是数字,不是化学式。同样是“把分子变成特征”这一步,选错表示方式会让后续所有努力白费。这一章先梳理表示方式,再给出完整的数据清洗和指纹生成流程。
2.1 从SMILES到图表示:怎么选分子编码
化学数据不能直接喂给sklearn,必须先转成机器能处理的向量或图结构。先看几种常见表示方式。
| 表示方式 | 典型用途 | 优点 | 缺点 |
|---|---|---|---|
| SMILES 字符串 | 输入生成模型、计算工具 | 文本简洁、生态成熟 | 同一分子有多种写法,需规范化 |
| InChI | 数据库去重、数据交换 | 标准唯一 | 可读性差 |
| Morgan 指纹(ECFP) | 经典 ML 特征 | 计算快、可解释 | 丢失三维构象信息 |
| 分子图(原子+键) | 图神经网络输入 | 保留拓扑结构 | 需要深度学习框架 |
选型理由可以很直接:数据量在几千到一两万条时,Morgan 指纹加随机森林或 XGBoost 往往是最稳的起点,因为特征维度适中,树模型对离散特征又不敏感。只有当数据量到十万级、且任务涉及长程相互作用或复杂拓扑时,切换到图神经网络才有明显收益。换表示之前,先确认手头数据量能不能撑起模型复杂度,否则就是给自己挖坑。
2.2 构建一个可用的训练集:数据清洗与划分
我见过太多“模型效果差”最后查出来是数据里有金属盐残留、SMILES 没规范化或者测试集和训练集有同骨架分子。清洗这一步必须做扎实,顺序也很重要:先解析,再规范化,再去盐,最后去重。
常见的清洗步骤可以总结成四条:
- 用 RDKit 解析 SMILES,解析失败的直接丢弃。
- 用
Chem.MolToSmiles(mol, canonical=True)生成规范 SMILES,消除原子编号顺序的差异。 - 去除含
.的 SMILES,这类写法通常表示盐或非共价复合物,不能简单当作单分子处理。 - 按规范 SMILES 去重,并把数据按 Murcko 骨架划分训练/测试集,避免同骨架分子跨集。
下面是一个可以直接套用的清洗脚本片段。
import pandas as pd import numpy as np from rdkit import Chem from rdkit.Chem.Scaffolds import MurckoScaffold from sklearn.model_selection import GroupShuffleSplit def clean_smiles(smiles_list): """清洗SMILES列表:解析、去盐、规范化。""" cleaned = [] for smi in smiles_list: mol = Chem.MolFromSmiles(smi) if mol is None: continue # 含'.'的SMILES通常表示盐、溶剂合物等混合物 if '.' in smi: continue # 生成规范SMILES,避免同一分子多种写法 canonical = Chem.MolToSmiles(mol, canonical=True) cleaned.append(canonical) return cleaned # 假设原始数据有两列:smiles 和 property data = pd.read_csv('my_data.csv') data['clean_smi'] = clean_smiles(data['smiles'].tolist()) data = data.dropna(subset=['clean_smi']) # 去重:同一个规范SMILES只保留一条 data = data.drop_duplicates(subset='clean_smi') # 计算每个分子的Murcko骨架,用于后续按骨架划分 data['scaffold'] = data['clean_smi'].apply( lambda s: Chem.MolToSmiles( MurckoScaffold.GetScaffoldForMol(Chem.MolFromSmiles(s)) ) ) # 按骨架分组做随机划分,保证同一个骨架不会同时出现在训练和测试 splitter = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) train_idx, test_idx = next(splitter.split(data, groups=data['scaffold'])) train, test = data.iloc[train_idx], data.iloc[test_idx]这段代码的逻辑说明:clean_smiles里去掉了含.的条目,但没有做更完整的中和电荷操作,如果数据里有带电荷分子,建议再用标准化工具做一步中和。骨架划分是化学机器学习里非常关键的一步——它把化学空间的重叠度压低,测试分数才不会被同系物“虚高”。random_state=42锁住划分结果,方便复盘对比。
2.3 编码与特征:用 RDKit 生成 Morgan 指纹
数据清理完,接下来就是把分子变成向量。Morgan 指纹也叫 ECFP(Extended Connectivity Fingerprint),它把每个原子周围的环境用圆形子结构编码成二进制或计数向量。两个参数最关键:半径radius和位长nBits。
半径 2 就是常见的 ECFP4,表示考虑了原子周围 2 个化学键内的子结构;位长通常选 1024 或 2048。小分子数据集用 2048 位不会太稀疏,太大反而让树模型无谓地分叉。生成代码也很短。
from rdkit import Chem from rdkit.Chem import AllChem import numpy as np def morgan_fp(smiles, radius=2, nbits=2048): """把SMILES转换成Morgan指纹位向量。""" mol = Chem.MolFromSmiles(smiles) if mol is None: return None # radius=2对应ECFP4,nbits决定特征维度 fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=radius, nBits=nbits) # BitVect转成numpy数组,方便进模型 arr = np.array(fp.ToList(), dtype=np.float32) return arr # 示例:生成一条指纹 smi = 'CC(=O)NC1=CC=CC=C1' fp_vector = morgan_fp(smi)逻辑说明:fp.ToList()把位向量变成 0/1 列表,再转成 float32 数组,这样可以直接拼成特征矩阵。参数radius=2是化学信息学里的默认经验值,对大多数性质预测够用;如果你处理的是大环或长链分子,可以尝试radius=3,但要注意特征维度膨胀和过拟合风险。
需要提醒的是,指纹一旦生成就会丢失三维构象信息,比如手性、顺反异构的影响。如果你的任务恰好是手性选择性预测,单靠 2D 指纹很难达到 80% 以上的准确率,那种场景要么加 3D 描述符,要么直接换图神经网络。
3. 模型选型:经典机器学习还是图神经网络
数据表示搞定,下一步就是选模型。很多初学者的第一反应是“做化学一定要用图神经网络”,但真实项目里,随机森林和 XGBoost 依然是最可靠的主力。这一章对比两条路线,并给出一个可复现的基准实验。
3.1 表格数据场景:随机森林与 XGBoost 为什么还能打
指纹矩阵本质上是一个稀疏的 0/1 表格,这种数据对树模型非常友好。随机森林不会因为特征离散而做无意义的分割,调参空间小,几百棵树跑下来很少出意外。XGBoost 则能捕捉到指纹之间的非线性交互,但需要小心学习率和树深度。
我把两个模型的关键超参数范围整理如下。
| 模型 | 超参数 | 典型范围 | 备注 |
|---|---|---|---|
| RandomForest | n_estimators | 500-2000 | 更多树方差更小,注意内存 |
| RandomForest | max_features | sqrt 或 1/3 | 降低树间相关性 |
| XGBoost | learning_rate | 0.05-0.2 | 配合早停避免过拟合 |
| XGBoost | max_depth | 4-8 | 指纹特征下加深极易过拟合 |
选型逻辑很简单:数据量小、噪声大、特征维度几千维,树模型是首选。只有当你发现树模型的误差已经收敛,且你有足够多的数据去训练更复杂的表达时,才需要考虑图神经网络。
3.2 图神经网络的适用边界与常用架构
图神经网络把分子看成原子和键组成的图,通过消息传递机制让每个原子逐步聚合邻居信息。它的长处是能自主学习与性质相关的子结构,而不是依赖人工设计的指纹。常见的架构有图注意力网络、图同构网络和消息传递网络。
边界条件要讲清楚:如果训练集只有几千个分子,GNN 很容易把训练集背下来,测试集上表现反而不如指纹加树模型。GNN 适合的是数据量至少两万、任务有明确拓扑敏感性(比如溶解度、毒性、反应选择性)的场景。另外,GNN 训练对 GPU 和框架依赖高,调参成本也高,不是“开箱即用”的工具。
3.3 可复现的基准实验:指纹+RF vs GNN
我先给一个完整的指纹加随机森林的训练验证代码,这也是我拿到新任务后最先跑的 baseline。
from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import cross_val_predict from sklearn.metrics import mean_absolute_error, r2_score # 假设 train 数据里有 fp_list 和 property 列 # fp_list 是上一章生成的指纹列表(每条都是 np.array) X = np.vstack(train['fp_list'].values) y = train['property'].values # 随机森林:树数 800,特征采样用 sqrt model_rf = RandomForestRegressor( n_estimators=800, max_features='sqrt', random_state=42, n_jobs=-1 ) # 5折交叉验证 cv_pred = cross_val_predict(model_rf, X, y, cv=5) print('CV MAE:', mean_absolute_error(y, cv_pred)) print('CV R2:', r2_score(y, cv_pred))逻辑说明:n_jobs=-1让随机森林用满所有 CPU;max_features='sqrt'可以防止所有树都在同一批强特征上分叉,从而提升泛化。cross_val_predict返回的是逐样本的预测值,可以直接和真实值算误差。
如果 GNN 是备选方案,我习惯先用一个最小消息传递层做通路实验,确认 pipeline 没有问题再上完整模型。下面是最简的 GNN 核心层片段。
import torch import torch.nn as nn class SimpleMessagePassing(nn.Module): """最小可读的消息传递层,只做邻居特征聚合。""" def __init__(self, in_dim, out_dim): super().__init__() self.W = nn.Linear(in_dim, out_dim) self.act = nn.ReLU() def forward(self, x, edge_index): # x: 节点特征矩阵 [N, in_dim] # edge_index: 两行张量,src和dst src, dst = edge_index # 对每个节点,把来自邻居的消息求和 messages = torch.zeros_like(x) messages.index_add_(0, dst, self.W(x[src])) # 自身变换叠加邻居消息 x_new = self.act(self.W(x) + messages) return x_new参数说明:edge_index的构建来自 RDKit 分子的邻接矩阵;index_add_是 PyTorch 提供的索引累加操作,比循环邻居快不少。真实 GNN 里还要加 BatchNorm、Dropout 和全局池化层,这里只展示消息传递的核心思路。跑通这个小层之后,再去叠加层数,否则一上来就堆大模型,出问题时很难定位是数据、特征还是网络结构的问题。
4. 实验验证陷阱:从过拟合到数据泄漏的排查清单
模型选完、训练跑完,不代表可以直接用。化学机器学习里最隐蔽的错误都藏在验证环节:数据泄漏、评估指标选错、可解释性用了黑匣子。这一章专门给出一份排查列表。
4.1 数据泄漏:最常见的翻车原因
数据泄漏的特征是交叉验证分数很高,但一上线上数据就崩。最常见的原因是特征预处理发生在数据集划分之前。
举例说明:对性质做标准化(StandardScaler)时,如果先在整个数据集上计算均值和方差,再划分训练/测试,测试集的统计信息就已经渗透进模型的训练过程。正确做法是先划分,再只在训练集上拟合规一化参数。
# 错误做法 scaler = StandardScaler() X_all_scaled = scaler.fit_transform(X_all) # 测试信息提前进入scaler train_test_split(X_all_scaled, y_all) # 正确做法 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X_all, y_all, test_size=0.2, random_state=42) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # 只用训练集参数除了标准化泄漏,化学数据里还有一个隐藏泄漏点:同一分子的规范 SMILES 没有做,导致同分异构体同时出现在训练集和测试集。所以第 2 章的清洗步骤不是形式主义,而是在给验证集“去污”。
4.2 不均衡与回归任务的评价指标
分类任务不能只盯 ROC-AUC,当正负样本比例悬殊时,PR-AUC 更能反映模型在少数类上的真实表现。回归任务则不能只看 R²,建议同时看 MAE 和残差分布。
我每次跑完回归模型都会画一张残差图,检查预测误差是否随真值增大而发散。
import matplotlib.pyplot as plt true = y_test pred = model.predict(X_test_scaled) residual = true - pred plt.scatter(true, residual, alpha=0.5) plt.axhline(0, color='k', linestyle='--') plt.xlabel('true property') plt.ylabel('residual (pred - true)') plt.show()如果残差随真值呈喇叭状分布,说明模型在极端值上系统性不稳。此时需要检查数据是否有高杠杆点,或者考虑对目标变量做对数变换。忘却这一点,只报一个 R² 好看很容易把问题掩盖掉。
4.3 模型可解释性:SHAP 与注意力权重怎么用
用确定性模型并不等于黑匣子。指纹+随机森林的场景可以直接用 SHAP 解释特征贡献。下面是一个典型用法。
import shap # model_rf 是之前训练好的随机森林 explainer = shap.TreeExplainer(model_rf) shap_values = explainer.shap_values(X_test[:100]) # 画摘要图:每个点是特征,颜色表示数值高低,横轴表示SHAP值 shap.summary_plot(shap_values, X_test[:100], feature_names=fp_names)注意fp_names需要自己定义,建议直接用该指纹位对应的子结构片段 ID,方便后续回溯到具体官能团。GNN 场景下,注意力权重也常被用来解释,但要保持谨慎:注意力权重反映的是模型计算路径中的相关性,不是因果性。把注意力权重当成“哪个原子更重要”之前,至少要做一次原子随机置换测试,确认模型真的依赖这个原子。
5. 避坑 / 常见问题:跑化学 ML 时我踩过的几个坑
这个方向上手容易,做扎实难。我把真实项目里踩过的四个典型问题按“现象 → 原因 → 解决”列出来,每一条都对应一种常见的失败模式。
5.1 现象:训练集 R²=0.99,测试集 R²=0.2
这是个很经典的翻车现场。表面看是过拟合,但根因常常是数据划分方式不对:按随机划分时,同一个骨架的分子可以同时出现在训练集和测试集;模型实际上学到了“看到这个骨架就输出这个数值”,一到新骨架自然崩溃。
原因:没有做骨架划分,测试集和训练集化学空间重叠度太高。
解决:改用第 2 章里基于GroupShuffleSplit的骨架划分,同时统计训练集和测试集骨架重叠数。重叠骨架占测试集比例超过 30%,就要警惕分数虚高。
5.2 现象:模型对同系物失效,换骨架就崩
另一种常见情况:测试集里的分子和训练集的 Tanimoto 相似度都很高,模型表现不错;但拿到供应商库里的新骨架后,误差直接翻倍。这说明模型并没有学会化学规则,只是在做近邻回忆。
原因:训练集化学空间覆盖太窄,多样性不足。
解决:在划分训练集时用 MaxMin 或 Butina 聚类挑选代表性分子,并在评估时报告测试集与训练集的最大相似度分布。这个最大相似度的中位数低于 0.4 时,模型外推风险很大,要谨慎宣称“全局可用”。
from rdkit.Chem import DataStructs from rdkit.Chem import AllChem # 计算测试集每个分子与训练集最近邻的相似度 train_fps = [AllChem.GetMorganFingerprintAsBitVect(Chem.MolFromSmiles(s), 2, 2048) for s in train['clean_smi']] test_fps = [AllChem.GetMorganFingerprintAsBitVect(Chem.MolFromSmiles(s), 2, 2048) for s in test['clean_smi']] test_max_sim = [] for fp_test in test_fps: sims = DataStructs.BulkTanimotoSimilarity(fp_test, train_fps) test_max_sim.append(max(sims)) print('测试集最大相似度中位数:', np.median(test_max_sim))这段代码用BulkTanimotoSimilarity一次计算一个测试分子和整个训练集的相似度,取最大值为最相似训练样本的相似度。中位数过低时,说明测试集里有大量离群结构,模型预测不靠谱是正常现象,不是 bug。
5.3 现象:特征标准化把分类器搞崩了
我见过有人在二进制指纹矩阵上强行套StandardScaler,结果模型准确率反而掉了。指纹每一位是 0 或 1,标准化后变成带正负的连续值,等于破坏了“片段是否存在”的语义。
原因:特征语义是离散逻辑,不适合用高斯标准化。
解决:指纹特征直接喂树模型,不需要任何缩放。如果一定要用线性模型,可以试试MaxAbsScaler或直接做 L2 归一化,但效果也未必更好。记住一个原则:化学里的人工特征,能不动就不动。
5.4 现象:同一个分子因为 SMILES 书写不同被当成两条样本
这属于数据血缘问题。数据集来源于多个数据库时,比如 PubChem、ZINC、内部旧数据,同一个分子可能写成不同的 SMILES,比如芳香环的凯库勒式与芳香式。如果没有规范化,去重失败,模型会把同一个结构当成两个不同样本,既造成数据泄漏,又稀释了 label 语义。
原因:没有统一 SMILES 规范。
解决:用Chem.MolToSmiles(mol, canonical=True)得到规范表达,再按它去重。如果对精确结构一致性要求高,还可以直接用 InChI 字符串做 key,因为 InChI 对互变异构的处理更严格。去重后建议随机挑 20 条,人工肉眼确认一遍。
6. 未来在哪:主动学习、大模型与自动化实验闭环
看标题里 “now and in the future”,最值得关注的是未来三年内能被普通课题组用上的方向。我不建议盲目追大模型,而是从三个可落地的技能点说起。
6.1 主动学习怎么减少湿实验次数
主动学习的大逻辑是:先在小训练集上学一个模型,然后用模型挑出“最不确定”的分子去做实验,把新实验结果加回训练集再重训。核心技巧是选择不确定性度量,比如分类任务的预测概率熵,或回归集成模型的预测标准差。
我常给的做法是训练 5 个随机森林,用预测方差作为不确定性。不确定性最高的分子优先做实验,能显著减少湿实验次数。
import numpy as np from sklearn.ensemble import RandomForestRegressor # 训练多个模型,方差大的样本优先推荐 models = [RandomForestRegressor(n_estimators=500, random_state=i) for i in range(5)] for model in models: model.fit(X_train, y_train) preds = np.vstack([model.predict(X_test) for model in models]) uncertainty = preds.std(axis=0) # 用标准差衡量不确定性 top_recommend = np.argsort(uncertainty)[::-1][:20]这个代码展示了怎么用集成模型找到当前最“有价值”的实验候选。实际闭环里,这些候选会被排进实验队列,而不是简单按精度排序。
6.2 化学大模型:从分子生成到性质预测
大模型在化学领域最成熟的应用是分子生成,比如给定目标性质生成候选结构。对性质预测,大模型不一定会比 GNN 更强,尤其在数据量不够的课题组场景下,微调大模型反而容易过拟合。我的建议是把它当作生成工具,而不是万能预测器。真要微调,优先考虑用现有 checkpoint,而不是从头训练。
6.3 实验闭环:ML+机器人平台的落地技巧
未来两三年,自动化实验平台会把“预测-实验-再训练”的闭环从论文推向实验室实操。落地有一个关键细节:把每一轮模型预测和对应实验结果都写进同一个数据库,用版本号管理。否则闭环跑一个月,你根本说不清哪一批预测是被哪一版模型生成的。
我现在的习惯是每跑一个模型都保留一份规范 SMILES、骨架划分和指纹参数的副本,避免复盘时发现数据版本对不上。实验数据库里的每一行都至少要有一条model_version和data_version字段,这个习惯帮我省过好几轮从头排查的苦工。希望帮到你。
本文还有配套的精品资源,点击获取