ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

数学建模竞赛备赛全流程:从拆题到论文提交的工程化指南

数学建模竞赛备赛全流程:从拆题到论文提交的工程化指南 准备华数杯这类数学建模竞赛时真正拉开差距的往往不是临场多背几个模型而是拿到赛题后能不能在有限时间内完成拆解、选型、建模、验证和论文写作这一整条链路。很多队伍第一天上午读完题就开始写代码结果数据没清干净、模型目标函数理解偏了、最后论文只写了结果没有过程说明成绩自然不理想。本文要给的是一套可以直接落地的备赛流程覆盖 A/B/C 类赛题的一般拆解方式、数据预处理、模型实现、结果验证、论文写作、代码管理和提交前检查适合数学建模备赛团队、刚接触竞赛的本科生以及希望把建模流程固化成工程习惯的开发者。文中的代码和配置都按通用环境编写落地前需要根据当年具体赛题调整。1. 拿到赛题后的第一小时先完成拆解再确定思路1.1 A/B/C 类赛题的常见风格与难度映射很多竞赛会提供 A、B、C 三类题目常见风格是 A 题偏机理与物理过程B 题偏优化与决策C 题偏数据与评价预测。但这并不是固定规则每年都会变化不能只看题号就默认题目类型。更稳妥的做法是读题时只看三个信息给定什么数据、要求输出什么结果、用什么指标评价结果。可以把题目快速归入三种风格题目风格典型特征适合团队主要难点机理与物理类给物理过程、微分方程、仿真场景工科背景、能推导公式数学建模、数值稳定性优化与决策类给资源、约束、目标函数求最优方案会写优化模型、懂线性规划或启发式算法目标函数建模、求解效率数据与评价类给大量表格数据要求预测、分类、排名数据处理和机器学习基础好特征工程、结果解释拿到题后的前 30 分钟建议只做三件事列出问题的输入、输出、约束和评价标准判断这是预测、优化、评价还是混合型问题确定两天内最低可交付版本是什么。例如一个“调度问题”刚看像优化题但实际给了历史订单数据要求预测未来需求量再排班那这就是预测加优化的混合题。如果只做排班不做预测结果会缺少重要一环如果只做预测不做排班又没有回答第二问。这类问题的关键是先拆子问题再组装流程。1.2 用倒排时间表安排比赛节奏竞赛通常持续两天左右时间并不宽裕。很多队伍前 20 小时都在反复换模型最后 5 小时才开始写论文效果往往很差。建议用倒排时间表把“交论文”作为终点向前推时间段建议任务检查点第一天上午读题、确定选哪题、完成问题拆解能用一段话说清每个子问题的输入和输出第一天下午数据清洗、EDA、跑通最简基线模型有可运行脚本和第一版结果第一天晚上完成主模型第一版开始做灵敏度分析模型能跑完结果存成表格第二天上午整理结果落论文建模和求解部分所有关键图表已插入第二天下午写摘要、统一全文格式、交叉检查提交文件能被评委快速读明白这里的关键是“基线模型要先跑通”。哪怕只是一个简单的线性回归或平均分配方案都要先给出结果再逐步升级。这样即使后面模型失败论文里也有一个可解释的底版。2. 数据预处理与探索性分析建模质量的第一道分水岭2.1 数据读取、结构检查与命名规范拿到表格数据后最先要确认的是数据能不能被正确读取。如果赛题给的是 Excel 文件建议统一用 pandas 读取并先查看形状、字段、缺失和类型import pandas as pd df pd.read_excel(data/problem_data.xlsx, sheet_name0) print(df.shape) print(df.head()) print(df.info()) print(df.describe())如果文件是 CSV需要注意编码问题。中文数据经常同时出现 UTF-8 和 GBK 两种情况# 先尝试 UTF-8报错时再试 GBK df pd.read_csv(data/problem_data.csv, encodingutf-8)这里有一个很常见的坑Excel 表格里经常有多余空行、合并单元格、单位行导致字段名解析错位。建议读取后先打印df.columns和df.head()确认第一行是不是真正的表头。如果发现表头错位可以在read_excel中指定header1跳过前面的说明行。2.2 缺失值、异常值与量纲处理缺失值处理不是简单地删掉或补零要先看缺失比例。建议先算缺失率missing_rate df.isnull().mean().sort_values(ascendingFalse) print(missing_rate[missing_rate 0])如果某一列缺失超过 30%要考虑这列是否可靠如果缺失率很低可以用均值、中位数或前后值填充。具体方法取决于问题背景不要一律填零。异常值识别可以用箱线图或 IQR 方法Q1 df[value].quantile(0.25) Q3 df[value].quantile(0.75) IQR Q3 - Q1 low Q1 - 1.5 * IQR high Q3 1.5 * IQR outlier_mask (df[value] low) | (df[value] high) print(df.loc[outlier_mask, value].head())IQR 方法只是一个通用参考不能自动决定删除还是保留。对于真实业务数据异常值可能是噪声也可能是关键信号需要结合赛题语境判断。量纲统一是另一个容易被忽略的问题。如果做距离类计算、聚类、主成分分析或梯度类模型建议先标准化或归一化from sklearn.preprocessing import StandardScaler, MinMaxScaler scaler StandardScaler() df_scaled scaler.fit_transform(df[[feature1, feature2, feature3]])注意fit_transform只能对训练集使用验证集或测试集要用同一个 scaler 的transform否则会造成数据分布不一致。2.3 EDA 与特征工程先把图和关系画出来在建模前要花一到两小时做探索性数据分析。建议先把相关性热力图画出来观察变量之间的关系import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(10, 8)) sns.heatmap(df.corr(), annotTrue, cmapcoolwarm, fmt.2f) plt.title(Correlation Heatmap) plt.savefig(figs/corr_heatmap.png, dpi300, bbox_inchestight)相关性热力图能帮助判断哪些特征和目标变量线性相关哪些特征之间高度共线。高度共线的特征进入线性模型会导致系数解释不稳定可以选择去掉一个或做 PCA 降维。特征工程要根据问题背景构造新列常见操作包括时间字段拆成年、月、日、星期连续变量分组交叉特征滞后特征。不要在一个赛题里堆几十个无解释的特征每个新增特征都要能说明物理或业务含义。3. 模型选型与代码实现预测、优化、评价三类问题的主干流程3.1 预测类回归模型、交叉验证与残差分析当问题要求预测数值型结果时先不要直接上复杂模型。建议按以下顺序建立基线线性回归、决策树或随机森林、梯度提升模型。线性回归帮助理解特征方向树模型帮助捕捉非线性。from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, r2_score X df[[feature1, feature2, feature3]].values y df[target].values X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model RandomForestRegressor(n_estimators200, max_depth6, random_state42) model.fit(X_train, y_train) pred model.predict(X_test) print(MAE:, mean_absolute_error(y_test, pred)) print(R2:, r2_score(y_test, pred))预测之后不要只看 R2还要看残差分布把预测值减去真实值画成散点图观察是否存在系统性偏移。如果所有样本的残差都是正的说明模型有系统偏差需要检查数据处理是否偷看了测试集。特征重要性也可以用来解释模型importance pd.Series( model.feature_importances_, index[feature1, feature2, feature3] ).sort_values(ascendingFalse) print(importance)这里一个常见坑是在时间序列问题中直接随机划分训练集和测试集会把未来的信息泄露给模型。如果数据带时间属性应该按时间顺序切分而不是随机切分。3.2 优化类线性规划与整数规划的实现框架优化类题目关键是把目标函数、决策变量、约束条件写清楚。很多队伍卡在“不会用代码描述约束”。先看一个最小化的线性规划示例from scipy.optimize import linprog # 目标最大化 3x 5y等价于最小化 -3x - 5y c [-3, -5] # 不等式约束 # x 4 # y 3 # 2x 3y 18 A_ub [[1, 0], [0, 1], [2, 3]] b_ub [4, 3, 18] # 变量非负 bounds [(0, None), (0, None)] res linprog(c, A_ubA_ub, b_ubb_ub, boundsbounds, methodhighs) print(res.x) print(-res.fun)linprog默认求最小值求最大值时需要把目标函数系数取反输出时再取反。methodhighs是 SciPy 目前推荐的求解器数值稳定性比旧版更好。如果决策变量需要取整数就要用整数规划。SciPy 1.9 之后提供了milp但接口不如pulp直观。实际比赛中更常见的是用pulp或ortools因为它们可以用自然语言写约束import pulp prob pulp.LpProblem(schedule, pulp.LpMaximize) x pulp.LpVariable(x, 0, 4, catInteger) y pulp.LpVariable(y, 0, 3, catInteger) prob 3 * x 5 * y prob 2 * x 3 * y 18 prob.solve() print(pulp.value(x), pulp.value(y), pulp.value(prob.objective))写完优化模型后务必先用最小规模算例手算一遍确认约束方向没有偷换。比如小于等于约束写成大于等于结果会完全偏离。3.3 评价类层次分析法、熵权法与 TOPSIS评价类题目通常会给出多个对象和多个指标要求排出先后顺序。难点在于确定权重。权重可以用主管赋权法比如层次分析法也可以用客观赋权法比如熵权法。如果比赛时间有限建议用熵权法它可以通过数据离散程度直接计算权重。import numpy as np def entropy_weight(data): # data 为 numpy 数组行是样本列是指标 # 所有指标已按正向化处理数值越大越好 p data / data.sum(axis0, keepdimsTrue) k 1 / np.log(data.shape[0]) e -k * (p * np.log(p 1e-12)).sum(axis0) w (1 - e) / (1 - e).sum() return w data np.array([ [85, 90, 70], [92, 80, 75], [78, 88, 82], ]) w entropy_weight(data) print(w)得到权重后可以继续用 TOPSIS 方法做排序。TOPSIS 的思想是最优解应该离正理想解最近离负理想解最远。实现时先把数据标准化再乘以权重再计算每个样本到正负理想解的欧氏距离最后计算贴近度排序。如果用层次分析法要注意判断矩阵的一致性。判断矩阵打分出现矛盾时权重会失真。最简单的检查办法是计算一致性比率 CR CI / RICR 小于 0.1 时一般认为一致性可接受。不要直接把一个明显矛盾的 3x3 矩阵交给程序算权重那样论文解释会站不住脚。3.4 机理建模微分方程与参数估计A 类赛题常涉及物理、生物、经济系统的动力学过程。典型做法是建立常微分方程再用数值积分求解。例如经典的传染病 SIR 模型from scipy.integrate import solve_ivp import numpy as np def sir(t, y, beta, gamma): S, I, R y return [-beta * S * I, beta * S * I - gamma * I, gamma * I] sol solve_ivp( sir, [0, 60], [0.99, 0.01, 0.0], args(0.3, 0.1), t_evalnp.linspace(0, 60, 200) ) # 输出 t 时刻的 S/I/R for i in range(0, 200, 40): print(sol.t[i], sol.y[0][i], sol.y[1][i], sol.y[2][i])如果微分方程里有未知参数需要利用给定数据做参数估计。常见做法是把数值积分结果与真实数据做差构造损失函数再用scipy.optimize.least_squares或curve_fit拟合参数。这个环节很考代码能力建议赛前专门准备一个模板比赛时直接改方程和损失函数。4. 结果验证与灵敏度分析让结论不只是“跑出来了”4.1 模型可复现随机种子、版本记录与脚本组织数学建模比赛的结果必须可复现。评委看到图表后可能会追溯到代码和数据。建议项目目录从一开始就固定下来competition_project/ ├── data/ │ ├── raw/ │ └── processed/ ├── figs/ ├── scripts/ │ ├── 01_data_check.py │ ├── 02_eda.py │ ├── 03_model_baseline.py │ └── 04_sensitivity.py ├── paper/ │ ├── main.tex │ └── figures/ └── README.md所有随机算法都要固定随机种子否则同一份代码每次运行结果不同import random import numpy as np random.seed(42) np.random.seed(42)如果用到 sklearn 模型也建议在模型初始化时传入random_state42。这能避免在论文里写了一个结果评委复现时却得到另一个结果。4.2 误差分析与交叉验证预测类模型至少在训练集和验证集上都输出指标不能只报训练集 R2。如果训练集 R2 非常高而验证集很低说明过拟合。交叉验证是更稳妥的评估方式from sklearn.model_selection import cross_val_score scores cross_val_score(model, X, y, cv5, scoringneg_mean_absolute_error) mae_scores -scores print(mae_scores.mean(), mae_scores.std())交叉验证能给出误差的均值和波动范围比单次划分更有说服力。论文里可以写“模型在 5 折交叉验证下的平均绝对误差为 X标准差为 Y”这比只写训练集误差可靠得多。4.3 灵敏度分析改变参数结果是否稳健优化类和机理类题目一定要做灵敏度分析。它回答的问题是当某个参数变化时结果是否仍然可靠。做法是选定一个关键参数在其基准值附近取上下浮动区间重新求解模型记录目标函数或排名变化。结果整理成表格更容易写进论文参数基准值-10%10%目标值变化幅度单位成本1210.813.22.1%最大产能5004505508.4%如果目标值对某个参数极其敏感论文里就要专门说明实际应用中该参数需要重点收集和校验。如果所有参数变化下结论方向都不变说明方案稳健性较好。4.4 测试集留出与极端场景测试不要把所有数据都用于建模。即使赛题没有明确区分训练集和测试集也建议自己留出一小部分数据做最终验证。对于优化模型可以构造一两个极端输入例如把产能设为极小值或极大值检查输出是否符合直觉。这里有一个常见错误优化模型得到一组最优值后不检查实际代入约束是否满足。正确做法是把解代回约束条件计算每个约束的松弛量确认没有越界。如果某些约束剩余量为负说明求解结果没有真正满足约束。5. 论文写作与图表输出用工程化流程管理文档5.1 摘要写作最后写但最先被阅读评委阅读论文时摘要可能是最重要的一页。建议正文完成后最后写摘要但写作顺序要固定第一句交代问题背景第二到第四句分别说明每个子问题使用的方法随后列出关键数值结果最后一句给出结论或改进方向。不要用“本文介绍了”这类空泛开头而要写“针对调度问题建立了以总成本最小为目标、以产能和交货期为约束的整数规划模型并通过 X 算法求解”。每一句话都要承载信息。注意摘要不要超过页面限制。如果摘要写不满说明问题拆解还不够清楚如果摘要超过一页说明没有抓住核心结论。5.2 图表规范统一命名、dpi、颜色与字体图表是论文的“界面”。建议在代码开头统一设置 matplotlib 参数import matplotlib.pyplot as plt plt.rcParams.update({ figure.dpi: 150, savefig.dpi: 300, font.sans-serif: [SimHei, DejaVu Sans], axes.unicode_minus: False, })中文字体在不同操作系统上有兼容问题。在 Linux 服务器上跑图时“SimHei” 不一定存在这时用英文标签或上传字体避免图里出现方框。保存图片时用bbox_inchestight避免坐标轴标签被裁剪。图片命名建议沿用论文中的编号例如fig_3_2.png表示第三章第二张图。这样写论文时不会因为图片太多而找不到对应文件。5.3 代码、数据与附录的管理方式附录放代码时不要贴几百行无注释代码。建议每个代码文件用 20 字左右说明用途并删掉无关调试输出。论文正文中涉及的关键代码片段可以保留完整代码以附件形式提交。数据文件命名要规范比如data_processed_v2.csv。如果多次处理数据不要用final_final2.csv这样的名字版本管理会让评审过程更顺畅。所有图表和结果表都应该能从脚本重新生成不能出现论文里有图但脚本里找不到绘图代码的情况。5.4 引用与参考文献管理参考文献要真实、可查不能为了凑数量编造。常见可引用的内容有数学建模教材、优化求解器官方文档、scikit-learn 文档、统计方法教材、历史公开赛题优秀论文等。使用 LaTeX 写作时可以用 BibTeX 统一管理引用Word 写作时也要注意排版顺序。6. 常见问题排查与备赛最佳实践6.1 用排查清单定位“模型有问题”比赛中遇到结果不对不要盲目重写代码。建议按下表顺序排查问题现象可能原因检查方式处理建议目标函数值明显不对约束方向写错、单位不统一打印约束矩阵和变量边界用小算例手算把最大值问题转成最小值时检查正负号预测误差巨大数据泄露、标签错位、标准化顺序不对检查 train_test_split 和 fit/transform 顺序只对训练集 fit测试集 transform中文路径报错文件编码或目录名问题检查文件路径、编码和操作系统统一 UTF-8避免中文目录名模型跑太久数据量大、网格搜索过大看运行时间和内存占用降维、抽样、缩小参数范围评价排序不稳定权重方法不合理或指标方向不一致检查指标正向化和归一化正负向指标统一后再计算权重灵敏度分析结果难解释只测了一个参数且没有分组对多组参数重复求解用表格整理参数变化与目标变化实际排查时优先级先从“输入是否正确”开始。如果数据读错了后面模型再高级也没有意义。然后是路径、依赖、版本、配置最后才怀疑算法本身。6.2 备赛阶段的训练安排平时训练比比赛现场临时查资料重要得多。建议赛前做至少三套完整模拟题按正式比赛的时间限制执行。模拟训练的重点不是做出完美结果而是验证团队流程是否顺畅。团队常见分工是建模、编程、写作三人各司其职但接口要清晰。建模同学输出“公式、假设和模型说明”编程同学输出“可运行代码和结果表”写作同学输出“图表骨架和摘要初稿”。三个接口之间不能等对方做完才开工而是要在第一天晚上前完成第一次合并。备赛参考资料要按质量筛选。优先看经典教材、官方文档、公开的历史赛题而不是网上随手找的代码片段。网上代码要确认两点依赖版本是否匹配、数据格式是否一致。直接复制一坨代码跑通后却不理解每个步骤比赛时一旦数据格式变化就会卡住。6.3 提交前检查清单提交前最后 30 分钟不要写新代码只做检查。建议准备一份检查清单参赛编号是否正确所有文件名是否按组委会要求命名。论文 PDF 是否能够打开版本是否为最终版页数是否超限。摘要是否包含关键数值结果是否超过一页。正文所有图表是否有编号、标题、单位和来源说明。附录代码是否能从数据处理到出图完整复现是否有硬编码个人目录路径。关键参数是否在正文中说明是否写清默认值和调整方式。引用是否真实存在参考文献格式是否统一。提交前是否备份了最终数据、脚本和论文是否有本地和网盘双备份。这些看似琐碎但每年都有队伍因为文件名错误、PDF 打不开、代码路径不清而影响成绩。6.4 版权与诚信提醒数学建模竞赛考察的是团队独立解决问题的能力。备赛阶段可以充分参考历史公开赛题和公开资料但正式比赛期间应严格按照赛题规则独立完成模型、代码和论文。引用他人思路或结论时需要在论文中明确标注。这样不仅是对竞赛规则的尊重也能保证提交成果真实反映团队水平。7. 给不同水平团队的备赛路线建议如果是第一次参加比赛建议把目标定为“完整走完流程”而不是追求拿到最高奖。只选一类题目反复训练例如只做数据预测类先掌握数据清洗、回归模型、特征重要性、论文摘要这四件事。如果已经有比赛经验重点应放在提高模型解释力和论文可读性上。很多队伍模型很复杂但论文没有讲清楚为什么选这个模型、假设是什么、结果意味着什么。评委更愿意看到一个逻辑完整、结果可复现的简单模型而不是一个解释不清的复杂模型。如果目标是冲刺高奖项就要在灵敏度分析、多模型对比、结果稳健性和方案可实施性上多花时间。把每一个结论都与数据、模型和现实约束挂钩让整篇论文形成“问题、数据、模型、求解、验证、结论”的完整链路。真正拉开高下差距的往往不是模型库大小而是把建模过程讲清楚、把每个决策解释明白的工程能力。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进