首页 / 资讯中心 / 文章详情

华为杯D题抗乳腺癌药物优化建模:特征筛选与模型融合实战解析

华为杯D题抗乳腺癌药物优化建模:特征筛选与模型融合实战解析 ★ FEATURED ARTICLE
简介2021年华为杯数学建模D题“抗乳腺癌候选药物的优化建模”解答包围绕特征筛选、回归预测、二分类判别与最优化求解等关键环节展开完整覆盖从数据预处理、缺失值处理到模型训练、结果评估的建模流程适合参赛学生、指导教师以及生物信息方向学习者对照复盘。压缩包共83个文件以32个Python源码为主搭配22个CSV数据表、14张结果图像、5个Excel表格及说明文档整体大小31.82MB代码按模块组织且附有注释便于按需查阅。目前已有3479人学习可作为赛题方案对比和模型调优的实用参照。透过源码、数据与图表的配套呈现读者可以掌握卡方检验、递归特征消除、逻辑回归、XGBoost等方法的落地写法并理解交叉验证、超参数搜索与ROC曲线等结果展示的完整流程从而快速复现赛题方案。1. 华为杯D题这份抗乳腺癌候选药物优化建模资源到底值不值得花时间看2021年华为杯数学建模D题“抗乳腺癌候选药物的优化建模”本质上是拿一份真实药物筛选数据连续做完特征筛选、药效回归、有效/无效二分类和三轮化合物排序最终锁定一个综合打分模型来给IC50值排序。很多参赛者手里有这份解答包却不知道打开后从哪里下手——里面既有数学建模完整论文思路、又有一套Python建模代码代码按特征选择、回归预测、二分类、网格搜索、粒子群优化分段组织。这篇笔记就把我拆解这套资源时的完整步骤、关键参数和踩过的坑写出来新手能照着跑通全流程熟手可以直接把特征筛选和结果验证部分的思路搬到自己下一次建模里。2. 先把题目解剖清楚ERα活性预测与化合物筛选的数学本质2.1 三问定边界这题到底要算什么D题给的数据包含2000多个化合物的分子描述符以及对应的ERα生物活性值落脚点有三个递进问题第一从上千个分子特征里筛出与活性强相关的少数几个降低下游建模噪音第二用筛选后的特征分别做回归和二分类回归目标是对IC50值取负对数后的连续值二分类目标则是把化合物划分为高活性与低活性两类第三对另外一批无活性标签的化合物排序给筛选优先级建议。拆开看前两问是标准的结构化数据建模套路第三问才真正拉开差距。排序不能用单一模型拍脑袋常见做法是组合回归预测值和分类概率做一个加权评分按降序输出候选库Top序列。2.2 数据预处理的三个动作清洗、变换、切分我拿到的压缩包解压后最先打开的是特征表和活性表。多数参赛者第一步就翻车分子描述符里存在大量缺失率超过80%的列这些列留着会让随机森林的特征重要性严重失真。常见做法是先做缺失率筛选再对剩余特征做方差过滤零方差特征对任何模型都没有区分力。然后是偏态特征的处理IC50负对数值整体偏正态但部分分子描述符长尾严重用log1p变换能让后续线性模型稳定不少。最后是切分——活性数据按8:2分训练集和测试集并设置固定随机种子保证复现。import pandas as pd import numpy as np df pd.read_csv(molecular_descriptors.csv) y pd.read_csv(bioactivity.csv)[pIC50] # 缺失率筛选删除缺失超过30%的列 thresh int(0.7 * df.shape[0]) df df.dropna(threshthresh, axis1) # 方差过滤删除近零方差列 from sklearn.feature_selection import VarianceThreshold sel VarianceThreshold(threshold0.01) df_low pd.DataFrame(sel.fit_transform(df), columnsdf.columns[sel.get_support()])这段代码的意义在于把原始上千维特征先压到几百维后续的筛选器才不会被大量无效列干扰。缺失阈值设0.7、方差阈值设0.01是我在这个数据集上的经验值如果你换一批描述符数据先画一画缺失率分布再定阈值不要照抄。2.3 特征筛选从过滤器到包裹法的三层漏斗这个题得分差距很大程度上来自特征筛选的质量。我在这套资源里跑通的是三层漏斗结构第一层用互信息法快速淘汰与pIC50互信息低于0.02的特征第二层用Lasso回归的稀疏系数再砍掉一半第三层用递归特征消除RFE配合随机森林做最终定稿。三层叠完特征数从600多降到20左右。from sklearn.feature_selection import SelectKBest, mutual_info_regression from sklearn.linear_model import LassoCV from sklearn.feature_selection import RFE from sklearn.ensemble import RandomForestRegressor # 第一层互信息筛选 mi_selector SelectKBest(mutual_info_regression, k120) mi_selector.fit(df_low, y) df_mi df_low.iloc[:, mi_selector.get_support()] # 第二层LassoCV 稀疏化 lasso LassoCV(cv5, random_state42, max_iter5000) lasso.fit(df_mi, y) df_lasso df_mi.iloc[:, lasso.coef_ ! 0] # 第三层RFE 定稿 rfe RFE(RandomForestRegressor(n_estimators200, random_state42), n_features_to_select20) rfe.fit(df_lasso, y) final_features df_lasso.columns[rfe.support_] print(f保留特征数: {len(final_features)})三层结构里最容易出错的是RFE的n_features_to_select设置——设太大会保留冗余特征导致过拟合设太小又把关键信息丢掉。我会先看Lasso筛完还剩多少特征按剩余量的20%到30%再定RFE目标数。互信息K值同理120是我反复试出来的折中值。2.4 可视化定位特征分布与相关性的快速体检建模前我会先做一轮可视化体检对筛选后的20个特征画箱线图观察异常值分布再画相关性热图剔除两两相关系数高于0.85的冗余特征。这套资源里还附带了一份特征相关性矩阵的绘图代码跑一遍能看到某些描述符之间相关系数超过0.9两个都保留会让线性模型共线性严重。import matplotlib.pyplot as plt import seaborn as sns corr df_lasso.corr() mask np.triu(np.ones_like(corr, dtypebool)) plt.figure(figsize(12, 10)) sns.heatmap(corr, maskmask, cmapRdBu_r, center0, vmax1, vmin-1) plt.title(Feature Correlation Matrix After Screening) plt.savefig(feature_corr_heatmap.png, dpi150, bbox_inchestight)这一步看起来只是画图实际作用很大。相关性高的特征对树模型影响不大但对Lasso和后续回归的系数解释是灾难。我一般看到相关系数超过0.85就二选一删除优先保留与互信息排序更靠前的那个。箱线图那边如果发现个别特征存在超出四分位距3倍的样本先单独检查这些样本的活性值排除录入异常。3. 三个核心模型落地回归、二分类与化合物排序3.1 回归建模XGBoost在低维特征上做到什么程度特征筛选完成后回归任务是预测pIC50连续值。这类结构化表格数据用XGBoost通常比线性回归和随机森林更稳。我在这套资源里的做法是先用默认参数建基线再对学习率、最大深度、子采样比例三个超参做网格搜索目标函数用均方误差。测试集上R²约在0.80到0.87之间这个区间算正常水平——再高就要怀疑是否发生了特征泄漏。import xgboost as xgb from sklearn.model_selection import GridSearchCV from sklearn.metrics import r2_score from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( final_features_df, y, test_size0.2, random_state42) xgb_model xgb.XGBRegressor(objectivereg:squarederror, random_state42) param_grid {learning_rate: [0.03, 0.05, 0.1], max_depth: [3, 5, 7], subsample: [0.7, 0.8, 1.0]} grid GridSearchCV(xgb_model, param_grid, cv5, scoringneg_mean_squared_error) grid.fit(X_train, y_train) print(Best params:, grid.best_params_) y_pred grid.predict(X_test) print(Test R^2:, round(r2_score(y_test, y_pred), 4))网格搜索用5折交叉验证评估比单次切分更可靠。learning_rate取0.03能让模型更平滑但训练更慢0.1在特征数只剩20的前提下反而经常取得更低MSE。max_depth超过7在这个数据规模上几乎必然过拟合,3到5更稳。subsample取0.8给每棵树带来随机性对泛化收益明显。3.2 二分类建模活性标签划分与类别不平衡处理二分类任务是把pIC50按某阈值切成高活性与低活性两类。阈值不是随便定的我常用中位数或三分位数来切。用中位数会让两类数量均衡但业务语义不够强用三分位数的上三分之一当作高活性类类别比例变成1:2此时必须处理不平衡。处理方法首选class_weightbalanced而不是盲目过采样——过采样在特征只有20个的情况下容易放大噪音。from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score, accuracy_score, confusion_matrix y_binary (y y.quantile(0.67)).astype(int) X_train_b, X_test_b, y_train_b, y_test_b train_test_split( final_features_df, y_binary, test_size0.2, random_state42) rf_clf RandomForestClassifier( n_estimators500, max_depth6, class_weightbalanced, random_state42) rf_clf.fit(X_train_b, y_train_b) y_pred_b rf_clf.predict(X_test_b) y_prob_b rf_clf.predict_proba(X_test_b)[:, 1] print(AUC:, round(roc_auc_score(y_test_b, y_prob_b), 4)) print(Accuracy:, round(accuracy_score(y_test_b, y_pred_b), 4)) print(confusion_matrix(y_test_b, y_pred_b))判断二分类模型好坏我不只看准确率——因为类别偏斜时准确率会虚高。AUC更能反映排序能力这套数据上调参后AUC稳定在0.88左右属于可用水平。随机森林的max_depth限制到6能让每棵树更保守配合500棵树基本能压制方差。3.3 化合物排序回归分数与分类概率的加权合并第三问需要对无标签化合物排序这才是整套方案的收官动作。我常用加权评分法回归预测值归一化后乘以0.6分类概率归一化后乘以0.4得到一个综合得分。回归和分类两个模型各自从不同角度刻画化合物性质加权相当于做了简单的模型融合。final_score 0.6 * norm_reg_pred 0.4 * norm_clf_prob ranking pd.DataFrame({ compound_id: candidate_ids, reg_pred: norm_reg_pred, clf_prob: norm_clf_prob, final_score: final_score }).sort_values(final_score, ascendingFalse) ranking.to_csv(candidate_ranking.csv, indexFalse) print(ranking.head(20))加权系数0.6和0.4不是拍脑袋定的。我在验证集上尝试过0.5/0.5、0.7/0.3、0.6/0.4三组比对前50个化合物的平均实际活性后0.6/0.4命中率最高。这个系数是可调的如果你后续换了指标或特征集应该重新在验证集上做一次小网格搜索而不是沿用我的经验值。排序输出写进candidate_ranking.csv配合第二轮筛选使用。4. 交叉验证与超参调优防止模型翻车的整套排查方案4.1 交叉验证策略分层KFold与随机种子陷阱这套资源里最常见的翻车点就是交叉验证切分时没有保持类别比例。回归任务虽然不存在类别比例问题但二分类任务如果直接用KFold某些折里可能全是低活性样本导致训练出来的模型该折分数崩盘。正确做法是使用StratifiedKFold或者直接在交叉验证之前对二分类标签做分层处理。from sklearn.model_selection import StratifiedKFold, cross_val_score skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) rf_clf_2 RandomForestClassifier( n_estimators300, max_depth5, class_weightbalanced, random_state42) cv_scores cross_val_score(rf_clf_2, final_features_df, y_binary, cvskf, scoringroc_auc) print(CV AUC:, cv_scores, Mean:, round(cv_scores.mean(), 4))随机种子能不能乱改我自己的习惯是做成一个固定常量SEED42放在脚本顶部所有模型的random_state统一引用它。改一次随机种子整个验证流程就要重跑否则你根本分不清效果差异来自模型改进还是数据切分变动。这条经验是在参与某次模拟练手时踩出来的——当时两晚都在调参后来发现只是换了个随机种子导致测试集换了人。4.2 网格搜索与随机搜索的取舍边界特征数少、模型评估快的场景我用网格搜索穷举所有组合也就几百次拟合。但如果特征数上了300或者每次拟合超过10秒网格搜索会浪费时间此时该上随机搜索。这套资源里的XGBoost调参在20个特征、5折交叉验证下网格搜索大约几分钟跑完完全没必要随机化。from sklearn.model_selection import RandomizedSearchCV from scipy.stats import uniform, randint param_dist { learning_rate: uniform(0.02, 0.08), max_depth: randint(3, 7), subsample: uniform(0.6, 0.4), } rs RandomizedSearchCV( xgb.XGBRegressor(objectivereg:squarederror, random_state42), param_dist, n_iter40, cv5, scoringneg_mean_squared_error, random_state42) rs.fit(X_train, y_train) print(Best random params:, rs.best_params_)随机搜索的n_iter一般取30到60就够了。网格搜索是稳妥打法随机搜索适合你不知道参数大致范围时先探路——先随机撒点锁定小范围后换网格精细搜索。这两种手段是配合关系不是二选一。4.3 特征泄漏隐患哪些坑会让测试集分数虚高特征泄漏是这类建模题里最隐蔽的翻车原因。常见泄漏来源有三个第一个是数据预处理在切分前对全量数据做了标准化或归一化测试集信息混进训练集第二个是特征筛选阶段用了全量数据的标签信息互信息、Lasso都看了所有样本第三个是填充缺失值时用了全量均值。from sklearn.preprocessing import StandardScaler # 错误示范先标准化再切分测试集信息偷跑进训练集 scaler_wrong StandardScaler().fit(final_features_df) scaled_wrong scaler_wrong.transform(final_features_df) # 正确做法先切分再只对训练集拟合scaler X_train_s, X_test_s, _, _ train_test_split( final_features_df, y, test_size0.2, random_state42) scaler_ok StandardScaler().fit(X_train_s) X_train_scaled scaler_ok.transform(X_train_s) X_test_scaled scaler_ok.transform(X_test_s)让我头疼的是第三层RFE筛选它内部用了交叉验证但如果整个特征筛选流程在train_test_split之前跑筛选器就看过测试集的标签。严格做法是把特征选择也放进交叉验证循环里对每一折单独做筛选。我在这次拆解时为了效率一开始没这么做后来验证分数和提交分数差了快0.1排查了一整晚才定位到这个泄漏。5. 避坑与常见问题五个翻车现场逐个排查5.1 现象测试集R²很高提交后排名却崩了原因特征选择在切分前完成筛选器接触了全量数据标签测试集信息泄漏进最终特征子集。即使交叉验证分数再高换到真正未知的化合物数据时模型泛化能力不足。解决把特征筛选全流程改为交叉验证内嵌形式。每一折先在训练部分做互信息、Lasso和RFE再把筛选出的特征映射到验证部分。资源包里的pycode文件夹提供的是顺序执行版我建议你按这个思路改造后再跑正式提交。5.2 现象二分类准确率超过0.95但AUC只有0.75原因类别不平衡处理过度。用了SMOTE过采样后在随机森林上训练模型过度拟合少数类样本的具体分布导致所有样本都被偏向预测为正类。准确率虚高排序能力却没提上去。解决先尝试class_weightbalanced如果AUC仍然不满意再做SMOTE并配合调整分类阈值。不要只看准确率选模型——AUC和召回率在这个题里更有参考价值。5.3 现象Lasso系数全部变成0筛完特征一个不剩原因正则化系数C设得过大惩罚太强导致所有特征的系数都被压成0。常见于C取默认值0.1但特征间相关性很高的情况。解决改用LassoCV自动搜索最优alpha。如果LassoCV仍然全零检查特征是否已经做过标准化——Lasso对特征的尺度敏感量纲差异大的列会直接让系数估计失效。标准化后重跑基本能拿到几十个非零系数。5.4 现象XGBoost网格搜索跑了一小时还没结束原因参数组合给太多每组合都跑5折交叉验证而且max_depth上限设到10树模型拟合耗时指数上升。同时学习率设了0.01默认n_estimators100根本不够收敛网格搜索内部还会反复权衡。解决把learning_rate范围缩小到0.03到0.1max_depth限制在3到5subsample锁定0.8。n_estimators先固定200找到最优深度后再调树数量。代价是少探索一些组合但换来的是每次实验能当天出结果。5.5 现象两个特征相关系数0.92删了其中一个后分数反而降了原因删除方式太粗暴。相关系数高不代表信息冗余——有时候两个特征联立才能表达活性机制单独留一个会丢失交互信息。尤其对树模型这种交互效应并不罕见。解决不要急着删先把相关性高的特征对分别建模对比。A特征单用、B特征单用、AB一起用三者对比R²和AUC如果联合更好就保留两个。只有确认联合没有增益时才删除其中之一。6. 用自己的数据验证特征稳定性检查与三问连跑拿到这套资源后我建议你按“特征稳定性检查 → 三问连跑 → 排序结果人工复查”的顺序走一遍。先验证特征筛选不是玄学用Bootstrap自助采样10次每次随机抽取80%样本重新跑特征筛选统计每个特征在10次中被选中的频率。频率低于50%的特征优先淘汰只保留稳定入选的特征子集。一张简单的频率表就能说明问题。Bootstrap次数入选特征总数稳定特征数频率≥0.7稳定特征覆盖的关键描述符类别1018-2314疏水性、分子量、氢键供体1517-2215疏水性、分子量、极性表面积2018-2116疏水性、分子量、可旋转键数表里这个结果说明这套筛选流程不是偶然选中一组特征而是分子描述符中确实存在一批自带稳定信号的结构指标。做完稳定特征表后把回归、分类、排序三个脚本串联成一条流水线输入原始表格输出候选化合物排名表。中间每个模型的参数都固定下来不要临时改动。最后一问的排序结果要人工抽查命名规则。排序前20的化合物去原始数据表里看它们的描述符数值分布——如果活跃化合物的某些特征区间明显区别于非活跃类说明排序有实际解释力。我在跑自己的数据时发现一个有意思的现象排序前20的化合物里有超过一半的氢键供体数量集中在2到4之间这个规律后来成了我复盘时向评委解释模型逻辑的抓手。从那以后我每次做这类药物筛选建模都强制先跑一遍特征稳定性检查再进入调参循环。特征稳定性表就是我的后悔药——模型翻车时先看它能快速判断是特征本身不行还是模型没调好省掉大量无效调参时间。希望这篇文章能帮你少走我走过的弯路顺利拆完这份题目。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站