科学智能实战:用分子性质预测跑通AI for Science完整流程

📅 发布时间:2026/8/31 12:32:59
科学智能实战:用分子性质预测跑通AI for Science完整流程
科学智能AI for Science正在从论坛上的概念热词变成需要动手解决的真实工程问题。很多技术团队和科研院所开始用实训营的方式推动这个方向落地9 月开营的磐石·科学智能实训营就是一类典型例子。这个信号值得注意科学智能不再只是“用神经网络拟合几条曲线”而是要把数据预处理、物理约束、模型训练、实验验证和结果解释串成一条完整链路。对于想进入这个方向的开发者来说真正的难点不是装一个深度学习框架而是理解科学数据长什么样、模型要学到什么规律、以及实验结果如何复现。在动手写代码之前我会用一个小型分子性质预测案例把科学智能项目的完整流程拆开讲清楚。你会看到这一类项目的环境怎么搭、代码怎么组织、指标怎么验证也会看到最容易出问题的数据泄漏、特征尺度、偶然相关等问题。整个项目用 Python 和常见机器学习库就能跑通适合作为第一个科学智能练习。1. 科学智能不是简单套用深度学习它改变的是研究闭环1.1 先理解科学计算与数据驱动的边界传统科学研究的常用路径是“猜想、建模、仿真、实验验证”。科学家先提出理论方程再用计算软件求解最后用实验数据修正参数。这个过程可靠但缺点是模型更新慢计算复杂时很难兼顾多尺度问题。科学智能做的事情是用机器学习模型去拟合那些无法轻易写成显式方程的关系。它不排斥物理方程而是在数据驱动和物理规律之间取一个结合点。比如在分子性质预测里分子结构和溶解度之间确实存在物理化学规律但规律太复杂很难用统一公式表达。机器学习可以把大量分子结构映射成特征再从实验数据里学出统计规律。所以科学智能的核心不是“有没有用深度学习”而是“能不能把科学知识的归纳和验证过程自动化”。这也决定了科学智能项目的评价标准与传统互联网推荐项目不同。推荐系统看点击率科学智能还要看预测结果是否具备物理合理性、是否能在新实验里复现。1.2 科学智能的几条主流技术路线把当前常见项目放在一起看大致能分成三类。第一类是分子和材料性质预测。输入是分子结构、晶体结构、原子坐标输出是溶解度、毒性、带隙、力学强度等性质。常用做法包括分子指纹加传统机器学习以及图神经网络、Transformer 等深度模型。这类任务最容易让初学者上手因为它和标准机器学习项目相似难点在于特征构造和实验数据质量。第二类是物理场和微分方程求解。很多物理过程可以用偏微分方程描述但方程复杂、边界条件复杂时传统求解器会很慢。神经网络算子可以学习从初始条件到解的映射训练完成后推理速度远快于数值求解。这类任务难度更高需要理解方程本身的数学含义。第三类是科学实验优化。在材料合成、药物研发里实验成本很高不能盲目尝试所有组合。算法需要根据已有实验数据推荐下一组实验参数把主动学习和贝叶斯优化用在实验设计中。这类任务讲究“少做实验、多做判断”工程上还需要和自动化实验设备对接。1.3 为什么科学智能工程的调试方向不同传统开发调试时大多数问题集中在接口返回、并发、数据库状态。科学智能项目里问题往往藏在数据和特征层。例如模型预测结果很好但换一批分子就不行可能是训练数据分布覆盖面不够模型损失下降很慢可能是特征没有做归一化测试集分数异常高可能是同一批分子在切分时泄漏到了训练集。这些问题的排查路径和普通 Web 服务完全不同也更依赖实验记录和版本管理。做科学智能的第一步不是急着学某个最新模型而是先建立一套能记录数据版本、特征版本、模型版本和实验指标的工程习惯。后面的代码示例也会围绕这个习惯展开。2. 搭建一个可复现的科学 AI 环境再开始写模型2.1 最小运行环境要求科学智能项目依赖较多建议先用虚拟环境隔离不要直接装在系统 Python 里。下面是我常用的环境组合。项目推荐配置备注操作系统Ubuntu 20.04/22.04macOS 也可Windows 需要额外处理 RDKit 依赖Python3.9 或 3.10部分科学计算库对 3.11 支持滞后包管理conda 或 venv pipRDKit 建议用 conda 安装GPU可选先不依赖小规模案例用 CPU 足够CUDA11.7 以上只在需要训练 GNN 时准备如果电脑上已经有 conda创建环境使用下面命令。conda create -n ai4s python3.9 -y conda activate ai4s2.2 安装依赖RDKit、scikit-learn、pandas分子性质预测需要 RDKit 处理分子结构。RDKit 安装比较特殊直接pip install rdkit在某些环境会失败建议用 conda 安装。conda install -c conda-forge rdkit -y pip install pandas scikit-learn numpy如果后续要扩展到深度模型再安装 PyTorch。pip install torch --index-url https://download.pytorch.org/whl/cpu这里重点解释一下 RDKit 的作用。RDKit 能把 SMILES 字符串解析成真正的分子对象然后计算分子量、脂水分配系数、氢键供体数量等描述符。这些描述符是模型输入的重要特征比直接把字符塞给模型要可靠得多。2.3 项目目录结构科学计算项目很容易变成一堆不可复现的脚本建议从第一个项目开始就按层次分目录。ai4s-solubility/ ├── data/ # 原始数据尽量只读 ├── notebooks/ # 探索性分析 ├── src/ # 正式代码 │ ├── features.py # 特征工程 │ ├── model.py # 模型训练和评估 │ └── inference.py # 推理脚本 ├── models/ # 模型产物 ├── results/ # 指标和图表 ├── requirements.txt └── README.md这里的关键约束是data目录只存放原始数据所有中间结果都写到results预防不小心修改原数据。真实研究中这个目录还会增加configs保存实验配置。2.4 环境检查清单进入正式实现前先检查环境是否满足下面条件。python --version输出 3.9 或 3.10。conda list | grep rdkit能看到 RDKit 版本。python -c from rdkit import Chem; print(Chem.MolFromSmiles(CCO))输出一个分子对象而不是报错。python -c import sklearn; print(sklearn.__version__)正常输出版本号。这些检查只需要一分钟但能避免后面把时间浪费在依赖冲突上。如果环境反复装不上 RDKit优先检查 conda 源和 Python 版本。3. 用最小案例跑通分子溶解度预测闭环3.1 案例目标从分子结构预测水溶解度在药物研发和材料筛选里水溶解度影响化合物的吸收、分布和实验方案设计。这个任务非常适合作为科学智能入门案例因为数据可以表示成 SMILES 字符串特征可以用 RDKit 计算模型也可以从最简单的随机森林开始。这个案例的目标是输入一个分子结构输出预测的溶解度数值。为了跑通流程我先造一个演示数据集实际项目要换成经过实验验证的公开数据集或自有数据。import random import pandas as pd from rdkit import Chem random.seed(42) smi_pool [ CCO, CCN, CCC, CCCC, c1ccccc1, CC(O)O, CCOC, CC(C)C, c1ccccc1O, CCCl ] rows [] for i in range(200): smi random.choice(smi_pool) mol Chem.MolFromSmiles(smi) if mol is None: continue logp Chem.Descriptors.MolLogP(mol) # 这里是演示数据生成逻辑不是真实实验值 sol -1.5 * logp random.uniform(-0.5, 0.5) rows.append({smiles: smi, solubility: round(sol, 3)}) df pd.DataFrame(rows) df.to_csv(data/solubility_demo.csv, indexFalse)这段演示代码生成 200 条包含smiles和solubility两列的记录。注意溶解度是模拟值只是为了演示完整链路不能拿去做真实科研判断。拿到真实数据集以后只需保证 CSV 里保留这两列后续代码可以复用。3.2 数据加载和描述符计算从 CSV 加载数据后下一步是把 SMILES 转成 RDKit 分子对象再计算分子描述符。import pandas as pd from rdkit import Chem from rdkit.Chem import Descriptors from rdkit.ML.Descriptors import MoleculeDescriptors df pd.read_csv(data/solubility_demo.csv) descriptor_names [MolWt, MolLogP, NumHDonors, NumHAcceptors, TPSA] calculator MoleculeDescriptors.MolecularDescriptorCalculator(descriptor_names) def smiles_to_features(smiles): mol Chem.MolFromSmiles(smiles) if mol is None: return None values calculator.CalcDescriptors(mol) return list(values) feature_list [] valid_idx [] for i, row in df.iterrows(): feats smiles_to_features(row[smiles]) if feats is not None: feature_list.append(feats) valid_idx.append(i) X pd.DataFrame(feature_list, columnsdescriptor_names) y df.loc[valid_idx, solubility]这里选择 5 个描述符作为演示特征原因有两个一是它们和分子疏水性、分子大小、氢键能力直接相关对溶解度有较强解释力二是数量少方便排查特征问题。真实项目里可以根据领域知识增加描述符或改用分子指纹。3.3 切分训练集和测试集关键是不能随机采样分子性质预测里数据切分需要特别小心。如果只是随机切分同一个分子的不同记录可能同时出现在训练集和测试集导致测试指标虚高。更贴近真实应用的做法是使用 ScaffoldSplitter按分子骨架切分。from rdkit import Chem from rdkit.Chem.Scaffolds import MurckoScaffold from sklearn.model_selection import train_test_split scaffolds [] smiles_list df.loc[valid_idx, smiles].tolist() for smi in smiles_list: mol Chem.MolFromSmiles(smi) scaffold MurckoScaffold.MurckoScaffoldSmiles(molmol) scaffolds.append(scaffold) scaffold_df pd.DataFrame({scaffold: scaffolds}) train_idx, test_idx train_test_split( range(len(scaffold_df)), test_size0.2, stratifyscaffold_df[scaffold], random_state42 ) X_train, X_test X.iloc[train_idx], X.iloc[test_idx] y_train, y_test y.iloc[train_idx], y.iloc[test_idx]这段代码先把分子转成骨架再按骨架做分层切分保证训练集和测试集里的分子骨架不重叠。这样验证结果更能反映模型遇到新结构时的表现。3.4 训练随机森林回归模型特征准备好以后模型部分可以用 scikit-learn 的随机森林。随机森林对特征尺度不敏感适合作为科学智能项目的基线模型。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score model RandomForestRegressor( n_estimators200, max_depth10, min_samples_leaf2, random_state42 ) model.fit(X_train, y_train) y_pred model.predict(X_test) print(R2:, r2_score(y_test, y_pred)) print(MAE:, mean_absolute_error(y_test, y_pred)) print(RMSE:, mean_squared_error(y_test, y_pred, squaredFalse))训练完成后输出三个指标。R2 表示模型解释了多少方差MAE 表示平均绝对误差RMSE 表示大误差样本对结果的惩罚。只看 R2 不够还要结合 MAE 和 RMSE 判断模型是否会在大误差样本上表现很差。3.5 保存模型和推理脚本训练完成后把模型和特征列名保存下来方便后续推理。import joblib joblib.dump(model, models/solubility_rf.joblib) joblib.dump(descriptor_names, models/descriptor_names.joblib)推理脚本读取模型输入 SMILES输出预测值。import joblib from rdkit.Chem import Descriptors from rdkit.ML.Descriptors import MoleculeDescriptors model joblib.load(models/solubility_rf.joblib) descriptor_names joblib.load(models/descriptor_names.joblib) calculator MoleculeDescriptors.MolecularDescriptorCalculator(descriptor_names) def predict_solubility(smiles): mol Chem.MolFromSmiles(smiles) if mol is None: raise ValueError(Invalid SMILES) descs calculator.CalcDescriptors(mol) pred model.predict([list(descs)])[0] return pred print(predict_solubility(CCO))到这里一个最小科学智能项目已经形成闭环原始分子结构进入系统模型输出性质预测。这个闭环虽然简单却是后面所有复杂方案的地基。4. 运行验证不是只看分数要回到任务理解结果4.1 训练时的预期输出运行上面对应脚本后应该看到类似下面的输出实际数值会因为演示数据和随机种子略有不同。R2: 0.87 MAE: 0.31 RMSE: 0.42如果脚本报错先对照环境检查清单确认 RDKit 和 scikit-learn 是否安装成功。如果 R2 为负值说明模型比直接取平均值还差需要检查特征构造和标签方向。4.2 从指标判断模型状态科学智能项目里指标要结合任务背景解读。R2 在 0.8 以上对这个演示任务算不错但真实实验数据通常噪声更大R2 反而可能只有 0.5 到 0.7。这不一定代表代码写错也可能是实验误差和特征信息量不足。MAE 和 RMSE 的差距也能说明问题。两者接近说明误差分布均匀RMSE 明显大于 MAE说明存在少数预测误差很大的样本。遇到这种情况要回到训练数据里找这些样本检查是否存在分子结构特殊或标签记录异常的情况。4.3 模型推理结果是否合理把训练集里出现过的分子和新分子分别输入推理脚本检查输出。for smi in [CCO, CCCC, c1ccccc1, OC(O)C]: print(smi, predict_solubility(smi))预期结果是碳链越长、脂溶性越强的分子溶解度数值越低带羟基、羧基的分子溶解度更高。如果推理结果明显违背化学常识那么特征计算或训练数据大概率有问题。4.4 参数调整会影响什么随机森林里有几个参数对科学智能项目影响较大整理成表格便于对照。参数默认值调大影响调小影响适用场景n_estimators100模型更稳定但训练更慢训练快易波动数据量大时 300 左右足够max_depthNone容易过拟合小数据偏差变大小数据限制为 5 到 15min_samples_leaf1叶子样本多更稳健过拟合数据量小时设为 2 到 5max_features1.0每棵树更相似树之间差异大特征多时设为 sqrt科学智能里没有一套万能参数更实际的做法是先把默认参数跑通再用交叉验证对比参数组合。不要一开始就追求复杂模型基线模型的结果就是后续优化的参照线。5. 科学智能项目中最常见的坑和排查路径5.1 数据泄漏测试集里混入了训练集信息这是科学智能项目最隐蔽的问题。现象是训练时 R2 很高线下评估也很高但一到实际新样本预测就崩。检查方式有几个。第一查看训练集和测试集的 SMILES 是否有交集。第二检查切分方式是否按分子骨架而不是普通随机切分。第三检查特征里是否包含实验条件等测试时拿不到的信息。解决方法是统一使用基于分子结构的切分策略并且在数据预处理阶段就把同一分子的所有记录放进同一集合。这种问题一旦出现后面的模型再复杂都没有意义。5.2 RDKit 解析大量 SMILES 时静默失败RDKit 遇到非法 SMILES 时MolFromSmiles会返回 None而不是抛出异常。很多初学者继续往下计算特征结果矩阵里混进空值。def smiles_to_features_safe(smiles): mol Chem.MolFromSmiles(smiles) if mol is None: raise ValueError(fUnable to parse SMILES: {smiles}) return list(calculator.CalcDescriptors(mol))在预处理阶段应该主动拦截非法分子记录原始 SMILES排查是否来自数据清洗遗漏。5.3 小数据集过拟合科学实验数据量通常很小几百条到几千条都很常见。模型很容易在训练集上记住个别样本测试集分数很高但真实预测不稳。处理方式是限制模型复杂度、增加交叉验证、使用简单模型作为基线。随机森林、线性回归在这种场景下往往比深度模型更实用。等基线稳定后再尝试图神经网络。5.4 结果无法复现实验做完同事却复现不出同样的分数常见原因是随机种子没有固定、依赖版本不一致、数据切分顺序不同。训练脚本里要固定 Python 的random、NumPy 和 scikit-learn 的随机种子。import random import numpy as np random.seed(42) np.random.seed(42)同时把依赖版本写入requirements.txt把数据哈希记录到实验配置文件里。科学计算的复现要求比普通项目高得多这一点要尽早养成习惯。5.5 分子指纹维度过大导致内存问题真实项目中经常用 RDKit 的摩根指纹替代少量描述符维度可能到 2048 或 4096。这时数据量一大稀疏矩阵直接转成密集矩阵会让内存暴涨。处理方案是使用稀疏格式。from rdkit.Chem import AllChem def mol_to_fingerprint(smiles): mol Chem.MolFromSmiles(smiles) if mol is None: return None fp AllChem.GetMorganFingerprintAsBitVect(mol, radius2, nBits1024) return fp这类指纹向量只有在位为 1 的位置才参与计算保存和读取时都要注意保持稀疏结构。下面把常见问题整理成一张排错速查表。现象常见原因检查方式处理建议测试分数很高但真实预测差数据泄漏检查 SMILES 交集和切分方式改用骨架切分特征去除实验条件训练 loss 正常测试 loss 高过拟合查看训练和测试指标差距减小模型深度增加数据量某些分子特征为空SMILES 非法打印MolFromSmiles返回值清洗数据记录原始 SMILES换机器后结果不同随机种子或版本不一致检查依赖版本固定 seed锁定 requirements程序内存溢出指纹维度或批量过大查看数据大小使用稀疏矩阵减小 nBits6. 从最小闭环走向真实科学工作流的建议6.1 扩展数据流水线演示项目只用了 5 个描述符和 200 条数据真实场景里数据规模、来源、质量都要复杂得多。建议从三个方面扩展。一是数据标准化。SMILES 字符串有多种写法同一分子可以表示成不同顺序要使用 RDKit 的标准化函数统一分子表示。二是数据关联。科学数据经常分散在不同实验记录里建模前要明确样本的唯一标识防止不同来源的数据错位。三是数据版本管理。原始数据一旦更新历史实验结果就要重新评估最好用 DVC 或简单哈希记录每次数据变更。6.2 增加模型可解释性和不确定性评估科学智能的落地不只看预测精度还看研究人员是否敢用预测结果做决策。这个环节建议关注两方面。可解释性可以用 SHAP 值分析特征贡献看模型是否依赖了有物理意义的特征。如果模型只靠一个过拟合噪声特征说明数据或特征选择有问题。不确定性估计也很重要模型应该知道自己对陌生分子不太确定。简单做法是使用随机森林每棵树的预测方差更复杂做法是使用贝叶斯神经网络或集成方法。6.3 与实验闭环结合科学研究对模型的期望不是“替代实验”而是“减少实验次数”。实践中可以把模型嵌入主动学习循环先训练初版模型用模型挑出最不确定且最可能有价值的候选样本再让实验人员验证把新数据加回训练集。这个闭环对工程的要求是接口清晰。模型输入输出要封装成函数实验记录要能自动回填数据库。演示项目里的predict_solubility(smiles)就是一个最小接口扩展成远程服务时同样要保持这种函数式语义。6.4 工程化最佳实践清单从最小项目走向真实项目时下面这份清单可以直接使用。数据文件只读所有中间结果写入独立目录。SMILES 解析失败时主动报错不静默跳过。模型训练前先固定全局随机种子。用骨架切分替代普通随机切分避免数据泄漏。记录每个实验的数据版本、依赖版本、模型参数和指标。保存推理脚本和特征列名避免上线时特征顺序不一致。用 SHAP 或特征重要性检查模型依据。基线模型使用简单算法深度模型作为后续优化方向。所有结果保存到带时间和实验编号的目录不要覆盖历史实验。上线前用一组人工标注的关键样本做回归测试。科学智能的学习路线和传统软件工程不太一样。真正值钱的不是背熟某个模型的 API而是能在数据、特征、模型、验证之间建立可追踪的闭环。把一个最小分子性质预测项目从头到尾跑通、记录清楚、排查出问题比起一次性堆很多高级模型更容易帮你建立起科学智能项目的全局观。后面再进入图神经网络、物理约束建模、实验优化时就可以沿着这套闭环逐层扩展。