
简介这份2021年华为杯数学建模D题抗乳腺癌候选药物的优化建模解答包面向数学建模参赛者、生物信息及医疗数据分析学习者完整覆盖从特征筛选、回归预测到二分类建模、最优化求解与模型评估的赛题全链路。压缩包共83个文件包含32个可运行的Python脚本、22个CSV数据文件、14张PNG结果图、5个Excel计算表与4个XML配置等整体约31.82MB其中Python脚本承载数据预处理与模型训练CSV为清洗后的特征数据PNG直观展示ROC曲线、特征重要性等关键结果。目前已有3479人学习。除解答文档外还提供带注释的代码、预处理后的数据集及可视化图表能帮助理解卡方检验、递归特征消除、逻辑回归、随机森林、k折交叉验证、网格搜索与粒子群算法等核心方法并可直接迁移至药物预测或医疗数据分析场景兼顾备赛参考与工程实践。1. 华为杯数学建模D题抗乳腺癌药物优化建模拉开差距的不是模型多新是数据处理有多稳很多人拿到2021年华为杯D题“抗乳腺癌候选药物的优化建模”这类解答包时第一反应是赶紧调一个XGBoost把R²刷到0.9然后开始写论文。但我在帮队伍复现这类题目时发现真正拉开奖项档次的地方根本不在算法复杂度而在三个环节分子描述符的清洗是否严格、特征筛选有没有引入泄漏、以及论文里能不能把模型结论翻译成可解释的药物优化方向。这道题的本质是定量构效关系建模输入是药物分子的结构描述符输出是活性指标中间靠回归模型连接。它考的不是你会不会调用模型接口而是你会不会用数据讲一个完整可信的故事。这篇文章适合正在备赛的队伍、想快速上手描述符建模的开发者以及做药物早期筛选的从业者。我会从数据准备讲到特征工程、模型调参、避坑清单最后落在可解释分析上照着这条链路走你能得到一条逻辑自洽、可以写进论文的完整流程。2. 抗乳腺癌药物优化建模的数据准备描述符表、活性标签与任务定性2.1 先给任务定性连续活性值走回归离散标签才走分类拿到数据之后的第一件事不是建模而是确认预测目标和数据类型。抗乳腺癌候选药物优化建模这类题目给的数据通常是药物分子的描述符表行是候选化合物列是分子量、脂水分配系数、氢键供体数量、拓扑极性表面积等结构描述符再加一列活性标签。这列标签如果给的是IC50或pIC50就是连续值任务定性为回归如果标签已经被离散成“高活性、中活性、低活性”才应该按分类来处理。这个判断一旦定错后面所有的评价指标、损失函数、可视化方式、论文模型部分写法全部跟着错。我见过不少队伍把连续回归任务硬生生用准确率来评价结果训练过程正常论文里却出现“回归模型准确率92%”这种逻辑不通的描述。所以要养成拿到数据先看标签分布的习惯打印describe、value_counts确认数据范围和量级再决定要不要做变换。import pandas as pd # 加载描述符表常见格式为csv或xlsx df pd.read_csv(drug_descriptors.csv) # 看前几行和整体形状确认特征列和标签列 print(df.head(3)) print(df.shape) # 关注标签列分布连续值还是离散值 target_col pIC50 # 根据实际表头调整 print(df[target_col].describe()) # 分类标签需要看类别分布 # print(df[target_col].value_counts()) # 统计缺失比例决定后续清洗策略 missing_ratio df.isnull().mean().sort_values(ascendingFalse) print(missing_ratio.head(20))这段脚本的作用是把数据结构一次性摸清。df.shape告诉你样本量和特征维度如果特征有几百列而样本只有几十行后面必须做特征筛选和降维df[target_col].describe()会输出均值、标准差、四分位数如果标准差很小说明样本间活性差异不大模型很难学出区分度解决思路转向特征工程missing_ratio按缺失比例从高到低排序能直接看出哪些描述符大面积缺失这列信息在后续清洗时优先处理。这里还要注意标签列的数据类型。生物活性数据经常出现字符串比如写成“0.1”或“N.D.”含义是低于检测限或未测出。这种值不能直接参与回归直接删除对应样本是最稳的做法因为用某个数值替换会引入截断偏差且竞赛环境下没有让你详细建模左截断数据的时间。另外如果标签列是IC50我一般会先取负对数转成pIC50再建模原因在于原始IC50的分布跨度可能有好几个数量级作为回归目标会让损失被极大值主导转成pIC50后分布更对称而且数值越大代表活性越高模型语义更直观。2.2 缺失值、重复样本与异常值清洗顺序比清洗方法更重要很多队伍的清洗顺序是“先填缺失、再删重复、最后归一化”看起来很顺但实际跑下来经常出问题。我习惯的顺序是先看缺失比例超过40%的描述符直接删列再删除完全重复的样本行但描述符相同而活性不同的情况要特殊处理然后填剩余缺失最后才做异常值观察和归一化。这里的逻辑是缺失比例过高的列本身已经接近噪声源不管用什么方式填充都相当于制造假数据删掉比填掉更干净。为什么不用均值填充所有缺失因为描述符的缺失模式往往携带着化学语义。有的描述符只对特定子结构有定义另一个分子没有该子结构时输出0或NaN这是结构差异而另一些列缺失是计算失败和分子结构无关。这两类信息混在一起用同一策略填充等于强行统一了两种语义。我在这类题上偏向用中位数填充因为分子描述符经常有温和的离群值均值会被拉偏中位数更稳定对后续线性模型也更友好。import numpy as np from sklearn.impute import SimpleImputer # 删除缺失比例超过40%的列 threshold 0.4 high_missing df.columns[df.isnull().mean() threshold] df.drop(columnshigh_missing, inplaceTrue) # 完全重复的行直接去重 df.drop_duplicates(subsetdf.columns.drop(target_col), inplaceTrue) # 剩余缺失用中位数填充 imputer SimpleImputer(strategymedian) feature_cols [c for c in df.columns if c ! target_col and c ! bin] df[feature_cols] imputer.fit_transform(df[feature_cols]) # 异常值观察Z-score超过3的样本先标记出来不急着删 from scipy import stats z np.abs(stats.zscore(df[feature_cols])) outlier_flags (z 3).sum(axis1) print(含极端值的行数:, outlier_flags[outlier_flags 0].shape[0])threshold0.4是经验值在300行左右的数据里缺失四成的列基本失去统计意义删掉不会影响信息完整性。strategymedian上面解释过要注意SimpleImputer虽然做了填充但它是在全量数据上fit的。如果你之后要做交叉验证这个填充器应该放进Pipeline或者对每一折重新fit否则又是一种轻微泄漏我在避坑章节会重点展开。重复样本的处理有一个容易忽略的细节分子描述符完全相同但活性值不同这种情况往往来自同一药物的多次重复测量。如果直接drop_duplicates算法会默认保留第一条可能丢弃更合理的测量值。我一般先按描述符分组对活性列取均值后再去重这样既消除重复又不浪费多次测量的信息。# 描述符相同但活性不同时先聚合再保留唯一行 df df.groupby(feature_cols, as_indexFalse)[target_col].mean()这步做完数据才算是真正干净的。很多队伍跳过这一步结果就是同一分子同时出现在训练集和验证集里分子结构一样但活性标签略不同模型等于提前见过答案交叉验证R²虚高这种问题在论文复现阶段最容易暴露。2.3 训练集与验证集的划分回归任务里怎么做到近似分层分类任务可以直接用StratifiedKFold按类别比例切分回归任务没有现成的stratify参数所以需要自己动手把连续标签分桶再基于桶做分层。这一步是竞赛老手的常规操作看起来不起眼但样本量小的时候它决定你后面调参结论的可信度。如果不做分层随机切分可能把高活性样本全部分到验证集训练集里高活性段是空的模型学不到完整分布验证集上结果波动极大。from sklearn.model_selection import StratifiedKFold # 把连续标签按分位数切成5个桶用于近似分层 df[bin] pd.qcut(df[target_col], q5, labelsFalse, duplicatesdrop) # 基于分桶做5折分层交叉验证 skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) for train_idx, val_idx in skf.split(df[feature_cols], df[bin]): train df.iloc[train_idx] val df.iloc[val_idx] print(train.shape, val.shape)pd.qcut的q5表示分成5个区间如果标签分布非常集中比如90%的样本活性值都在一个窄区间里q5可能导致某些桶只有一两个样本这时候把q降到3更合理。duplicatesdrop处理分位边界上重复值的情况不传这个参数在部分版本会报错。random_state42是固定随机种子目的是让结果可复现。我强烈建议整个项目从数据划分到模型训练全部固定一个种子否则你今天调出R²0.8明天重跑一遍变成0.75你根本分不清是参数生效了还是随机性在起作用。3. 描述符筛选与降维从几百个分子特征里留下真正影响活性的那批3.1 粗筛三步低方差、高缺失、强相关几百个描述符直接丢进模型不是一个好选择一方面树模型虽然能消化高维特征但很多描述符是冗余计算产物同一个理化性质由不同软件或不同算法生成多个变体另一方面论文写作时评委一定会问为什么留下这些特征所以筛选过程既要有逻辑也要能讲清楚。我的筛选分三步走先删高缺失列这步在上一章已经做了再删低方差列因为方差接近零意味着该描述符在所有样本上的取值几乎不变对区分活性没有任何帮助最后删强相关列避免共线性干扰线性模型。from sklearn.feature_selection import VarianceThreshold # 删掉方差过低的列 selector VarianceThreshold(threshold0.01) selector.fit(df[feature_cols]) low_var_cols df[feature_cols].columns[~selector.get_support()] df.drop(columnslow_var_cols, inplaceTrue) # 重算特征列 feature_cols [c for c in df.columns if c ! target_col and c ! bin] # 删除相关系数高于0.95的冗余对 corr_matrix df[feature_cols].corr().abs() upper corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k1).astype(bool)) high_corr_cols [col for col in upper.columns if any(upper[col] 0.95)] df.drop(columnshigh_corr_cols, inplaceTrue)threshold0.01是绝对方差阈值但描述符量纲差异很大有的在0到1之间有的高达几千直接用绝对阈值可能误删有意义的特征。更稳的做法是先标准化再算方差或者把threshold调得更低只过滤掉那些方差接近0的列。相关系数0.95是竞赛常见阈值如果你后面准备用线性模型或SVR建议降到0.8因为这两个模型对共线性非常敏感方差膨胀因子会让你系数不稳定如果走树模型路线0.95足够树模型的分裂机制天然容忍相关性。低方差和强相关的删除顺序也有讲究。先删低方差再算相关矩阵否则那些方差极小的列会跟所有列的相关性都接近0不会干扰结果但会拖慢计算。值得注意的是这里计算相关性只发生在特征矩阵内部绝对不要把手伸到标签列上。特征和标签的相关性高是好事那是信号不是冗余。3.2 精筛随机森林重要性排序以及它的偏好陷阱粗筛之后通常还剩几十个特征下一步是让模型来打分。随机森林的feature_importances_是绝大多数队伍的首选因为它开箱即用、速度快、结果直观。但这里有个必须说清楚的陷阱基于不纯度减少的重要性分数天然偏好高基数特征也就是那些取值种类特别多的列比如整数编号或高度离散的拓扑指数同时当两个特征高度相关时重要性分数会被拆散分配导致真正重要的特征被低估。所以只看一次重要性排序就定特征集是不靠谱的。我一般会跑至少5轮随机森林每次换不同的random_state然后统计哪些特征稳定出现在Top20里。只出现一次的不算数出现三次以上的才进入候选集。这个方法比单次重要性排序慢不了多少但稳定性显著提升。from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor( n_estimators500, max_depth8, min_samples_leaf3, random_state42, n_jobs-1 ) rf.fit(df[feature_cols], df[target_col]) importance pd.Series(rf.feature_importances_, indexfeature_cols) importance.sort_values(ascendingFalse, inplaceTrue) print(importance.head(20))n_estimators500是为了让重要性估计更平稳理论上树越多越稳定但计算时间线性增长500在这个数据规模下已经足够。max_depth8和min_samples_leaf3是控制单棵树复杂度的参数这里设置它们不是为了最终模型效果而是为了让重要性估计不受过拟合干扰——单棵树深度过深时某些特征会被局部模式绑架重要性失真。特征重要性筛选和最终建模的模型参数可以不一样这是常见做法筛选用保守参数得到稳定排序建模再用调参后的模型追求精度。这里还要强调一个容易被忽略的点筛选后必须验证。把选出的20个特征放回交叉验证里跑一遍和全量特征的结果对比如果R²下降超过0.03说明你筛掉了有效信息需要扩大候选集。这个过程看似麻烦但它能防止你在错误的方向上越走越远。3.3 PCA与PLS的选择逻辑线性模型前用树模型前慎用特征筛选后如果仍然剩下几十个特征并且你用SVR或线性模型可以考虑PCA做降维。PCA把原始描述符合成为互不相关的主成分直接解决共线性问题让线性模型稳定下来。但它的代价是主成分没有物理含义第一主成分是什么没人能回答。而抗乳腺癌药物优化建模最后一定要落到“哪个描述符在拉高活性”这种可解释结论上PCA做完这句话基本说不出来了。PLS偏最小二乘是更贴合当前场景的选择。它在降维的同时利用标签信息找到与活性相关性最强的成分方向在QSAR研究里是经典方法论文引用也有据可查。如果数据量小而且特征共线性严重PLS的第一成分往往稳定对应一组与活性强相关的描述符组合后续写作解释比PCA省力很多。我的一般取舍标准是这样的如果最终模型是SVR或岭回归特征还有二三十个以上优先尝试PCA或PLS如果最终模型是随机森林或XGBoost那通常不需要PCA树模型通过逐特征分裂已经能处理相当程度的冗余强行PCA只会让特征失去语义并削弱树模型对非线性关系的表达能力。关键不是选哪个方法而是能不能在论文里写清楚“为什么需要降维”以及“降维后信息保留了多少”这两个问题。4. 模型选型与调参把回归R²从0.6推到0.8的落地顺序4.1 先跑线性基线和岭回归明确“最低水位”很多队伍拿到清洗好的数据第一步直接XGBoost这不是不行但问题在于你失去了判断基准。先跑一个简单的岭回归基线有实实在在的作用它告诉你“在不考虑复杂非线性关系的情况下描述符能撑起多少预测力”后续所有模型的提升幅度都以它为参照。from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Ridge from sklearn.model_selection import cross_val_score ridge_pipe Pipeline([ (scaler, StandardScaler()), (ridge, Ridge(alpha1.0)) ]) scores cross_val_score(ridge_pipe, df[feature_cols], df[target_col], cv5, scoringr2) print(Ridge R2: %.3f - %.3f % (scores.mean(), scores.std()))Pipeline把标准化和岭回归绑在一起每次交叉验证的每一折都会重新计算均值和方差避免数据泄漏。StandardScaler对树模型可有可无但对Ridge是必须的因为L2正则的惩罚项对量纲敏感如果不标准化量纲大的特征受惩罚小模型会被它们主导。alpha1.0是起点不同的数据规模对alpha的敏感度不同后续可以放在网格搜索里按对数空间试1、10、100。如果Ridge跑出来R²只有0.3左右说明特征和活性之间主要是非线性关系需要在特征工程和树模型上重点发力如果Ridge已经能跑到0.7说明描述符本身携带很强的线性信号这时候SVR或带核方法的模型可能带来额外提升。这个基线还有一个隐藏功能如果Ridge在交叉验证里跑出0.95以上的高分先别高兴大概率是泄漏了回到第2章检查清洗和划分流程。4.2 树模型三件套随机森林、XGBoost、SVR的调参顺序在几百行的竞赛数据上随机森林、XGBoost和SVR几乎覆盖了竞赛队伍常用的模型范围。SVR对特征尺度敏感适合接在岭回归之后继续挖掘线性核或RBF核的非线性能力随机森林不需要手工特征工程就能学到特征交互稳定性好是调参风险最小的选择XGBoost上限最高但过拟合风险同样最大。模型核心参数常用范围注意点随机森林n_estimators300-800超过800收益递减max_depth6-12数据量小别太深min_samples_leaf2-5防过拟合主力XGBoosteta0.02-0.1越小越稳但越慢max_depth3-6比随机森林浅subsample0.7-0.9行采样防过拟合colsample_bytree0.7-0.9列采样丰富树多样性SVRC1-100越大越容易过拟合gamma0.001-0.1RBF核的关键参数epsilon0.01-0.1不敏感带宽度调参不要全表扫描按影响顺序来。随机森林先调min_samples_leaf它是控制过拟合最直接的手柄从3开始试往5、8方向调如果验证集R²开始下降就回头max_depth其次n_estimators最后只调数量级。XGBoost的套路是先用eta0.1把max_depth和subsample粗调一轮再回到eta0.03做细调小学习率配合理想的深度是它出效果的关键。SVR只需要关注C和gamma的组合gamma控制单样本影响半径C控制对误差的容忍度一般从C10、gamma0.01起步用网格搜索各试5个值足够覆盖常见最优区间。一个反直觉的经验如果调参过程中R²几乎不随参数变化问题往往不在模型而在特征。这时候停下来检查特征筛选和标签变换而不是继续烧网格搜索。4.3 用同一套K折公平对比模型别让随机性背锅对比模型的正确姿势是先固定一个KFold切分器所有模型共用同一份训练验证划分。如果每个模型各用各的随机种子那模型之间的差异里混入了数据划分差异你得出的“A模型比B模型好”结论可能只是巧合。from sklearn.model_selection import KFold from sklearn.svm import SVR from xgboost import XGBRegressor cv KFold(n_splits5, shuffleTrue, random_state42) models { Ridge: ridge_pipe, RF: RandomForestRegressor(n_estimators500, max_depth8, min_samples_leaf3, random_state42, n_jobs-1), XGB: XGBRegressor(eta0.05, max_depth4, subsample0.8, colsample_bytree0.8, random_state42), SVR: Pipeline([(scaler, StandardScaler()), (svr, SVR(C10, gamma0.01))]) } for name, model in models.items(): r2 cross_val_score(model, df[feature_cols], df[target_col], cvcv, scoringr2) rmse cross_val_score(model, df[feature_cols], df[target_col], cvcv, scoringneg_root_mean_squared_error) print(%s: R2%.3f RMSE%.3f % (name, r2.mean(), -rmse.mean()))这里每个模型都用了适中的初始参数目的是快速判断谁更有潜力不是追求最优解。neg_root_mean_squared_error返回的是负值因为sklearn习惯把损失函数统一成“越大越好”的形式所以打印时需要取负号这是新手经常看迷糊的地方。如果某轮跑出来RF和XGB差距在0.02以内说明当前特征下两者能力相近后续优先调更稳定的一方。我一般还会把每折的具体分数打出来看标准差正常情况下R²的标准差在0.05到0.1之间如果标准差超过0.1意味着某些折上模型表现特别好、某些折上崩掉这通常指向特征分布不均或样本量不足先处理这个问题再谈调参。5. 抗乳腺癌药物优化建模的5个典型坑现象、原因与解决5.1 特征泄漏归一化和特征筛选做在划分之前现象交叉验证R²高达0.95看起来结果非常好但一旦把模型应用到新数据或者换一种数据划分表现直接跌到0.4左右完全撑不住。原因归一化和特征筛选在划分训练集验证集之前就使用了全量数据。比如你先在全量数据上计算均值方差再切分数据那么验证集的信息已经通过均值和方差这两个统计量渗入训练过程。特征筛选同理如果先在全量数据上算特征重要性再切分重要性排序本身已经见过验证集的标签。解决所有需要拟合数据的操作严格限定在训练集内完成。归一化用Pipeline包住让交叉验证自动对每一折重新fit特征筛选放在每一折内部重新执行或者至少先切分再筛选。判断有没有泄漏很简单如果线性模型都能跑到0.9以上的R²先怀疑泄漏。5.2 活性标签严重偏斜模型永远只输出平均值现象训练过程中损失函数在正常下降但输出结果很奇怪所有样本的预测值都挤在一个很窄的范围里基本等于标签均值模型对高活性样本完全没有区分能力。原因如果标签不是pIC50而是原始的IC50分布会严重右偏极大值主导均方误差。模型发现预测一个中间值比预测极端值的损失小得多于是选择了“保守策略”。解决对标签做log变换IC50取负对数转成pIC50让分布更对称。如果题目已经给了pIC50但分布仍然偏斜考虑用RMSE作为主要评价指标并在论文中说明或者对极端高活性样本做加权。这个坑在处理真实药物数据时特别常见因为体外活性测定数据的数值跨度天然很大。5.3 只看训练集R²看起来很好看一测验证集就崩现象训练集R²有0.95验证集R²只剩0.55中间的差距一眼就能看出模型在背答案。原因树模型过拟合了训练数据描述符维度高、树的深度大、叶子节点样本数少模型把每个训练样本的个体噪声当成了规律。解决记录训练集和验证集R²的差距差值超过0.2基本就是过拟合。快速缓解手段是先调min_samples_leaf到3到5这个参数对过拟合的抑制效果比max_depth更直接。同时用交叉验证的标准差做警惕信号如果标准差超过0.1即使均值好看也不可信。5.4 特征筛选用全量数据选出来的特征天然“认识”验证集现象用随机森林在全量数据上算重要性取Top20特征然后交叉验证效果很好基本都在0.8以上。但把同样的特征集放到新一批药物描述符上模型完全失效。原因特征筛选本身就是一个有监督的学习过程。在全量数据上计算特征重要性等于让特征选择也看到了验证集的标签。这和归一化泄漏是同一类问题但更隐蔽因为筛选代码看起来只是在使用模型的特征没有明显的标签操作。解决把特征筛选放进交叉验证的每一折内部在每折训练集上重新计算重要性再选特征虽然慢一些但结果可信。论文里如果只写了“使用随机森林重要性筛选特征”没有说明筛选是否独立于验证集评委追问时很容易被问倒。5.5 复现解答包时最常见的路径、随机种子和版本问题现象下载了一份解答资料按说明一步步执行结果要么报错要么结果和作者描述对不上R²差一大截。原因这类资料包最常见的三个问题。一是代码用了相对路径但解压后的工作目录和作者当时的环境不一样数据文件没被正确加载二是有没有set随机种子sklearn和模型库各自有默认随机逻辑不固定种子每跑一次结果就变一次三是依赖库版本差异XGBoost旧版本和新版本的默认参数行为不完全相同同一个参数在不同版本里效果可能差0.05以上。解决先建独立虚拟环境固定依赖版本用基于代码文件位置的路径拼接代替相对路径在看不懂别人的代码之前先打印数据形状和原始文件核对。另外强烈建议自己重跑时把numpy、pandas、sklearn、xgboost的随机种子全部固定然后跑两遍确认结果完全一致再开始往下调。6. 用SHAP打开模型黑匣子把预测结论写成药物优化建议6.1 SHAP值怎么看哪个描述符在拉高活性模型调好之后论文写作中最大的难点是如何把“模型R²为0.8”转化成有指导意义的结论。SHAP是目前最常用的模型解释工具它给每个样本的每个特征分配一个贡献值正值表示该特征把活性预测值推高也就是朝向高活性方向负值表示压低了预测值朝向低活性方向。对药物优化建模来说正贡献最大的几个描述符就是候选药物优化时应该优先保留或增强的性质。import shap explainer shap.TreeExplainer(rf) shap_values explainer.shap_values(df[feature_cols]) shap.summary_plot(shap_values, df[feature_cols])TreeExplainer只支持树模型如果你想解释SVR或岭回归可以用KernelExplainer但计算成本高很多几百个样本可能就要等几分钟。shap_values是一个二维数组行是样本列是特征summary_plot生成的蜂群图同时展示了特征的重要性排序和正负方向是论文里最常用的一张图。这里有一个措辞上的雷区SHAP解释的是你的模型不是药物作用机制。写论文时应该说“在当前模型预测范围内该描述符对活性预测贡献最大”而不是“该描述符决定药物活性”。后者会被评审质疑过度解释前者严谨且和建模定位一致。6.2 论文里最值得放的三张图第一张是预测值与真实值的散点图对角线作为参考线R²标注在图内这是模型整体表现最直观的呈现。第二张是特征重要性条形图按SHAP值的平均绝对值排序说明哪些描述符在模型中被重点依赖。第三张是SHAP蜂群图让读者一眼看出每个特征的方向性影响比如某个拓扑描述符值越大活性预测越高而某个电性描述符值越大会抑制活性。三张图正好回答三个递进问题模型整体可不可信、模型依赖什么特征、这些特征怎么影响活性。生成散点图时如果样本量超过两三百随机抽样30到50个点画图否则点会糊成一片看不清楚。配色尽可能和论文模板保持一致图表里的字体大小要按最终打印尺寸来定不要为图省事用默认字号。6.3 我的一个习惯我习惯在论文定稿前把模型从头到尾重跑一遍从原始数据加载到最终结果输出全程记录随机种子和依赖版本号。这个习惯救过我几次因为竞赛评审对复现性越来越重视凡是写“结果可复现”却拿不出种子和环境的文章印象分都会打折扣。文件命名我也喜欢带版本号data_clean_v2.py、feature_select_v3.py改一版存一版这样多轮迭代不会混乱。如果多人分工约定原始数据只读不写所有中间结果输出到单独的processed目录避免有人误改了原始表。这套流程跑顺之后你不需要反复回头猜“这个R²是哪份代码跑出来的”写论文和答辩时都从容很多。希望帮到你。本文还有配套的精品资源点击获取