从数据预处理到多目标优化:抗乳腺癌药物活性预测与筛选建模全解析

📅 2026/8/23 4:50:41
从数据预处理到多目标优化:抗乳腺癌药物活性预测与筛选建模全解析
1. 从赛题到解题一次完整的抗乳腺癌药物优化建模复盘最近在整理过往的竞赛资料翻到了2021年研究生数学建模竞赛D题题目是关于抗乳腺癌候选药物的优化建模。这道题当时在圈内讨论度很高因为它完美地结合了生物医药领域的实际问题和数学建模的经典方法既有理论深度又有很强的应用导向。很多同学拿到题目后第一反应是“数据在哪”“模型怎么建”感觉无从下手。今天我就以这道题为例结合我自己的参赛和后续指导经验把从题目理解、数据预处理、模型构建到代码实现的完整思路拆解一遍。这不是一份简单的“参考答案”而更像是一次深度的“解题复盘”我会重点讲清楚每个决策背后的“为什么”以及在实际操作中容易踩的坑和应对技巧。无论你是正在备赛的研究生还是对交叉学科建模感兴趣的朋友希望这篇长文能给你带来一些实实在在的启发。这道题的核心是要求我们基于给定的分子描述符数据可以理解为药物的“指纹”信息去预测化合物的抗乳腺癌活性pIC50值并进一步从海量候选化合物中筛选出活性高、且结构新颖的“苗头化合物”。这本质上是一个回归预测多目标优化筛选的问题。难点在于第一数据维度高、可能存在噪声和冗余第二预测模型的准确性直接决定后续筛选的可靠性第三“活性高”和“结构新颖”这两个目标往往是矛盾的需要合理的权衡。接下来我们就一步步拆解。2. 赛题核心剖析与解题路径设计拿到题目尤其是这种数据驱动的题目切忌一上来就埋头敲代码。花足够的时间去理解题目背景、明确任务目标、评估数据特点往往能事半功倍。2.1 任务拆解我们到底要解决几个问题仔细阅读赛题描述我们可以将任务分解为三个环环相扣的子问题活性预测问题这是所有工作的基石。给定一个包含分子描述符特征和pIC50值标签的训练集我们需要构建一个回归模型能够根据分子的描述符准确预测其抗乳腺癌活性pIC50。pIC50是负对数半抑制浓度值越大代表活性越强。预测的准确性用均方根误差RMSE、决定系数R²等指标衡量。高活性化合物初筛问题利用上一步训练好的预测模型对一个更大的、没有标签的候选化合物库进行预测。从中筛选出预测pIC50值高于某一阈值例如题目可能暗示或需要我们根据训练集分布自行设定的化合物作为“高活性候选池”。这一步是粗筛目的是缩小范围。多目标优化筛选问题这是题目的升华点。从“高活性候选池”中不仅要选出活性高的还要选出“结构新颖”的。因为药物研发追求的是新的作用机制和化学实体避免与已知活性化合物过于相似。这就构成了一个经典的双目标优化问题最大化预测活性同时最大化与已知活性化合物的结构差异性即新颖性。我们需要设计合适的优化模型和算法从帕累托最优解集中给出最终的推荐化合物列表。2.2 数据理解与预处理建模前的“大扫除”题目通常会提供一个包含数百个甚至上千个分子描述符的数据集。这些描述符可能包括物理化学性质如分子量、脂水分配系数LogP、拓扑描述符、电子描述符等。第一步就是数据探索性分析EDA和预处理。为什么预处理如此重要在机器学习中有这么一句话“Garbage in, garbage out.” 原始数据往往存在量纲不一、存在缺失值、存在高度线性相关的特征等问题直接丢进模型效果会很差甚至导致计算错误。我的实操步骤与心得缺失值处理首先检查每个特征的缺失比例。对于缺失比例过高的特征例如超过50%我通常会直接删除该特征因为用任何方法填充都可能引入巨大噪声。对于缺失比例较低的特征常用的填充方法有中位数/众数填充对数值型特征用中位数对类别型特征用众数。这是最稳健、最常用的方法。KNN填充利用相似样本的值来填充。效果可能更好但计算量稍大。特别注意绝对不能使用测试集或候选集的任何信息来填充训练集的缺失值反之亦然否则会造成数据泄露严重高估模型性能。必须对训练集和测试集分别进行填充且填充的统计量如中位数只能从训练集中计算。特征缩放很多模型如支持向量机SVR、K近邻KNN、神经网络对特征的尺度非常敏感。我们必须将特征归一化到相似的尺度。最常用的是标准化Z-score标准化和归一化Min-Max缩放。标准化(x - mean) / std。将数据转换为均值为0标准差为1的分布。适用于数据分布近似正态或者存在异常值的情况对异常值有一定鲁棒性。归一化(x - min) / (max - min)。将数据缩放到[0, 1]区间。对异常值非常敏感如果存在极端值整个分布会被压缩。我的选择在不知道特征具体分布且后续可能使用多种模型对比时我优先选择标准化。同样缩放器的参数mean, std, min, max必须仅从训练集拟合然后用于转换训练集和测试集。特征选择与降维成百上千的描述符中很多可能是冗余的或与活性无关的。直接使用所有特征会导致模型复杂、计算慢、且容易过拟合尤其在样本量不大时。常用方法方差过滤删除方差接近0的特征几乎为常数这些特征对区分样本没有贡献。相关性过滤计算特征与标签pIC50的相关系数如皮尔逊相关系数保留相关性较高的特征。同时计算特征之间的相关性如果两个特征高度相关如相关系数0.9则删除其中一个以消除多重共线性。基于模型的特征选择使用Lasso回归L1正则化其系数具有稀疏性可以将不重要特征的系数压缩至0。或者使用树模型如随机森林、XGBoost计算特征重要性保留重要性高的特征。降维主成分分析PCA或t-SNE。PCA是线性降维旨在保留最大方差降维后的特征主成分是原始特征的线性组合失去了可解释性。这里有一个关键取舍如果后续需要解释哪些具体的分子描述符对活性贡献大这在药物设计中很重要则应避免使用PCA而采用特征选择。如果只追求最终的预测精度且特征间多重共线性严重PCA可能是个好选择。踩坑实录在一次比赛中我们为了追求高R²使用了PCA将1000多个特征降到了50维模型在测试集上表现惊艳。但在论文写作解释“哪些化学性质导致高活性”时我们傻眼了——主成分无法对应回具体的化学描述符导致生物机理解释部分非常苍白。后来我们改用“相关性过滤随机森林重要性排序”的组合虽然模型精度略降但可解释性大大增强最终论文评分反而更高。3. 预测模型构建不只是精度竞赛预测pIC50是一个回归问题。我们的目标不是找到某个“最优”模型而是构建一个稳健、可靠、泛化能力强的预测系统。3.1 模型选型与对比没有放之四海而皆准的“最好”模型。我的策略是快速实现2-3个不同原理的模型进行对比然后选择表现最佳且稳定的或者进行模型融合。线性模型家族线性回归基准模型。如果特征与标签关系接近线性且数据质量高它可能就足够了。但通常难以捕捉复杂关系。岭回归Ridge与Lasso回归处理多重共线性的利器。Lasso附带特征选择功能。它们计算快可解释性强是很好的起点。树模型家族随机森林回归我的“首选试水模型”。它对数据分布要求低无需精细的特征缩放能自动处理特征交互且能给出特征重要性。不容易过拟合通过bagging但训练好的模型就像个黑箱解释单个预测较难。梯度提升树如XGBoost, LightGBM在诸多数据科学竞赛中霸榜的模型。通过迭代地构建弱学习器树来纠正前序的误差精度通常比随机森林更高。但需要调参学习率、树深度、叶子节点数等且更容易过拟合。支持向量机回归SVR对于中小规模数据集当特征与标签关系非线性时SVR可能表现出色。但其性能极度依赖于核函数的选择线性、多项式、RBF以及惩罚系数C、核参数gamma的调优计算复杂度也较高。神经网络如果数据量足够大本题数据量通常不足以支撑深度网络神经网络可以拟合极其复杂的非线性关系。但对于本题规模一个简单的多层感知机MLP可以尝试但要小心过拟合必须使用早停Early Stopping、丢弃法Dropout等正则化技术。我的操作流程我会先用随机森林和XGBoost快速跑一个基线观察特征重要性同时用岭回归作为线性模型的代表。通过交叉验证比较它们的性能。如果时间充裕会尝试用网格搜索Grid Search或随机搜索Random Search对XGBoost或SVR进行调参。3.2 模型评估与验证防止“纸上谈兵”绝对不能只用训练集上的分数来评价模型必须使用严格的验证策略来估计模型在未知数据上的泛化能力。训练-验证集划分将原始训练集有标签数据进一步划分为新的训练集和验证集例如70%-30%。用新训练集训练模型在验证集上评估。这可以初步判断模型是否过拟合。K折交叉验证K-Fold CV更稳健的方法。将数据分为K份通常K5或10依次将其中一份作为验证集其余K-1份作为训练集循环K次最后取K次评估指标的平均值。这能充分利用有限的数据得到更可靠的性能估计。Scikit-learn的cross_val_score函数可以很方便地实现。评估指标选择回归问题常用指标有均方误差MSE和均方根误差RMSE最常用RMSE与目标值单位一致更好解释。它惩罚大误差。平均绝对误差MAE对异常值不如RMSE敏感。决定系数R²表示模型对数据波动的解释比例越接近1越好。我的报告习惯我会同时报告RMSE和R²。RMSE告诉我们在pIC50尺度上平均误差多大R²则从拟合优度角度给出整体评价。核心技巧保存数据预处理管道和模型在Python中使用sklearn.pipeline.Pipeline将预处理步骤如填充、缩放、特征选择和模型训练封装成一个整体管道。这样做有两个巨大好处第一确保在交叉验证或最终预测时预处理步骤被正确、一致地应用避免数据泄露第二方便将训练好的整个管道保存下来用joblib或pickle用于后续对候选化合物库的批量预测。4. 多目标优化筛选寻找活性与新颖性的平衡点当我们有了一个可靠的预测模型后就可以对庞大的候选化合物库进行活性预测得到每个化合物的预测pIC50值。假设我们设定阈值T例如T可以是训练集pIC50值的前20%分位数筛选出预测值大于T的化合物组成“高活性候选池”记作集合H。现在进入最精彩的部分从H中选出既活性高又结构新颖的化合物。这本质上是一个双目标优化问题。4.1 目标函数的定义目标一最大化活性Maximize Potency这个很直接就是最大化化合物的预测pIC50值。设化合物i的预测活性为P_i则目标函数f1 P_i我们希望其越大越好。目标二最大化新颖性/多样性Maximize Novelty/Diversity这是关键也是难点。“新颖性”如何量化通常我们通过计算候选化合物与已知活性化合物集合即我们最初的有标签训练集记作集合A在化学结构空间中的“距离”来衡量。思路将每个化合物用其分子描述符向量表示。那么化合物i的新颖性可以定义为它到集合A中所有已知活性化合物的平均距离或最小距离。距离度量常用欧氏距离或曼哈顿距离。如果之前做了PCA则在主成分空间计算距离如果用了特征选择则在选出的特征子空间计算。定义公式以平均距离为例Novelty_i 1/|A| * Σ_{j in A} distance(Descriptor_i, Descriptor_j)其中|A|是已知活性化合物的数量。Novelty_i越大说明该化合物与所有已知活性化合物平均差异越大即越新颖。目标函数f2 Novelty_i同样希望越大越好。于是对于候选池H中的每个化合物i我们都有两个目标值(f1_i, f2_i)。我们的任务是从H中选出一个子集使得这个子集中的化合物在f1和f2上综合表现最好。4.2 优化策略帕累托最优与非支配排序我们无法找到一个化合物在f1和f2上同时都是最大值因为这两个目标通常是冲突的一个活性极高的化合物其结构很可能与某个已知活性化合物相似新颖性低反之一个结构完全新颖的化合物其预测活性可能只是中等。因此我们引入帕累托最优的概念。对于一个化合物如果不存在另一个化合物在活性上不低于它且在新颖性上严格高于它或者在新颖性上不低于它且在活性上严格高于它那么这个化合物就是帕累托最优解或称非支配解。所有帕累托最优解构成的集合称为帕累托前沿。如何求解帕累托前沿对于H这种离散、有限的候选集我们可以用非支配排序算法来筛选。算法步骤简述对于H中的每一个化合物i计算它支配了哪些其他化合物以及被哪些化合物支配。支配关系化合物p支配化合物q当且仅当(f1_p f1_q 且 f2_p f2_q)并且至少有一个不等式是严格的。第一轮筛选找出所有不被任何其他化合物支配的化合物。这些就是第一层帕累托前沿Rank 1是最优的一批。将Rank 1的化合物从H中暂时移除。第二轮筛选在剩余的化合物中再次找出所有不被任何其他剩余化合物支配的化合物作为第二层帕累托前沿Rank 2。重复此过程直到所有化合物都被分层。最终我们可以选择Rank 1的所有化合物作为推荐结果。如果Rank 1的化合物数量太多可以根据实际需求比如只需要推荐前10个在Rank 1内部按照某种规则进一步排序例如给两个目标赋予权重计算加权和Score w1 * f1 w2 * f2(需要将f1, f2标准化到同一尺度)。使用TOPSIS逼近理想解排序法等多属性决策方法进行排序。4.3 代码实现的关键点这部分的核心是计算新颖性和执行非支配排序。在Python中我们可以利用numpy进行高效的向量化距离计算并手动实现非支配排序逻辑。import numpy as np from scipy.spatial.distance import cdist def calculate_novelty(candidate_features, known_active_features): 计算候选化合物集合中每个化合物相对于已知活性化合物集合的新颖性平均欧氏距离。 参数: candidate_features: numpy数组形状为 (n_candidates, n_features)候选化合物的描述符。 known_active_features: numpy数组形状为 (n_known, n_features)已知活性化合物的描述符。 返回: novelty_scores: numpy数组形状为 (n_candidates,)每个候选化合物的新颖性得分。 # 计算每个候选化合物到所有已知活性化合物的距离矩阵 # cdist 计算两个集合中每对点之间的距离返回矩阵 dist_mat 形状为 (n_candidates, n_known) dist_mat cdist(candidate_features, known_active_features, metriceuclidean) # 对每个候选化合物计算到所有已知活性化合物的平均距离 novelty_scores np.mean(dist_mat, axis1) return novelty_scores def non_dominated_sorting(potency_scores, novelty_scores): 对候选化合物进行非支配排序。 参数: potency_scores: numpy数组预测活性分数 (f1)越大越好。 novelty_scores: numpy数组新颖性分数 (f2)越大越好。 返回: fronts: 列表的列表fronts[0]是第一层帕累托前沿Rank 1的索引fronts[1]是第二层以此类推。 n len(potency_scores) # 初始化支配计数和被支配集合 domination_count np.zeros(n, dtypeint) # 被多少个解支配 dominated_solutions [[] for _ in range(n)] # 支配了哪些解 fronts [[]] # 存储各层前沿 # 第一步计算支配关系 for i in range(n): for j in range(i1, n): # 判断i是否支配j if (potency_scores[i] potency_scores[j] and novelty_scores[i] novelty_scores[j]) and \ (potency_scores[i] potency_scores[j] or novelty_scores[i] novelty_scores[j]): dominated_solutions[i].append(j) domination_count[j] 1 # 判断j是否支配i elif (potency_scores[j] potency_scores[i] and novelty_scores[j] novelty_scores[i]) and \ (potency_scores[j] potency_scores[i] or novelty_scores[j] novelty_scores[i]): dominated_solutions[j].append(i) domination_count[i] 1 # 第二步找到第一层前沿支配计数为0的解 current_front [] for i in range(n): if domination_count[i] 0: current_front.append(i) fronts[0] current_front # 第三步迭代寻找后续前沿 front_index 0 while fronts[front_index]: next_front [] for i in fronts[front_index]: for j in dominated_solutions[i]: # 对于被i支配的解j domination_count[j] - 1 if domination_count[j] 0: # 如果j不再被任何当前层以外的解支配 next_front.append(j) front_index 1 if next_front: fronts.append(next_front) else: break return fronts使用示例# 假设我们已经有了 # candidate_potency: 候选池H的预测活性数组 # candidate_novelty: 候选池H的新颖性得分数组 # 进行非支配排序 pareto_fronts non_dominated_sorting(candidate_potency, candidate_novelty) # 获取第一层帕累托最优解Rank 1 rank1_indices pareto_fronts[0] print(f找到 {len(rank1_indices)} 个帕累托最优解。) # 这些索引对应候选池H中的化合物可以输出它们的ID、活性值和新颖性值 recommended_compounds candidate_pool.iloc[rank1_indices] # 假设candidate_pool是H的DataFrame print(recommended_compounds[[Compound_ID, Predicted_pIC50, Novelty_Score]])5. 完整流程串联与工程化思考将以上所有步骤串联起来就构成了一个完整的解决方案。但在实际竞赛或项目中我们还需要考虑工程实现的健壮性和结果的可解释性。5.1 构建可复现的自动化流程一个好的建模项目应该像一条流水线从原始数据输入到最终结果输出中间步骤清晰、可复现。我建议使用Jupyter Notebook或Python脚本模块化组织代码01_data_preprocessing.ipynb: 数据加载、探索、清洗、特征工程。输出清洗后的训练集、测试集、候选集。02_model_training_evaluation.ipynb: 模型训练、调参、交叉验证、性能评估。使用Pipeline保存最佳模型。03_activity_prediction.ipynb: 加载保存的模型管道对候选化合物库进行批量活性预测生成带有预测pIC50的候选池文件。04_novelty_calculation.ipynb: 计算候选化合物相对于训练集的新颖性得分。05_multi_objective_optimization.ipynb: 执行非支配排序筛选帕累托最优解并输出最终推荐列表。utils.py: 存放公共函数如calculate_novelty,non_dominated_sorting等。5.2 结果可视化与洞察“一张好图胜过千言万语”。在论文或报告中可视化至关重要。预测模型性能绘制真实值 vs. 预测值的散点图并标注R²和RMSE。绘制残差图检查残差是否随机分布判断模型是否系统性地高估或低估某些值。特征重要性如果使用树模型绘制特征重要性条形图找出对活性预测贡献最大的分子描述符并尝试从化学角度解释。帕累托前沿可视化绘制活性-新颖性的二维散点图用不同颜色或标记区分不同的非支配排序层级Rank 1, Rank 2...。可以清晰展示目标之间的权衡关系以及你所选出的最优解在全体候选者中的位置。化合物结构展示如果数据包含化合物的SMILES字符串可以使用RDKit库绘制出最终推荐化合物的二维化学结构式直观展示其结构新颖性。5.3 可能遇到的挑战与应对思路数据不平衡已知的活性化合物训练集可能远少于非活性或未标记化合物。这可能导致模型对高活性区域的预测不准。可以考虑使用加权回归给高活性样本更高权重或合成少数类过采样技术SMOTE的回归变体但需谨慎避免引入过多噪声。新颖性度量的局限性我们使用的描述符距离未必完全等同于化学家眼中的“结构新颖性”。可以尝试结合分子指纹如MACCS, Morgan指纹的Tanimoto相似度来定义新颖性Novelty_i 1 - max(Tanimoto_similarity(i, j) for j in A)。距离度量和相似性度量可以都尝试看哪个结果更合理。帕累托前沿解过多如果Rank 1的解有上百个推荐给化学家做实验验证是不现实的。此时需要在Rank 1内部进行精细化排序。除了加权和、TOPSIS还可以考虑聚类将Rank 1的解按描述符进行聚类然后从每个类簇中选取一个代表性化合物如类簇中心这样可以确保推荐的化合物不仅在目标上最优而且在化学空间分布上也具有多样性。回顾整个解题过程从数据清洗到模型预测再到多目标优化每一步都充满了选择和权衡。数学建模的魅力正在于此它没有唯一的标准答案而是要求我们根据问题背景、数据特点和实际约束构建一个逻辑自洽、行之有效的解决方案。这道抗乳腺癌药物优化的赛题就是一个非常经典的范例。它训练我们的不仅仅是调包和调参更是问题定义、量化、求解和评估的系统性思维。希望这份超详细的思路拆解能帮助你在下次面对类似复杂问题时能够更加从容地抽丝剥茧找到属于自己的最优解。