在化学信息学和计算化学领域里,“in silico chemistry”已经不是新鲜词,但如何设计一个真正贴近实验室场景的基准测试,仍然是一块难啃的骨头。近期开始看到 onepot-Bench 这类方向被讨论,它把目光从“标准数据集上的分数”拉回到“实验台上到底发生了什么”。这篇文章会围绕 onepot-Bench 0 所代表的 lab-aware in silico chemistry benchmarks,梳理核心概念、设计思路、最小可运行示例,以及实际搭建评估体系时容易踩的坑。适合正在做反应预测、条件推荐、化学数据建模的开发者阅读,也适合想了解基准测试设计逻辑的初学者作为入门参考。
1. 背景:为什么需要实验室感知的化学基准
1.1 关于 onepot-Bench 0
“onepot”在化学里通常指一锅法,也就是多个反应步骤在同一个反应容器中连续完成,不需要中间分离。它能够减少操作步骤、节约试剂和设备消耗,特别适合串联反应、多组分反应和自动化合成场景。而 Bench 0 在这里可以理解为该基准方向的初始版本,面向的是 lab-aware 的基准测试设计:不仅评估模型是否能在标准数据上预测准确,还希望模型能感知实验室中的实际操作条件,比如溶剂、温度、催化剂、当量比、反应时间、加料顺序等。
换句话说,onepot-Bench 0 的核心主张是:一个有用的化学模型,不能只在干净整齐的 benchmark 上表现好,还应该能回答“如果我在实验室里按这个条件去做,结果会怎样”的问题。
1.2 in silico chemistry 解决什么问题
in silico chemistry 泛指通过计算机模拟、计算化学、机器学习等手段来研究化学反应和分子性质。常见的应用包括:
- 反应产物预测。
- 反应条件推荐。
- 产率预测。
- 选择性预测。
- 催化剂和溶剂筛选。
- 分子逆合成路线设计。
这些任务如果只在标准数据集上做评测,很容易出现高分低能的情况。因为实验记录中的条件参数、数据噪声、批次效应往往会被简化,而真实实验室环境中这些因素恰恰是决定成败的关键。
1.3 传统基准测试的局限性
传统基准测试通常以公开数据集为依托,把反应简化为“反应物 + 产物”的形式。这样做的好处是便于统一比较不同模型,但坏处也很明显:生产环境中的很多变量被抹掉了。具体来看,常见问题有以下几点。
- 条件信息缺失:很多数据集只保存反应 SMILES 和产率,不使用溶剂、温度、催化剂的完整信息。
- 数据分布偏差:公开数据集中的反应多来自文献专利,和自家实验室的底物空间、试剂偏好差异很大。
- 可重复性不足:同一反应在不同仪器、不同批次试剂下产率差异明显,但基准没有建模这种不确定性。
- 指标过于单一:只看 top-1 准确率或平均绝对误差,无法反映模型在“实际合成可行性”上的优劣。
1.4 lab-aware 基准的含义
lab-aware(实验室感知)的意思,是把实验室中的物理化学条件纳入基准设计和模型评估当中。例如:
- 训练集中是否包含完整的实验条件?
- 测试集的负样本是否来自真实失败的实验?
- 评估时是否考虑条件扰动带来的波动?
- 模型推荐的反应条件是否符合安全操作范围?
当基准设计开始关注这些问题,它就不再只是一个静态分数排行榜,而更像是连接计算模型和实验验证的桥梁。
2. onepot-Bench 的核心概念拆解
2.1 什么是 in silico chemistry benchmark
benchmark 是一套用于评估算法性能的标准化任务、数据集和指标。in silico chemistry benchmark 则是面向化学计算模型的标准测试环境,通常包括:
- 输入格式:例如反应物 SMILES、条件向量、分子图。
- 任务定义:例如二分类、回归、排序。
- 数据划分:例如按反应类型划分、按模板划分、按时间划分。
- 评估指标:例如 RMSE、MAE、Top-k Accuracy、ROC-AUC。
- 基线方法:例如基于指纹的随机森林、基于图的 GNN、大语言模型微调。
一个好的 benchmark 应当具备:公开可复现、指标清晰、数据划分合理、任务贴近实际。
2.2 传统基准与 lab-aware 基准的差异
| 对比维度 | 传统化学基准 | lab-aware 化学基准 |
|---|---|---|
| 输入信息 | 反应 SMILES、产物 SMILES | 反应 SMILES + 条件参数 + 实验元数据 |
| 任务目标 | 预测产物/产率 | 预测实验可行性、条件影响、波动范围 |
| 数据来源 | 文献/专利数据库 | 实验室电子记录、自动化平台数据 |
| 评估方式 | 固定测试集指标 | 留一实验、批次划分、条件扰动测试 |
| 失败样本 | 很少记录 | 明确记录失败反应 |
| 和实验闭环 | 弱 | 强,支持主动学习 |
从表格能看出,lab-aware 基准不只是对已有数据进行重新打包,而是从实验数据产生方式开始重新设计评估逻辑。
2.3 “一锅法”场景为什么适合作为首个基准
onepot-Bench 0 选择一锅法场景是有道理的。一方面,一锅法合成在工业界有真实需求,步骤短、效率高,适合自动化筛选;另一方面,一锅法反应比单步反应更复杂,涉及中间体兼容性、催化剂/试剂混用、加料顺序等条件,这对模型是更有挑战的测试场。
例如一个经典的两步一锅反应,第一步生成中间体 A,第二步加入新试剂继续反应得到最终产物 B。如果模型不清楚第一步结束后是否需要调 pH、是否必须降温,它给出的反应条件就不具备实验可操作性。
因此,one-pot 场景天然适合作为“lab-aware”的试金石:只有模型真正理解实验条件之间的依赖关系,才能在该任务上取得好结果。
3. 环境准备与项目结构
3.1 运行环境
下面要实现的示例是一个简化的 lab-aware 基准构建与评估脚本。建议环境如下:
- 操作系统:Windows / Linux / macOS 均可。
- Python 版本:3.9 或以上。
- 核心依赖:numpy、pandas、scikit-learn、rdkit。
- 开发工具:Jupyter Notebook 或 VS Code。
如果你还没有安装相关依赖,可以在命令行中执行:
pip install numpy pandas scikit-learn rdkit如果 RDKit 安装遇到问题,推荐使用 Anaconda 安装:
conda install -c conda-forge rdkit3.2 项目结构规划
我们把代码组织成一个小型项目,便于后续扩展:
lab_aware_bench/ ├── data/ │ └── reactions_sample.csv ├── src/ │ ├── __init__.py │ ├── data_loader.py │ ├── features.py │ └── evaluate.py ├── scripts/ │ └── run_benchmark.py └── README.md其中:
- data:存放基准数据。
- src:存放加载、特征化和评估代码。
- scripts:存放入口脚本。
- README:记录任务定义和运行方式。
这个结构不是唯一的,但能帮助你把数据、逻辑和入口解耦,后续扩展新任务时不用改动太多。
4. 完整实战:构建一个实验室感知基准的最小示例
4.1 定义任务
为了不过度依赖真实化工数据,本文使用“模拟反应记录”演示基准构建流程。任务定义为:根据反应物结构信息、温度、溶剂极性、催化剂用量,预测一锅法反应的产率区间。
这是一个回归任务,但也可以转换成三分类:高产率(>70%)、中产率(30%~70%)、低产率(<30%)。通过多分类指标,我们能更直观对比不同条件组合下的模型表现。
4.2 反应数据格式设计
lab-aware 基准的数据格式,建议至少包含三部分信息:反应物结构、实验条件、结果标签。这里以 CSV 为例:
reaction_id,reactant_smiles,condition_temp,condition_solvent,condition_catalyst,condition_time,yield rxn001,CC(=O)Oc1ccccc1C(=O)O,80,toluene,DMAP,2,78.5 rxn002,CC(C)(C)OC(=O)NCc1ccccc1,60,DCM,TEA,1,85.2 rxn003,COC1=CC=C(C=C1)Br,100,DMF,Pd(PPh3)4,4,32.1这在真实项目中可以扩展为 JSON 嵌套格式,把加料顺序、压力、溶剂体积等都包含进去。但 CSV 格式最直观,适合做第一版。
4.3 生成示例数据
为了演示,我们写一个脚本生成模拟数据。注意:这里是模拟数据,目的只是让流程跑通,不代表任何真实化学实验结果。
# scripts/generate_sample_data.py import pandas as pd import numpy as np np.random.seed(42) data = [] solvents = ["toluene", "DCM", "DMF", "THF"] catalysts = ["DMAP", "TEA", "Pd(PPh3)4", "None"] for i in range(200): temp = np.random.choice([60, 80, 100, 120]) solvent = np.random.choice(solvents) catalyst = np.random.choice(catalysts) time = np.random.choice([1, 2, 4, 8]) reactant = f"R{i:03d}" # 模拟一个简单的产率公式,注意这里仅为演示 yield_val = 50 if solvent == "DMF": yield_val += 10 if catalyst == "Pd(PPh3)4": yield_val += 15 if temp > 100: yield_val -= 8 yield_val += np.random.normal(0, 5) yield_val = max(0, min(100, yield_val)) data.append({ "reaction_id": f"rxn{i:03d}", "reactant_smiles": reactant, "condition_temp": temp, "condition_solvent": solvent, "condition_catalyst": catalyst, "condition_time": time, "yield": round(yield_val, 2) }) df = pd.DataFrame(data) df.to_csv("data/reactions_sample.csv", index=False) print("数据生成完成,共", len(df), "条记录")运行方式:
python scripts/generate_sample_data.py这段代码生成了 200 条带有反应物编号、条件参数和模拟产率的记录。实际项目中,你应该从电子实验记录本(ELN)导出数据,保证反应器类型、搅拌速度等关键信息也是完整的。
4.4 数据加载与特征化
我们需要把 CSV 中的文本类条件编码成数值特征。
# src/data_loader.py import pandas as pd from sklearn.preprocessing import LabelEncoder def load_reaction_data(path): df = pd.read_csv(path) return df def encode_conditions(df): df = df.copy() solvent_encoder = LabelEncoder() catalyst_encoder = LabelEncoder() df["solvent_code"] = solvent_encoder.fit_transform(df["condition_solvent"]) df["catalyst_code"] = catalyst_encoder.fit_transform(df["condition_catalyst"]) return df, solvent_encoder, catalyst_encoder这里使用 LabelEncoder 把溶剂和催化剂名称映射成整数。对于树模型,这种编码方式够用;如果后续使用神经网络,建议改成 one-hot 编码或嵌入向量。
4.5 构建特征矩阵
我们采用经典的特征组合方式:条件参数 + 条件间交互项。虽然这个示例没有真实分子结构,但足够演示如何为模型准备输入。
# src/features.py import pandas as pd def build_feature_matrix(df): features = pd.DataFrame() features["temp"] = df["condition_temp"] features["time"] = df["condition_time"] features["solvent_code"] = df["solvent_code"] features["catalyst_code"] = df["catalyst_code"] # 增加温度和时间的交互项 features["temp_time"] = df["condition_temp"] * df["condition_time"] features["solvent_temp"] = df["condition_temp"] * df["solvent_code"] return features这个示例可以继续扩展:如果你有分子 SMILES,可以使用 RDKit 计算分子指纹、分子量、LogP 等描述符,作为反应物结构特征。
4.6 编写评估脚本
评估流程包括:数据划分、模型训练、指标计算。
# src/evaluate.py import numpy as np from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, r2_score import pandas as pd from src.data_loader import load_reaction_data, encode_conditions from src.features import build_feature_matrix def evaluate_model(data_path): df = load_reaction_data(data_path) df, _, _ = encode_conditions(df) X = build_feature_matrix(df) y = df["yield"].values X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) model = RandomForestRegressor(n_estimators=200, random_state=42) model.fit(X_train, y_train) pred_train = model.predict(X_train) pred_test = model.predict(X_test) train_mae = mean_absolute_error(y_train, pred_train) test_mae = mean_absolute_error(y_test, pred_test) r2 = r2_score(y_test, pred_test) print("训练集 MAE: {:.3f}".format(train_mae)) print("测试集 MAE: {:.3f}".format(test_mae)) print("测试集 R2: {:.3f}".format(r2)) return model这里我们选择随机森林作为基线模型。随机森林对异常值不敏感,也能捕捉部分非线性关系,是化学性质预测里很常见的 baseline。
4.7 入口脚本
最后写一个入口脚本,串联整个流程。
# scripts/run_benchmark.py import sys import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from src.evaluate import evaluate_model if __name__ == "__main__": evaluate_model("data/reactions_sample.csv")运行命令:
python scripts/run_benchmark.py4.8 预期输出
因为数据是模拟的,每次随机种子固定后输出应该稳定。类似结果如下:
训练集 MAE: 4.123 测试集 MAE: 4.876 测试集 R2: 0.512这个结果说明两点:
- 流程本身已经跑通。
- 模型在模拟数据上的预测能力有限,说明特征和任务之间关系并不简单。
真实场景中,如果测试集 R2 很低,需要重新审视特征设计、数据质量、样本量和模型选择。
5. 常见问题与排查思路
5.1 表格化排查
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| RDKit 安装失败 | Python 环境冲突 | 使用 Anaconda 创建独立环境并安装 rdkit |
| 数据读取时报错找不到文件 | 工作目录不对 | 使用绝对路径或在项目根目录运行脚本 |
| 模型 R2 很低 | 特征不足以解释产率 | 补充真实分子指纹、工艺参数,增加特征维度 |
| 交叉验证结果波动大 | 样本量过少或划分方式不合理 | 使用分层采样,或按反应类型分组划分 |
| 训练集指标好但测试集差 | 模型过拟合 | 增加正则化、减少特征、增加数据量 |
| 催化剂编码重复 | LabelEncoder 只适合训练集 | 保存 encoder 对象并在预测时复用 |
5.2 避免条件数据泄漏
在真实 lab-aware 基准中,最需要注意的问题是数据泄漏。比如:
- 把实验批次号作为特征,模型可能学到“同一批实验产率相似”,而不是学到化学规律。
- 使用全局归一化时,统计量来自全部数据,导致测试集信息提前暴露。
- 同一个反应在不同日期重复多次,直接随机划分会让同一反应同时出现在训练集和测试集。
解决办法是使用按反应模板或反应物骨架分组的 GroupKFold,保证同一底物或同一反应系列不跨组。
5.3 如何处理缺失条件
实验室记录中经常出现缺失值,例如某个催化剂用量没有记录。可以用以下方式处理:
- 删除缺失比例过高的字段。
- 使用中位数/众数填充。
- 使用 “unknown” 单独标记缺失类别,让模型自己学习缺失信息。
不建议对所有缺失条件做全局均值填充,因为某些条件下缺失可能和实验失败相关,直接填充会掩盖这种信息。
6. 最佳实践与工程建议
6.1 数据记录规范
lab-aware 基准的好坏,很大程度取决于原始实验数据的完整程度。建议从数据源头规范记录字段:
- 反应物 SMILES 必须经过标准化,例如去除盐、中和电荷。
- 记录每种试剂的当量,而不是只记录体积/质量。
- 记录加料顺序,尤其是多步一锅反应。
- 记录实际温度波动范围,而不是只记录设定温度。
- 记录设备类型和搅拌方式,这些会影响传质和结果。
- 记录失败实验,这一点容易被忽略,但对模型理解“什么条件不 work”至关重要。
6.2 版本管理与可复现性
基准数据也在不断迭代,建议采用数据版本管理:
bench_data/ ├── v0/ │ ├── reactions.csv │ ├── README.md └── v1/ ├── reactions.csv ├── condition_schema.json └── README.md每次发布新版本时,记录版本变更,例如:
- 新增了多少条反应记录。
- 修正了哪些 SMILES 错误。
- 条件字段有哪些新增或废弃。
在代码侧,固定核心依赖版本:
pip freeze > requirements.txt这样别人在复现模型结果时,环境一致,减少“明明代码一样,结果却不同”的困扰。
6.3 指标设计要多视角
产率预测不能只用一个 MAE 指标。建议在基准中同时输出多个指标:
- MAE / RMSE:评估整体误差。
- R2:评估拟合程度。
- Top-1 条件推荐命中率:判断模型是否能把最优条件排在最前面。
- 低产率识别召回率:判断模型能否识别高风险条件。
对于条件推荐任务,还可以使用 NDCG(归一化折损累计增益)评估排序质量。这比单一准确率更能反映模型在实验筛选中的实用价值。
6.4 基线模型选择
第一版基准不要一上来就上复杂模型。建议至少跑三个 baseline:
- 随机森林/梯度提升树:处理表格特征强,训练快。
- 支持向量回归(SVR):在样本量小时表现稳定。
- 图神经网络或基于 Transformer 的反应预测模型:在真实分子结构上表现更好,但实现成本高。
只有多个 baseline 都在同一数据划分和指标下比较,才能判断新方法的提升是真实效果还是评估设置不同引起的。
6.5 安全与合规注意事项
处理化学数据进行模型训练时,需要注意:
- 涉及公司内部实验室数据时,要先做好脱敏和授权。
- 涉及危险化学品类信息,避免在公开代码仓库中记录。
- 使用开源数据时,注意其许可证,不能简单“拿来就用”。
- 模型预测结果不能替代真实实验验证,尤其是涉及危险反应条件时,仍需要安全审查。
6.6 面向生产环境的扩展
在真实业务中,lab-aware 基准可以和工作流自动化结合:
- 实验平台每次运行后自动导出结构化数据。
- 数据进入特征化管道,更新特征库。
- 模型在每周固定时间重新评估。
- 当模型指标低于阈值时,触发主动学习采样,挑选信息量最大的一组实验推荐给实验人员。
- 实验人员反馈新结果,完成闭环。
这种闭环是 lab-aware 基准的最终价值:让计算模型成为实验设计的助推器,而不是停留在论文里的“离线评测”。
7. 总结与后续方向
回到 onepot-Bench 0 的定位,它代表的不只是一个新的数据集,而是一套更贴近实验事实的评估思路。我们在本文中梳理了从概念到代码实现的完整路径:理解 lab-aware 基准的意义、设计数据格式、构建特征、训练基线模型、评估结果,并讨论了数据泄漏、版本管理和指标设计等工程问题。
如果你准备在自己的研究或项目里实践这个方向,可以参考下面的路线图继续扩展:
- 使用真实实验数据替换模拟数据。
- 加入 RDKit 计算分子描述符或指纹。
- 将条件信息编码为结构化向量。
- 引入多任务学习,同时预测产率、选择性和副产物。
- 添加主动学习模块,让模型参与实验设计。
- 对不同模型和特征组合做 ablation study,形成完整报告。
下一步可以重点关注“条件扰动评估”:在测试集中修改温度、溶剂、催化剂等条件,观察模型预测是否发生合理变化。这是判断模型是否真正理解化学条件最直接的测试方式,也是 lab-aware 基准区别于传统基准的核心优势。
如果你对自动化合成、反应条件推荐或者化学数据工程感兴趣,欢迎自己动手实现一版最小 lab-aware benchmark,再从数据质量、特征工程、模型评估三个方向逐步完善。先让流程闭环,再追求模型性能,是这条路上最务实的做法。