公司动态
AI化学家实战:数学建模竞赛中的机器学习与化学信息学应用
1. 项目概述当数学建模遇上AI化学家最近刚带着学生打完第四届长三角高校数学建模竞赛赛道B这个“人工智能范式的物理化学家”的题目着实让我们团队兴奋了一把。这题目听起来很前沿把人工智能AI、物理化学和数学建模这三个硬核领域拧在了一起本质上就是让我们用数据和算法去模拟甚至部分替代传统物理化学研究中那些依赖专家经验和复杂理论计算的工作。简单说给你一堆化学物质的数据可能是光谱、可能是分子结构、可能是反应条件让你构建一个AI模型去预测物质的性质、识别反应路径、或者设计新的分子。这不仅是数学建模更是交叉学科研究的一次实战演练。对于参加数模竞赛的同学尤其是对化学、材料、计算机交叉方向感兴趣的这道题的价值远超一次比赛。它逼着你去理解一个陌生的专业领域物理化学并将其抽象成数学问题和可计算的模型最后用Matlab或Python这样的工具实现出来。整个过程从数据清洗、特征工程、模型选择到结果分析每一步都充满了挑战和学问。我猜很多队伍看到“物理化学家”这个头衔就有点发怵觉得专业壁垒太高。别怕这道题的核心不是让你成为化学专家而是考察你如何运用数学和AI工具解决一个特定领域的复杂问题——这正是现代科研的常态。接下来我会结合我们团队的解题过程把这道题的核心思路、模型构建的详细步骤、Matlab/Python双代码实现以及我们踩过的坑和总结的技巧毫无保留地分享出来。无论你是参赛者想找思路还是对AI在科学计算中的应用感兴趣这篇文章都能给你提供一份可直接“抄作业”的实战指南。2. 赛题核心剖析与解题总纲拿到题目“人工智能范式的物理化学家”第一步不是急着写代码而是深度拆解题目明确我们要扮演的“AI化学家”到底要干什么。通常这类题目会提供一份数据集可能包含输入X分子描述符如分子量、极性、各种键的数量、实验条件温度、压力、浓度、光谱数据红外、拉曼光谱的峰值和强度或更抽象的表示如分子指纹、SMILES字符串。输出y待预测的目标性质比如溶解度、沸点、反应产率、毒性、催化活性等。我们的核心任务就是建立一个映射函数 f: X - y并且这个f要足够“智能”能捕捉复杂的非线性关系。解题总纲可以概括为以下四个阶段2.1 第一阶段问题定义与数据理解这是最重要也最容易被忽视的一步。你需要像侦探一样审视数据。确定任务类型是回归预测连续值如沸点、分类预测类别如是否有毒、还是聚类发现物质分组题目会明确指示。数据探索性分析EDA用统计和可视化手段“感受”数据。查看特征分布直方图、缺失值情况、特征与目标的相关性热力图。例如如果发现“分子量”和“沸点”的散点图呈明显正相关那这个特征很可能非常重要。评估指标确认题目会规定如何评价你的模型。回归常用均方误差MSE、均方根误差RMSE、决定系数R²分类常用准确率、精确率、召回率、F1-score。你的所有模型优化都要围绕提升这些指标进行。2.2 第二阶段特征工程与数据预处理数据和特征决定了机器学习的上限模型和算法只是逼近这个上限。这一步是拉开差距的关键。处理缺失值对于少量缺失可以用均值、中位数或众数填充对于缺失较多的特征可以考虑直接删除或使用模型如KNN进行预测填充。处理异常值使用箱线图或3σ原则识别异常值。对于回归问题异常值对MSE影响巨大需谨慎处理剔除或缩尾。特征编码如果数据中有类别型变量如溶剂类型“水”、“乙醇”必须进行编码如独热编码One-Hot Encoding。特征缩放很多模型如SVM、神经网络、KNN对特征尺度敏感。必须进行标准化StandardScaler使均值为0方差为1或归一化MinMaxScaler缩放到[0,1]区间。特征构建与选择这是“化学家”智慧的体现。例如你可以根据化学知识从原子组成计算“不饱和度”从结构式估算“极性表面积”。还可以使用统计方法如方差过滤、相关性过滤或模型方法如基于树模型的特征重要性、递归特征消除RFE来选择最有用的特征子集避免维度灾难。2.3 第三阶段模型选择、训练与验证这是AI范式的核心。不要只用一个模型要构建模型梯队。基准模型首先建立简单的线性回归或逻辑回归作为基准。它的性能是底线。传统机器学习模型尝试决策树、随机森林、梯度提升树如XGBoost、LightGBM、支持向量机SVM。这些模型通常能提供不错的性能且可解释性相对较好。深度学习模型如果数据量足够大或者数据本身是序列如SMILES字符串或图分子结构可以尝试深度学习。例如用循环神经网络RNN处理SMILES用图神经网络GNN处理分子图。这是“AI化学家”最前沿的武器但复杂度高训练时间长。模型验证绝对禁止用全部数据训练后直接在测试集上评估必须使用交叉验证如5折或10折交叉验证来稳健地评估模型性能防止过拟合。2.4 第四阶段模型集成、优化与解释单一模型可能已足够好但集成往往能进一步提升。模型集成将多个模型的预测结果进行平均回归或投票分类即软投票或硬投票。例如将随机森林、XGBoost和神经网络的预测结果取平均。超参数优化使用网格搜索Grid Search或随机搜索Random Search来寻找模型的最佳参数组合。更高效的方法是使用贝叶斯优化工具如Optuna。模型解释作为“物理化学家”你不能只给出黑箱预测。使用SHAP、LIME等工具解释模型告诉人们是哪些特征比如“氢键供体数量”对预测结果比如“溶解度”影响最大。这能极大地提升论文的理论深度和说服力。3. 核心工具链Matlab与Python的战术分工在实战中我们团队采用了Matlab和Python混编的策略充分发挥两者优势。这不是炫技而是基于效率的务实选择。3.1 Matlab快速原型与稳健计算的利器Matlab在矩阵运算、可视化以及某些特定工具箱如统计与机器学习工具箱、曲线拟合工具箱上具有天然优势特别适合在思路探索阶段快速验证想法。数据可视化绘制精美的二维/三维散点图、相关性热力图、模型性能对比图Matlab的绘图函数scatter,heatmap,plot非常直观高效。传统统计建模进行主成分分析PCA降维、拟合多元线性回归模型Matlab的代码简洁明了。信号/光谱处理如果数据涉及光谱Matlab的信号处理工具箱是神器用于峰值查找、基线校正、平滑去噪等。注意Matlab的深度学习工具箱虽然强大但在自定义复杂网络结构和与最新研究接轨方面灵活性不如Python的PyTorch/TensorFlow。我们主要用Matlab做前期EDA和传统模型基线。3.2 Python机器学习与深度学习的主力军Python凭借其丰富的库生态Scikit-learn, XGBoost, PyTorch, RDKit成为构建核心AI模型的不二之选。数据预处理Pandas用于数据加载、清洗和操作比Matlab的表格操作更灵活。机器学习流水线Scikit-learn提供了从特征缩放StandardScaler、特征选择SelectKBest、到模型训练RandomForestRegressor、交叉验证cross_val_score的完整、一致的API极易构建可复现的流水线。高级模型与调优使用XGBoost或LightGBM这类高性能梯度提升库并配合Optuna进行超参数优化是获得高分的常见路径。化学信息学RDKit库是处理分子数据的标准工具可以从SMILES字符串生成分子对象、计算分子描述符、绘制分子结构式是连接化学与AI的桥梁。深度学习使用PyTorch Geometric或DGL库来搭建图神经网络处理分子图数据这是解决本题最前沿的方法。我们的分工策略是用Matlab快速完成数据初探和可视化将结论和分析图用于论文用Python构建核心的机器学习/深度学习模型流水线进行严格的训练和评估最终将Python得到的最优模型参数和预测结果再导入Matlab进行最终的可视化呈现和报告整合。这样既能保证模型先进性又能产出高质量的论文图表。4. 实战代码详解从数据到预测的完整流水线假设我们拿到一个数据集目标是预测化合物的沸点回归问题。数据包含一些分子描述符和实验条件。下面我将用Python代码展示核心流程并穿插说明Matlab的对应实现。4.1 数据加载与探索性分析EDA# Python (使用 pandas, matplotlib, seaborn) import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 1. 加载数据 df pd.read_csv(compound_data.csv) print(df.head()) print(df.info()) print(df.describe()) # 2. 检查缺失值 missing df.isnull().sum() print(缺失值统计:\n, missing[missing 0]) # 3. 可视化特征分布及与目标的关系 fig, axes plt.subplots(2, 3, figsize(15, 10)) features_to_plot [Molecular_Weight, Polar_Surface_Area, Num_H_Donors, Temperature, Concentration, LogP] target Boiling_Point for i, feat in enumerate(features_to_plot): ax axes[i//3, i%3] ax.scatter(df[feat], df[target], alpha0.5) ax.set_xlabel(feat) ax.set_ylabel(target) # 尝试添加趋势线 try: z np.polyfit(df[feat].dropna(), df[target].dropna(), 1) p np.poly1d(z) ax.plot(df[feat].sort_values(), p(df[feat].sort_values()), r--, lw1) except: pass plt.tight_layout() plt.show() # 4. 相关性热力图 plt.figure(figsize(12, 8)) # 计算数值型特征的相关性矩阵 numeric_df df.select_dtypes(include[np.number]) corr_matrix numeric_df.corr() sns.heatmap(corr_matrix, annotTrue, fmt.2f, cmapcoolwarm, center0) plt.title(Feature Correlation Heatmap) plt.show()% Matlab 对应部分 (数据探索与可视化) % 1. 加载数据 data readtable(compound_data.csv); head(data) summary(data) % 2. 检查缺失值 (Matlab table中缺失通常为NaN) missing_sum sum(ismissing(data)); disp(Features with missing values:); disp(missing_sum(missing_sum 0)); % 3. 绘制散点图与趋势线 figure(Position, [100, 100, 1200, 800]); features {Molecular_Weight, Polar_Surface_Area, Num_H_Donors, Temperature, Concentration, LogP}; target Boiling_Point; for i 1:length(features) subplot(2, 3, i); scatter(data.(features{i}), data.(target), filled, MarkerFaceAlpha, 0.5); xlabel(features{i}); ylabel(target); grid on; % 添加线性拟合线 hold on; valid_idx ~isnan(data.(features{i})) ~isnan(data.(target)); p polyfit(data.(features{i})(valid_idx), data.(target)(valid_idx), 1); x_fit linspace(min(data.(features{i})(valid_idx)), max(data.(features{i})(valid_idx)), 100); y_fit polyval(p, x_fit); plot(x_fit, y_fit, r--, LineWidth, 1.5); hold off; end sgtitle(Feature vs Target Scatter Plots with Linear Fit); % 4. 相关性热力图 (需要Statistics and Machine Learning Toolbox) figure; corr_matrix corr(table2array(data(:, ismember(data.Properties.VariableNames, features))), Rows, pairwise); heatmap(features, features, corr_matrix, Colormap, parula, ColorLimits, [-1 1], Title, Feature Correlation Heatmap);实操心得EDA阶段一定要花时间。我们曾在一个数据集里发现两个特征高度共线性相关系数0.95如果不处理会导致模型不稳定。通过热力图和散点图我们果断移除了其中一个模型效果反而提升了。4.2 数据预处理与特征工程流水线# Python (使用 sklearn) from sklearn.model_selection import train_test_split from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline # 假设数据集包含数值型和分类型特征 # 定义特征和标签 X df.drop(columns[Boiling_Point]) y df[Boiling_Point] # 划分训练集和测试集 (注意先划分再在训练集上拟合预处理器避免数据泄露) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 识别数值型和分类型列 numeric_features X.select_dtypes(include[int64, float64]).columns.tolist() categorical_features X.select_dtypes(include[object]).columns.tolist() # 构建预处理流水线 numeric_transformer Pipeline(steps[ (imputer, SimpleImputer(strategymedian)), # 用中位数填充缺失值 (scaler, StandardScaler()) # 标准化 ]) categorical_transformer Pipeline(steps[ (imputer, SimpleImputer(strategyconstant, fill_valuemissing)), # 类别型缺失填‘missing’ (onehot, OneHotEncoder(handle_unknownignore, sparse_outputFalse)) # 独热编码 ]) # 组合变换器 preprocessor ColumnTransformer( transformers[ (num, numeric_transformer, numeric_features), (cat, categorical_transformer, categorical_features) ]) # 在训练集上拟合预处理流水线 X_train_processed preprocessor.fit_transform(X_train) # 用拟合好的流水线变换测试集 X_test_processed preprocessor.transform(X_test) print(f原始训练集形状: {X_train.shape}) print(f处理后训练集形状: {X_train_processed.shape})4.3 构建与评估机器学习模型# Python (使用 sklearn, xgboost) from sklearn.linear_model import LinearRegression, Ridge from sklearn.ensemble import RandomForestRegressor from xgboost import XGBRegressor from sklearn.metrics import mean_squared_error, r2_score from sklearn.model_selection import cross_val_score, KFold # 1. 定义模型列表 models { Linear Regression: LinearRegression(), Ridge Regression: Ridge(alpha1.0), Random Forest: RandomForestRegressor(n_estimators100, random_state42, n_jobs-1), XGBoost: XGBRegressor(n_estimators100, learning_rate0.1, random_state42, n_jobs-1) } # 2. 使用5折交叉验证评估模型 kf KFold(n_splits5, shuffleTrue, random_state42) results {} for name, model in models.items(): cv_scores cross_val_score(model, X_train_processed, y_train, cvkf, scoringneg_root_mean_squared_error) results[name] { RMSE_mean: -cv_scores.mean(), RMSE_std: cv_scores.std(), cv_scores: -cv_scores } print(f{name:20s} | CV平均RMSE: {-cv_scores.mean():.2f} (±{cv_scores.std():.2f})) # 3. 选择表现最好的模型在完整训练集上训练并在测试集上最终评估 best_model_name min(results, keylambda x: results[x][RMSE_mean]) print(f\n选择的最佳模型是: {best_model_name}) best_model models[best_model_name] best_model.fit(X_train_processed, y_train) y_train_pred best_model.predict(X_train_processed) y_test_pred best_model.predict(X_test_processed) train_rmse np.sqrt(mean_squared_error(y_train, y_train_pred)) test_rmse np.sqrt(mean_squared_error(y_test, y_test_pred)) train_r2 r2_score(y_train, y_train_pred) test_r2 r2_score(y_test, y_test_pred) print(f训练集 RMSE: {train_rmse:.2f}, R²: {train_r2:.4f}) print(f测试集 RMSE: {test_rmse:.2f}, R²: {test_r2:.4f}) # 4. 特征重要性分析 (对于树模型) if hasattr(best_model, feature_importances_): importances best_model.feature_importances_ # 获取特征名称 (需要处理独热编码后的特征名) # 注意ColumnTransformer处理后特征名需要从变换器中提取 feature_names numeric_features.copy() # 处理独热编码产生的特征名 if len(categorical_features) 0: ohe preprocessor.named_transformers_[cat].named_steps[onehot] cat_feature_names ohe.get_feature_names_out(categorical_features) feature_names.extend(cat_feature_names) # 确保长度匹配 if len(feature_names) len(importances): feat_imp_df pd.DataFrame({feature: feature_names, importance: importances}) feat_imp_df feat_imp_df.sort_values(importance, ascendingFalse).head(15) plt.figure(figsize(10,6)) sns.barplot(ximportance, yfeature, datafeat_imp_df) plt.title(Top 15 Feature Importances) plt.tight_layout() plt.show()4.4 模型集成与超参数优化示例# Python (模型集成与超参数优化) from sklearn.ensemble import VotingRegressor from sklearn.model_selection import RandomizedSearchCV # 1. 模型集成 (软投票) ensemble_model VotingRegressor( estimators[ (rf, RandomForestRegressor(n_estimators100, random_state42)), (xgb, XGBRegressor(n_estimators100, random_state42, verbosity0)) ] ) ensemble_model.fit(X_train_processed, y_train) y_test_pred_ensemble ensemble_model.predict(X_test_processed) ensemble_rmse np.sqrt(mean_squared_error(y_test, y_test_pred_ensemble)) print(f集成模型 (RFXGB) 测试集 RMSE: {ensemble_rmse:.2f}) # 2. 超参数优化 (以XGBoost为例) xgb_model XGBRegressor(random_state42, verbosity0) param_dist { n_estimators: [100, 200, 300], max_depth: [3, 5, 7, 10], learning_rate: [0.01, 0.05, 0.1, 0.2], subsample: [0.8, 0.9, 1.0], colsample_bytree: [0.8, 0.9, 1.0] } random_search RandomizedSearchCV( estimatorxgb_model, param_distributionsparam_dist, n_iter50, # 随机搜索50组参数 scoringneg_root_mean_squared_error, cv3, verbose1, n_jobs-1, random_state42 ) random_search.fit(X_train_processed, y_train) print(f最佳参数: {random_search.best_params_}) print(f最佳交叉验证RMSE: {-random_search.best_score_:.2f}) # 用最佳参数模型在测试集上评估 best_xgb random_search.best_estimator_ y_test_pred_best best_xgb.predict(X_test_processed) best_rmse np.sqrt(mean_squared_error(y_test, y_test_pred_best)) print(f优化后XGBoost测试集 RMSE: {best_rmse:.2f})5. 高级进阶当数据是分子结构时使用RDKit与图神经网络如果题目提供的是分子的SMILES字符串那么特征工程就进入了化学信息学领域。这里简要介绍如何使用RDKit计算分子描述符以及图神经网络GNN的思路。5.1 使用RDKit计算分子描述符# Python (需要安装rdkit: pip install rdkit-pypi) from rdkit import Chem from rdkit.Chem import Descriptors, rdMolDescriptors import pandas as pd def smiles_to_descriptors(smiles_list): 将SMILES列表转换为分子描述符DataFrame desc_list [] for smi in smiles_list: mol Chem.MolFromSmiles(smi) if mol is not None: # 计算一系列描述符 desc { MolWt: Descriptors.MolWt(mol), LogP: Descriptors.MolLogP(mol), NumHDonors: Descriptors.NumHDonors(mol), NumHAcceptors: Descriptors.NumHAcceptors(mol), TPSA: Descriptors.TPSA(mol), # 极性表面积 NumRotatableBonds: Descriptors.NumRotatableBonds(mol), RingCount: rdMolDescriptors.CalcNumRings(mol), # 可以添加更多描述符... } desc_list.append(desc) else: # 处理无效SMILES desc_list.append({k: np.nan for k in desc.keys()}) # 用NaN填充 return pd.DataFrame(desc_list) # 假设df中有一列叫SMILES df[SMILES] df[SMILES].astype(str) # 确保是字符串 descriptor_df smiles_to_descriptors(df[SMILES].tolist()) # 将描述符合并到原数据框 df_enhanced pd.concat([df.reset_index(dropTrue), descriptor_df.reset_index(dropTrue)], axis1) print(df_enhanced.head())5.2 图神经网络GNN概念与流程如果数据允许且队伍实力较强使用GNN是极大的加分项。分子天然可以用图表示原子是节点化学键是边。处理流程如下图表示使用RDKit将每个SMILES转换为分子图提取原子特征如原子类型、杂化、电荷等和键特征如键类型、是否共轭等。构建数据集使用PyTorch Geometric或Deep Graph Library (DGL) 将分子图数据构建成图数据集。定义GNN模型通常采用消息传递神经网络MPNN如GCN、GAT、GraphSAGE等层来聚合邻居信息。图池化与读出通过全局池化如全局平均池化将整个图的节点特征聚合为一个图级表示向量。预测将图级表示向量输入全连接层预测目标性质如沸点。注意事项GNN对数据量要求较高且训练和调参更复杂。在数模竞赛有限的时间内除非有现成代码框架和充足GPU资源否则建议以传统机器学习模型XGBoost等为主将GNN作为创新点进行简要阐述和尝试而非主力模型。6. 论文写作与结果呈现要点数学建模竞赛模型和代码只占一半另一半是论文写作。如何将你的“AI化学家”工作清晰、有说服力地呈现出来6.1 论文结构建议问题重述与分析用自己语言解读题目明确任务类型、输入输出、评价指标。模型假设与符号说明列出合理的假设定义文中用到的主要数学符号。数据分析与预处理展示EDA的结果相关性热力图、分布图说明数据清洗、特征工程的具体步骤和理由。这是体现你工作细致程度的关键。模型建立分小节介绍你尝试的模型线性模型、树模型、集成模型等包括原理简述、模型选择理由。对于核心模型可以给出数学公式或流程图。模型求解与结果分析实验设置说明数据划分比例、交叉验证策略、超参数寻优方法。结果对比用表格清晰对比不同模型在交叉验证和测试集上的性能指标RMSE, R²等。可视化绘制真实值 vs 预测值的散点图最好带对角线。绘制特征重要性图。这些图用Matlab或Python的seaborn/matplotlib制作务必精美、清晰。结果分析分析哪个模型最好为什么好特征重要性告诉了我们什么化学洞见例如“模型发现极性表面积对沸点预测贡献最大这与物理化学中的极性相互作用理论相符”。模型评价与推广讨论模型的优缺点、可能的过拟合问题、在实际化学研究中的应用前景。参考文献与附录引用使用的关键算法和工具包。核心代码可以放在附录。6.2 可视化技巧模型性能对比使用分组柱状图比较不同模型的RMSE并加上误差棒表示交叉验证的标准差。预测效果图y_truevsy_pred散点图添加yx的对角线。点越靠近对角线预测越准。可以按训练集/测试集用不同颜色区分。特征重要性对于树模型用水平条形图展示Top-N重要特征一目了然。学习曲线绘制训练集和验证集误差随训练样本数或迭代次数的变化曲线用于诊断偏差和方差问题。7. 常见陷阱与实战避坑指南根据我们和众多参赛队伍的经验以下几个坑几乎每年都有人掉进去数据泄露这是最致命的错误。任何基于数据集统计量的预处理如标准化用的均值、方差填充缺失值用的中位数都必须只在训练集上计算然后应用到验证集和测试集。使用sklearn的Pipeline和ColumnTransformer可以完美避免这个问题。千万不要在划分训练测试集之前就对整个数据集进行标准化忽视交叉验证只在单次划分的训练/测试集上评估模型结果具有偶然性。必须使用K折交叉验证来获得模型性能的稳健估计。最终的测试集只用于最后一次“高考”在模型和参数全部确定后使用。盲目追求复杂模型一上来就搞神经网络结果可能还不如调参好的随机森林。务必建立模型梯队从简单模型开始把它作为基准。复杂模型未必更好且更难训练和解释。特征工程不足或过当要么不做任何特征工程直接扔给模型要么构造了大量高度相关或无意义的特征导致维度灾难。特征工程需要结合领域知识化学和数据分析相关性、重要性分析。不进行模型解释交上去一个黑箱模型即使预测准也缺乏说服力。使用SHAP等工具解释模型说明哪些特征在驱动预测能将你的论文从“技术报告”提升到“科学研究”的层次。论文重模型轻分析论文通篇在讲用了什么模型、准确率多少但缺乏对结果背后物理化学意义的深入分析。一定要结合特征重要性讨论模型发现与化学常识或理论是否一致这能体现你作为“AI化学家”的洞察力。代码与论文脱节论文里说的模型和参数要和提交的代码完全一致。评委可能会运行你的代码来复现结果。最后再分享一个我们团队内部的小技巧在比赛开始后第一时间用最简单的模型比如线性回归跑通整个流程数据读取-预处理-训练-预测-评估得到一个基准分数。这个“端到端”的流程打通后后续无论尝试多么复杂的模型你都有一个快速验证的框架心态上会稳很多。记住在这场比赛中你不仅是程序员更是用数据和方法解决科学问题的研究者。祝大家在数模竞赛和AI for Science的道路上都能取得好成绩