简介本资源为2021年华为杯数学建模竞赛D题「抗乳腺癌候选药物的优化建模」的完整解答包面向参加数学建模竞赛的高校学生及对生物信息学、医疗数据分析感兴趣的进阶学习者。内容围绕特征选择、回归预测、二分类建模、最优化求解、模型训练与验证、数据预处理、结果可视化等核心环节展开可帮助读者理解如何将机器学习方法落地到药物研发场景。压缩包共83个文件以32个Python脚本、22个CSV数据文件、14张PNG图表为主另含XLSX、XML、Markdown与DOCX等辅助材料整体约31.82MB目录按代码、数据与文档分层组织便于按模块查阅。目前已有3477人学习下载适合需要复盘赛题思路、参考建模流程与代码实现、查漏补缺的参赛者与研究者。1. 华为杯D题抗乳腺癌候选药物优化建模这道题到底在考什么2021年华为杯研究生数学建模竞赛D题题目全称是「抗乳腺癌候选药物的优化建模」属于典型的「数据驱动 多目标优化」复合型赛题。它给出的是一批化合物分子描述符数据要求参赛队围绕生物活性、ADMET性质吸收、分布、代谢、排泄、毒性建立预测模型再在此基础上做分子结构的优化筛选。很多队伍拿到题的第一反应是直接上机器学习调包结果发现数据维度高、样本量小、指标之间还互相打架最后论文写得像实验报告模型精度也上不去。这道题真正考的不是某个算法会不会用而是你能不能把「预测」和「优化」串成一条闭环先用回归模型把pIC50等活性指标预测准再用分类模型判断化合物是否满足ADMET约束最后在满足约束的前提下搜索活性更高的分子描述符组合。适合已经做过一两次数学建模、想冲击华为杯优秀论文的研究生也适合想系统练一遍「特征工程 多目标优化」落地流程的工程师。下面我按自己复现这道题时的实际路径把选型理由、参数设置和踩过的坑讲清楚。2. 数据预处理与特征筛选从729个描述符里挑出能用的2.1 先搞清楚数据长什么样再动手这道题官方给的数据通常包含训练集和测试集两部分训练集里每个化合物有分子描述符MD、pIC50值、以及若干ADMET标签。分子描述符数量在729个左右涵盖拓扑指数、分子指纹、物化性质等。样本量通常只有几百到一千出头属于典型的「宽而浅」数据。直接把这729列全丢进模型结果一定是过拟合交叉验证分数好看但测试集崩掉。我一般会先做三件事检查缺失值分布、看每列方差、算一下描述符与pIC50的皮尔逊相关系数。方差接近0的列直接删因为对区分样本没贡献相关系数绝对值低于某个阈值的列也先放一边但不要急着永久删除后面做特征重要性时还要回头看。import pandas as pd import numpy as np from sklearn.feature_selection import VarianceThreshold # 读取训练数据假设描述符列从第3列开始 train pd.read_csv(train.csv) desc_cols train.columns[2:] # 根据实际列名调整 # 1. 删除方差极低的描述符 selector VarianceThreshold(threshold0.01) X_var selector.fit_transform(train[desc_cols]) kept_cols desc_cols[selector.get_support()] print(f方差筛选后剩余特征数: {len(kept_cols)}) # 2. 计算与pIC50的相关系数 corr train[kept_cols].corrwith(train[pIC50]).abs() corr_selected corr[corr 0.1].index.tolist() print(f相关性筛选后剩余特征数: {len(corr_selected)})这段代码的逻辑是先用方差阈值砍掉近似常数列再用相关系数做粗筛。threshold0.01这个值不是固定的如果数据做过标准化可以适当调高到0.05如果没标准化方差本身量纲差异大建议先做StandardScaler再筛。相关系数阈值0.1也是经验值目的是保留弱相关但可能非线性有用的特征真正精细的筛选交给后面的模型。2.2 用随机森林做嵌入法特征重要性排序粗筛之后特征数可能还有两三百个这时候用嵌入法让模型自己挑。随机森林的特征重要性排序稳定、对共线性不敏感适合这种高维小样本场景。我一般会跑一次RandomForestRegressor取importance累计贡献达到95%的前N个特征作为最终输入。from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor(n_estimators500, max_depth8, random_state42, n_jobs-1) rf.fit(train[corr_selected], train[pIC50]) imp pd.Series(rf.feature_importances_, indexcorr_selected).sort_values(ascendingFalse) cum_imp imp.cumsum() / imp.sum() final_features cum_imp[cum_imp 0.95].index.tolist() print(f最终入模特征数: {len(final_features)})n_estimators500是为了让重要性估计更稳定max_depth8是防止单棵树过深导致重要性被噪声主导。累计贡献阈值0.95意味着保留能解释95%重要性的特征剩下的5%大概率是噪声。这一步做完特征数通常能压到50以内后面建模会轻很多。注意特征筛选必须在交叉验证的每一折内部独立做不能在全量数据上筛完再交叉验证否则会信息泄漏交叉验证分数虚高。我见过不少论文在这里翻车。3. 活性预测模型pIC50回归怎么选、怎么调3.1 为什么我首选梯度提升树而不是深度学习这道题的样本量决定了深度学习不是好选择。几百个样本、几十个特征神经网络参数量随便就超过样本量训练集loss降得再低也是背答案。梯度提升树XGBoost、LightGBM、CatBoost在这个量级上表现稳定对特征缩放不敏感还能直接输出特征重要性做解释。我一般先用LightGBM跑基线再用XGBoost对比最后用贝叶斯优化调参。import lightgbm as lgb from sklearn.model_selection import KFold, cross_val_score model lgb.LGBMRegressor( n_estimators800, learning_rate0.03, num_leaves15, max_depth6, min_child_samples10, subsample0.8, colsample_bytree0.7, reg_alpha0.1, reg_lambda0.1, random_state42 ) kf KFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(model, train[final_features], train[pIC50], cvkf, scoringneg_mean_squared_error) rmse np.sqrt(-scores.mean()) print(f5折交叉验证RMSE: {rmse:.4f})num_leaves15和max_depth6是控制模型复杂度的关键小样本下叶子数太多必然过拟合。learning_rate0.03配合n_estimators800是慢学习率多棵树的经典组合比0.1配200棵更稳。min_child_samples10保证每个叶子至少有10个样本防止模型记住个别离群点。这些参数不是拍脑袋是我在类似规模数据集上反复试出来的区间你可以在这个基础上用Optuna做精细搜索。3.2 评估指标不能只看RMSE回归任务里RMSE是基础但这道题最终要服务于「筛选高活性化合物」所以排序能力比绝对误差更重要。我建议同时看Spearman相关系数和Top-K命中率把预测pIC50最高的前10%化合物挑出来看真实pIC50也排在前10%的比例有多少。这个指标直接对应后续优化环节的实用性。from scipy.stats import spearmanr # 用交叉验证的预测结果计算排序指标 from sklearn.model_selection import cross_val_predict pred cross_val_predict(model, train[final_features], train[pIC50], cvkf) spearman spearmanr(pred, train[pIC50]).correlation print(fSpearman相关系数: {spearman:.4f}) # Top-K命中率 k int(len(pred) * 0.1) top_pred_idx np.argsort(pred)[-k:] top_true_idx np.argsort(train[pIC50].values)[-k:] hit_rate len(set(top_pred_idx) set(top_true_idx)) / k print(fTop-10%命中率: {hit_rate:.4f})Spearman能到0.7以上、Top-10%命中率能到0.5以上这个模型就算可用了。如果Spearman低于0.6优先检查特征筛选是不是漏掉了关键描述符而不是急着换模型。4. ADMET分类与多目标约束把「能用」和「好用」分开4.1 ADMET标签不平衡怎么处理ADMET性质通常是二分类标签比如是否具有肝毒性、是否高血浆蛋白结合率。这类数据往往正负样本比例悬殊直接训练分类器会偏向多数类。我的做法是用SMOTE做少数类过采样但只在训练折内部做验证折保持原始分布。分类器选LightGBM的class_weightbalanced也能缓解但SMOTE在小样本下更直接。from imblearn.over_sampling import SMOTE from sklearn.model_selection import StratifiedKFold from sklearn.metrics import f1_score skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) f1_scores [] for train_idx, val_idx in skf.split(train[final_features], train[ADMET_label]): X_tr, X_val train[final_features].iloc[train_idx], train[final_features].iloc[val_idx] y_tr, y_val train[ADMET_label].iloc[train_idx], train[ADMET_label].iloc[val_idx] smote SMOTE(random_state42, k_neighbors3) X_res, y_res smote.fit_resample(X_tr, y_tr) clf lgb.LGBMClassifier(n_estimators300, learning_rate0.05, num_leaves15, random_state42) clf.fit(X_res, y_res) pred_val clf.predict(X_val) f1_scores.append(f1_score(y_val, pred_val)) print(fADMET分类平均F1: {np.mean(f1_scores):.4f})k_neighbors3是因为小样本下邻居太多会生成不真实的合成样本。F1比准确率更适合评估不平衡分类如果F1低于0.6说明ADMET标签本身噪声大或者特征区分度不够这时候不要硬调模型回去检查标签定义和特征覆盖。4.2 多目标优化活性最大化和ADMET约束怎么同时满足这道题的核心输出是一组推荐化合物要求活性高且ADMET性质达标。我把它建模成带约束的单目标优化目标函数是预测pIC50最大化约束是各ADMET分类器预测概率超过阈值。搜索空间是分子描述符的可行域但描述符之间不独立不能简单随机采样。常见做法是在现有化合物库中做筛选排序而不是从头生成分子。# 对测试集化合物打分活性预测 ADMET约束过滤 test_pred_activity model.predict(test[final_features]) admet_probs clf.predict_proba(test[final_features])[:, 1] # 约束ADMET达标概率 0.7 mask admet_probs 0.7 candidates test[mask].copy() candidates[pred_pIC50] test_pred_activity[mask] candidates candidates.sort_values(pred_pIC50, ascendingFalse) print(f满足ADMET约束的候选化合物数: {len(candidates)}) print(candidates[[compound_id, pred_pIC50]].head(10))阈值0.7是平衡召回和精度的经验值如果候选太少可以降到0.6候选太多就提到0.8。这一步的关键是先把约束卡死再在可行域内排序而不是把两个目标加权求和——加权系数很难解释评审也不买账。5. 避坑与排查这道题最容易翻车的五个地方5.1 特征筛选在交叉验证外面做导致分数虚高现象交叉验证RMSE只有0.3测试集RMSE飙到0.8以上。原因在全量数据上做特征筛选验证折的信息泄漏到了训练过程。解决把方差筛选、相关性筛选、随机森林重要性全部封装进Pipeline在每折训练集上fit验证集上transform。5.2 把ADMET标签当回归做现象用回归模型预测ADMET数值结果全是0.5左右的无效输出。原因ADMET标签是二分类回归模型优化MSE会倾向于预测均值。解决改用分类模型评估指标换成F1或AUC不要看RMSE。5.3 优化环节直接对描述符做梯度上升现象生成的描述符组合在化学上不可能存在评审一眼看出是编的。原因描述符之间有物理约束独立扰动会破坏分子合法性。解决在现有化合物库内做筛选排序或者用遗传算法在合法分子空间搜索不要对描述符向量直接做梯度优化。5.4 忽略描述符的量纲差异现象基于距离的模型KNN、SVM效果远差于树模型。原因描述符量纲从0.001到10000都有距离计算被大量纲特征主导。解决用树模型可以跳过标准化但如果要用SVM或KNN必须先做StandardScaler或MinMaxScaler。5.5 论文里只写最终模型不写选型过程现象评审质疑为什么用LightGBM不用随机森林答不上来。原因只跑了最终模型没做基线对比。解决至少跑三个基线线性回归、随机森林、XGBoost用表格列出交叉验证RMSE和Spearman选型理由自然就有了。6. 从复现到拿奖一个被低估的提分技巧这道题拿高分的关键往往不在模型本身而在「可解释性」和「闭环验证」这两块。我复盘过几篇华为杯优秀论文发现它们的共同点是不仅给出了预测模型还把特征重要性映射回化学意义比如哪些拓扑指数对应分子柔性、哪些指纹对应疏水性然后解释为什么这些性质影响抗乳腺癌活性。评审看的是你有没有把数学建模和领域知识接上。具体操作上我习惯在最终论文里加一张「关键描述符-生物活性」对照表用SHAP值量化每个特征对pIC50的贡献方向。SHAP比特征重要性多了一层方向信息能直接说「这个描述符增大时活性上升还是下降」。import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(train[final_features]) # 输出每个特征的平均绝对SHAP值按降序排列 shap_importance pd.Series( np.abs(shap_values).mean(axis0), indexfinal_features ).sort_values(ascendingFalse) print(shap_importance.head(15))这张表出来之后挑前5个特征去查化学数据库确认它们对应的分子性质写进论文的「结果分析」部分。这一步花不了多少时间但能让论文从「调包报告」变成「有洞察的建模工作」。另一个提分点是做敏感性分析把ADMET约束阈值从0.6到0.9逐档变化看推荐化合物数量和平均预测活性的变化曲线。这条曲线能说明你的优化方案在不同严格程度下都稳定而不是卡在一个特定阈值上才有效。我一般会跑5档画一张双轴图左边是候选数量、右边是平均pIC50评审看到这种分析基本会给加分。最后说个血泪经验这道题的数据预处理和特征筛选至少占整个工作量的60%建模和优化各占20%。很多队伍反过来80%时间调模型结果特征没选好怎么调都上不去。我现在的习惯是先把特征筛选的Pipeline搭稳交叉验证分数稳定了再动模型参数。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?