第一次数学建模:作为编程手的 72 小时
2025 年东北三省数学建模联赛的参赛记录:三人分工、三天时间线的实际推进、把论文公式翻译成代码的办法、数据预处理与结果验证习惯。
2025 年我参加了东北三省数学建模联赛,三人一队,我担任编程手,负责模型代码实现和结果验证。最后拿了三等奖。名次不算亮眼,但这三天大概是我大学前两年密度最高的 72 小时,学到的东西比很多课程都实在。
赛前:先把分工说清楚
组队之后我们没有马上开始刷题,而是先明确了三个人各自负责什么。数学建模的标准分工是这样的:
| 角色 | 主要职责 | 产出物 |
|---|---|---|
| 建模手 | 抽象问题、选模型、推导公式 | 公式、变量定义、假设列表 |
| 编程手(我) | 实现模型、跑数据、出图 | 可运行代码、图表、数值结果 |
| 写作手 | 组织论文、写摘要、排版 | 完整论文 |
分工不是绝对的,但边界要先划出来。我们的教训是:如果不提前说好“公式由建模手给到什么粒度”,编程手就会一直在猜建模手的意图,而建模手以为代码那边早就开始了。
三天时间线
第一天:选题与定模型
上午拿到题目,A/B/C 三选一。我们花了两个多小时对比,取舍标准很朴素:哪道题的数据我们能拿到、模型我们能讲清楚。有一道题看起来很酷,但需要我们没有的数据源,直接放弃——竞赛里最贵的错误就是在做不出来的方向上死磕。
下午到晚上是定模型。建模手把问题拆成几个子问题,给出第一版的变量定义和公式。我这一天的任务不是写代码,而是把公式读明白,并确认输入输出。我当时做的一件事是拿纸把每个公式的输入变量、输出变量、量纲列出来,跟建模手逐个核对。这一步救了我们:有两个变量的物理含义我们俩理解不一致,如果等到代码写完才发现,至少要报废半天。
第二天:跑通与调参
第二天是纯编程。技术栈没有任何花哨的东西,就是 Python 的那套基础库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import minimize
# 读数据,统一用 DataFrame 处理,方便后面按列切片
df = pd.read_csv("data/raw.csv", encoding="utf-8")
# 缺失值先看分布再决定怎么补,不要上来就 fillna(0)
print(df.isna().sum())
# 简单的 3σ 异常值剔除
for col in ["指标A", "指标B"]:
mu, sigma = df[col].mean(), df[col].std()
df = df[(df[col] > mu - 3 * sigma) & (df[col] < mu + 3 * sigma)]
# 求解:目标函数 + 约束条件
def objective(x):
return np.sum((x - df["目标"].values) ** 2)
res = minimize(objective, x0=np.ones(len(df)), method="SLSQP")
print(res.x, res.fun, res.success)
我们没有用商业求解器,一方面是题目的规模用 scipy.optimize 完全够,另一方面是比赛环境里装一个商业求解器本身就有时限风险。第二天的核心矛盾是调参:同一个模型换几组参数,结果差异可能很大。我的习惯是每跑出一组结果就存一份,文件名带上参数,比如 result_alpha0.3_beta1.2.csv,这样后面回溯不用重新跑。
第三天:出图与验证
第三天上午出图,下午写作手整合论文,我负责最后一轮数值验证。
出图这件事比我想的费时间。matplotlib 默认的样式直接放进论文里很糙,我们把图统一成同一套配色、同一号字体、同一尺寸。写作手那边排版时才发现有图例压在数据点上、有的图坐标轴标签是英文——这些都要返工。
怎么把论文里的公式翻译成代码
这是我作为编程手最核心的技能,也是我觉得最值得分享的一点。我的做法是逐层降维:
拿到 min f(x) = Σ(xᵢ - aᵢ)² s.t. g(x) ≤ 0 这样的式子,我按四步走。第一步,把符号和 DataFrame 的列名对应起来,写在注释里;第二步,把数学求和写成一个 for 循环或者 np.sum,先不追求向量化;第三步,跑一个极小规模的测试用例(比如 3 个样本)手工验证结果对不对;第四步,确认无误后再优化性能。
第三步特别重要。我有一次直接用 2000 行数据跑,结果一直是 inf,查了很久才发现是约束写反了方向。如果当时先用 3 行数据跑一遍,一眼就能看出来。
数据预处理与结果验证的习惯
验证这块我总结出两条后来一直在用的方法。
一是换参数复跑。 模型里总有需要人为设定的参数。如果参数在合理范围内动一动,结论就完全翻转,那这个结论是不可靠的,论文里必须说明这一点,而不是挑一组最好看的数字写上去。
二是边界情况检查。 把输入推到极值看看会发生什么:样本量为 0 会怎样?所有指标相同会怎样?约束刚好取等号时会怎样?这些检查往往能暴露出模型假设里的漏洞。
这两条习惯影响了我后面所有项目。做图书管理系统时,我会故意去测“只有一本库存时两人同时借”这种情况,本质上就是同一个思路——不要相信一次跑通的结果,用数据反向检查方案。
三人协作的教训
三条,都是踩出来的:
接口先约定好输入输出。 建模手给我公式,我要回给他“这个函数的输入是一个 N×3 的数组,输出是一个长度为 N 的向量”。把这个说清楚,比说“我实现好了”有用得多。
图表风格统一。 论文里图表风格不一致,审阅的人一眼就能看出是拼凑的。我们在最后一天返工了不少,应该在第一天就定好模板。
版本用 Git 管理。 我们一起改论文和代码,最开始是互相传文件,很快就出现了 论文_final_v2_真最终版.docx 这种命名。后来改用 Git,至少能知道谁在什么时候改了什么。对三个人、三天的比赛来说,这就够了。
关于三等奖
拿到三等奖的时候我是有点不甘心的,因为我们的模型在前两天跑得挺顺。但复盘下来,差距主要在两个地方:一是论文的摘要写得不够清楚,评委在短时间内抓不住我们的亮点;二是验证部分做得浅,只给了一组最优结果,没有做敏感性分析。
这两点都不是编程问题,而是表达和严谨性问题。这也是我后来开始写博客的一个原因:把过程写清楚,本身就是需要练习的能力。