简介:面向数学建模竞赛参赛者与备赛学生的四大模型总结PDF,聚焦预测、分类、优化、评价四类高频题型,帮助在有限时间内建立模型选型与套用的整体框架。压缩包仅含1个PDF文件,约225KB,轻量便于离线查阅与打印。目录按模型大类分层展开:预测部分覆盖神经网络、灰色预测、拟合插值、时间序列、马尔科夫链、微分方程与Logistic回归;分类部分梳理贝叶斯、SVM、聚类、主成分分析、判别分析与决策树;优化部分归纳线性、非线性、整数与动态规划、排队论及遗传算法、模拟退火、禁忌搜索、蚁群算法;评价部分汇总模糊综合评价、层次分析、熵权法、灰色关联与包络分析等。文中对多种方法给出适用条件、优缺点对比和小结,并提示数据缺失用插值、样本少时慎用灰色预测等经验,可作为竞赛模板与素材快速检索。已有801人学习下载,适合需要系统梳理模型、按题查漏补缺的初中级参赛者。
1. 四大模型在数模竞赛里为什么总在最后一天打架:先分清预测、分类、优化、评价的边界
数学建模竞赛里,预测模型、分类模型、优化模型、评价模型这四类常考模型几乎覆盖了大部分题面,但很多人到最后一晚才发现自己把预测做成了分类、把评价做成了排序。预测关心“未来值是多少”,分类关心“样本属于哪一类”,优化关心“在约束下怎么选最好”,评价关心“多个对象怎么排先后”。四个词看起来都像“算一个结果”,输入输出却完全不同。
我见过最典型的翻车,是拿分类模型的准确率去证明预测模型可靠,或者把优化模型的可行解直接当成评价得分。题目里一旦出现“预测未来三天”“判断是否违约”“安排最少班次”“给方案打分”,先写清输出是什么,再决定用哪类模型。适合刚上手数模、准备把代码和论文串起来的人。
2. 预测模型:从移动平均到回归残差,先把时间轴对齐再谈精度
2.1 预测模型选型:数据频率、预测步长和解释成本
预测模型在数模题里最常见的翻车不是算法不够新,而是时间轴没对齐。先把数据按时间排序、补齐缺失日期、确认预测步长,再谈选型。数据只有十几条、题目又要求短期趋势时,移动平均或指数平滑足够交差;有外生变量(价格、天气、投放量)时,线性回归更容易解释;序列平稳且样本稍多时,ARIMA 一类的模型才有发挥空间。灰色预测在数模里常被拿来处理小样本,但它的适用边界要写清楚,别把一条近乎直线的序列硬说成模型预测能力强。
| 模型 | 适合的数据形态 | 输出 | 主要风险 |
|---|---|---|---|
| 移动平均 | 短期、波动不剧烈 | 下一期均值 | 滞后明显,拐点跟不上 |
| 线性回归 | 有外生变量、关系近似线性 | 连续数值 | 共线性、残差自相关 |
| 指数平滑 | 近期权重更高 | 平滑后的下一期 | 参数靠经验,解释弱 |
| ARIMA | 平稳或差分后平稳 | 短期序列值 | 定阶主观,样本少时不稳 |
选型时我会先问三个问题:预测步长是一期还是多期、有没有外生变量、论文里要不要解释系数。三个答案定下来,模型基本就定了。不要因为某个模型名字听起来高级就硬上,赛后复核时解释不清参数,比用移动平均更伤。
2.2 用 Python 跑通移动平均与线性回归的最小流程
下面这段代码把一条时间序列读进来,先做三期移动平均,再用滞后特征做线性回归。日期切分是为了模拟“用过去预测未来”,不能随机打乱。代码里的窗口、滞后阶数和切分日期都要在论文里写清楚。
import pandas as pd from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error # 读取时间序列,date 列解析为日期,y 是待预测指标 df = pd.read_csv("data/series.csv", parse_dates=["date"]) df = df.sort_values("date").reset_index(drop=True) # 三期移动平均:先 rolling 再 shift(1),避免把当期值泄漏进预测 df["ma3"] = df["y"].rolling(window=3, min_periods=1).mean().shift(1) # 构造滞后特征,lag1 表示上一期,lag2 表示上两期 for lag in [1, 2, 3]: df[f"lag{lag}"] = df["y"].shift(lag) # 去掉因滞后产生的空行 model_df = df.dropna().copy() # 按日期切分训练集和测试集,测试集不参与拟合 train = model_df[model_df["date"] < "2024-01-01"] test = model_df[model_df["date"] >= "2024-01-01"] features = ["lag1", "lag2", "lag3"] X_train, y_train = train[features], train["y"] X_test, y_test = test[features], test["y"] model = LinearRegression() model.fit(X_train, y_train) pred = model.predict(X_test) print("MAE:", mean_absolute_error(y_test, pred)) print("coef:", model.coef_) print("intercept:", model.intercept_)逻辑说明:rolling(window=3)计算最近三期的均值,shift(1)把当期值往后错一位,保证预测第 t 期时只使用 t-1 及以前的信息。滞后特征lag1到lag3把序列转成监督学习表格,线性回归才有输入。按日期切分而不是随机切分,是为了避免未来信息混进训练集。
参数说明:window=3表示移动窗口长度,序列周期明显时可以改成 7 或 12;min_periods=1让开头不足三期也能出值,但论文里要说明前几期是弱估计;滞后阶数先取 3 期,样本多时可以看自相关图再调;切分日期要结合题目预测起点,不能为了测试集好看随意改。MAE越低越好,但要和原始指标量纲一起看。
提示:移动平均列和滞后特征列不能同时把当期
y放进自变量,否则就是数据泄漏。
2.3 残差检查和滚动回测:别只看 R²
线性回归拟合完,R²高不代表预测可靠。先看残差是否围绕 0 波动,再看残差有没有明显自相关。如果残差一会儿正一会儿负地成串出现,说明模型漏掉了时间结构,这时移动平均或差分后的序列可能更稳。可以用残差图、残差自相关和滚动回测三件事做检查。
滚动回测的做法是:每次用前一段数据训练,预测后一期,再向前滚动。比如前 30 期训练预测第 31 期,前 31 期训练预测第 32 期,重复到末尾。这样得到的误差更接近真实使用场景。数模论文里至少写清训练窗口、预测步长和误差指标,别只放一张拟合曲线。
误差指标建议同时给 MAE 和 MAPE。MAE 受量纲影响,解释直观;MAPE 是百分比,方便跨对象比较,但真实值接近 0 时会爆炸。若题目要求预测区间,可以用残差标准差做粗略区间,但要说明假设。预测模型的价值在于“可复现的误差范围”,不是一条漂亮但不可解释的曲线。
3. 分类模型:特征表、阈值和交叉验证,哪一步最容易把准确率做虚
3.1 分类任务先定标签口径和混淆矩阵
分类模型的第一步不是选算法,而是把标签口径定死。什么算正类、什么算负类、样本时间范围是什么、重复样本怎么处理,这些不写清楚,后面准确率再高也站不住。比如“是否违约”要看逾期天数阈值,“是否故障”要看故障定义,标签一变,模型全变。数模题里经常给一张特征表和一个结果列,先做缺失值统计和类别分布,再看正负样本是否失衡。
混淆矩阵比单一准确率更能说明问题。正类少的时候,模型全预测负类也能有很高的准确率,但召回率为 0,这种结果在论文里必须被识别出来。分类模型至少报告精确率、召回率、F1 和 AUC 中的两到三个,并说明业务上更怕误报还是漏报。若题目关心“尽量不漏掉风险样本”,阈值就不能默认 0.5。
| 指标 | 看什么 | 适用场景 |
|---|---|---|
| 准确率 | 整体判对比例 | 类别较均衡 |
| 精确率 | 判为正的样本里有多少真为正 | 误报成本高 |
| 召回率 | 真为正的样本里抓回多少 | 漏报成本高 |
| F1 | 精确率和召回率的折中 | 类别不平衡 |
| AUC | 排序能力 | 阈值未定时的整体评估 |
标签口径还要和特征时间对齐。用未来信息构造特征、用全量数据做标准化、在划分测试集之前做缺失值填充,都会让交叉验证分数虚高。分类模型的黑匣子感往往来自这些预处理细节,不是算法本身。
3.2 用逻辑回归和随机森林跑一个可复现基线
下面代码用逻辑回归和随机森林做两个基线,逻辑回归前面加标准化,随机森林限制深度和叶节点样本数,防止小样本过拟合。交叉验证用分层抽样,保证每折类别比例接近。
import pandas as pd from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import confusion_matrix, roc_auc_score, precision_recall_curve # 特征表:features 为数值特征列,label 为 0/1 标签 df = pd.read_csv("data/classification.csv") features = ["x1", "x2", "x3", "x4"] X, y = df[features], df["label"] # 分层切分,保持训练集和测试集的正负比例 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.25, stratify=y, random_state=42 ) # 逻辑回归基线:标准化 + 类别权重平衡 lr = make_pipeline( StandardScaler(), LogisticRegression(max_iter=1000, class_weight="balanced", random_state=42) ) # 随机森林基线:限制深度,避免小样本把树长满 rf = RandomForestClassifier( n_estimators=300, max_depth=5, min_samples_leaf=5, class_weight="balanced", random_state=42 ) cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) for name, model in [("lr", lr), ("rf", rf)]: scores = cross_val_score(model, X_train, y_train, cv=cv, scoring="roc_auc") print(name, "AUC mean:", scores.mean(), "std:", scores.std()) # 用逻辑回归输出概率,再根据精确率-召回率曲线选阈值 lr.fit(X_train, y_train) prob = lr.predict_proba(X_test)[:, 1] prec, rec, thr = precision_recall_curve(y_test, prob) # 以 F1 最大为目标挑阈值,阈值要写进论文 f1 = 2 * prec[:-1] * rec[:-1] / (prec[:-1] + rec[:-1] + 1e-9) best_idx = f1.argmax() print("best threshold:", thr[best_idx], "F1:", f1[best_idx]) print("confusion matrix:\n", confusion_matrix(y_test, prob >= thr[best_idx]))逻辑说明:逻辑回归先标准化再拟合,系数才有可比性;class_weight="balanced"让少数类在损失里权重更高。随机森林限制max_depth和min_samples_leaf,是在小样本场景下压住方差。交叉验证只在训练集里做,测试集留到最后一次评估,避免调参调出乐观结果。阈值用精确率-召回率曲线选,而不是拍一个 0.5。
参数说明:test_size=0.25是测试集比例,样本少时可以用 0.2 并多做几次切分;n_splits=5是折数,正类很少时改成分层 5 折并检查每折正类数量;max_depth=5控制树深,特征多时可以试 3 到 7;min_samples_leaf=5防止叶子节点只落一两个样本。阈值选择要结合题目成本,如果漏报代价高,就把阈值往 recall 高的方向调。
注意:标准化、缺失值填充、特征筛选都要放进交叉验证流程里,不能在全量数据上做完再切分。
3.3 阈值移动与类别不平衡的处理边界
类别不平衡时,先看数据量。正类只有几十条,重采样要谨慎,过采样容易把少数类复制成过拟合,欠采样会丢信息。更稳的做法是先用类别权重和阈值移动,再看召回率是否可接受。阈值不是越低保召回越好,误报会把后续人工复核成本推高,论文里要写清取舍。
如果题目要求输出类别而不是概率,阈值必须固定并写进论文。很多人只写“用模型预测”,复核时换一台机器、换一次随机种子,结果就变了,这是分类模型最常见的不可复现来源。随机种子、切分比例、阈值、特征列顺序都要落到附录或代码说明里。
4. 优化模型:把“尽量”“不超过”翻译成约束矩阵的四个动作
4.1 优化模型先画决策变量表再写目标函数
优化模型最容易在“题目读起来简单,写出来不可行”上翻车。先别急着写目标函数,先把决策变量列成表:每个变量代表什么、单位是什么、取值范围是多少、是连续还是整数。比如资源分配题里,变量可能是“给第 i 个对象分配的数量”,单位可能是吨、人、班次;如果题目说“不能拆分”,变量就是整数,不能用连续线性规划直接糊过去。
目标函数要写清是最大化还是最小化,系数从哪来。约束条件要逐条翻译:“不超过”写成小于等于,“至少”写成大于等于,“尽量满足”要么转成软约束加惩罚项,要么在论文里说明先求可行再谈优化。单位不统一是硬伤,成本用元、时间用小时、资源用吨,混在一起列矩阵,求解器不会提醒你,但结果会离谱。
| 动作 | 要落到的内容 | 检查点 |
|---|---|---|
| 列变量 | 名称、含义、单位、类型 | 有没有漏掉隐含变量 |
| 写目标 | 方向、系数来源 | 系数和变量单位是否匹配 |
| 译约束 | 每条文字对应一个不等式或等式 | 左右两边单位是否一致 |
| 定边界 | 上下界、整数限制 | 边界是否来自题目而非拍脑袋 |
我一般会先把模型写成“变量表 + 目标函数 + 约束清单”,再动代码。这样即使求解器返回不可行,也能逐条回查是哪句文字翻译错了。
4.2 用 scipy.optimize.linprog 求解一个资源分配最小例
下面是一个最小化成本的线性规划例子:三个决策变量,三条资源上限约束,变量非负。代码里同时算松弛变量,用来观察哪条约束卡得最紧。
import numpy as np from scipy.optimize import linprog # 目标函数系数:三个变量单位成本 c = np.array([3.0, 2.0, 4.0]) # 不等式约束 A_ub @ x <= b_ub # 每行代表一种资源消耗,每列代表一个决策变量的消耗系数 A_ub = np.array([ [1.0, 2.0, 1.0], [2.0, 1.0, 0.0], [0.0, 1.0, 3.0] ]) b_ub = np.array([10.0, 8.0, 12.0]) # 变量下界为 0,上界不设 bounds = [(0, None), (0, None), (0, None)] # highs 是常见线性规划求解器,success 为 False 时先查约束 res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method="highs") print("success:", res.success) print("min cost:", res.fun) print("x:", res.x) # 松弛变量 = 资源剩余量,接近 0 表示该约束是紧约束 if res.success: slack = b_ub - A_ub @ res.x print("slack:", slack)逻辑说明:c是目标函数系数,A_ub和b_ub把所有“不超过”约束写成矩阵形式,bounds控制变量上下界。linprog默认求最小化,如果题目是最大化,把c取负号,结果再取负。松弛变量能看出哪条资源先用完,紧约束对应的资源在论文里值得重点解释。
参数说明:method="highs"是常用求解方法,遇到不可行先检查A_ub的符号和b_ub是否写反;bounds里的None表示不设上界,如果题目有库存上限要改成具体数值;变量若要求整数,不能直接用这个连续求解结果,要换整数规划或加取整后的可行性检查。res.fun是最优目标值,res.x是决策变量取值。
提示:求解成功不等于模型正确,先拿一组手工可行解代回约束,确认矩阵没写反。
4.3 不可行与多解时看对偶价格和松弛变量
求解器返回不可行,先别改算法。按顺序查:约束方向是否写反、单位是否统一、边界是否互相矛盾、有没有漏掉“至少满足”的约束。比如一个资源上限写成 10,另一个约束又要求同一资源至少用 12,模型当然不可行。多解时看目标函数系数是否成比例,或者约束是否太松,导致一片区域都一样优。此时论文里要给出选择规则,不能只报一个解。
对偶价格在数模论文里很好用,它表示某条约束右端项增加一个单位时目标值的变化。资源分配题里,对偶价格高的资源就是瓶颈资源。松弛变量为 0 的约束是紧约束,松弛变量大于 0 说明还有余量。把这两个信息写进论文,比只放一个最优解更有说服力。优化模型的价值不只是“算出数”,而是说清“为什么是这个数、卡在哪”。
5. 避坑与排查:四大模型在代码和论文里最容易翻车的 5 个现场
5.1 数据泄漏:验证集里混进未来信息
现象:交叉验证分数很高,换一组测试数据掉一大截;预测模型用了当期值做特征,分类模型在全量数据上做标准化,评价模型把未来指标算进权重。
原因:预处理在切分之前完成,或者时间序列随机打乱。标准化用了测试集均值方差,缺失值填充用了全量统计量,本质上都是把测试集信息提前泄露。时间序列题里随机切分更隐蔽,训练集可能包含测试集之后的数据。
解决:先切分再预处理,把所有变换放进管道;时间序列按日期切分,分类任务用分层切分;论文里写清训练窗口和测试窗口。发现分数异常高时,先查特征表里有没有“结果列衍生变量”,再查预处理顺序。这个坑血泪经验最多,赛后复核一查一个准。
5.2 评价权重拍脑袋:排序变了才发现
现象:评价模型跑出来第一名和直觉不符,换一种权重排序大改;论文里只写“采用熵权法”,没写指标正向化公式和标准化方法。
原因:指标方向没统一,成本型指标直接当收益型算;标准化公式不同,熵权结果就不同;权重没有做灵敏度检查,结论对参数过于敏感。评价模型看起来只是打分,实际每个公式都会改变排序。
解决:先列指标方向表,成本型指标做正向化;标准化公式写进论文;权重算完后做扰动,观察前几名是否稳定。若权重稍微一变排序就翻,结论要降级成“参考排序”,不能写成确定结论。评价模型的结论强度取决于稳健性,不取决于公式复杂度。
5.3 优化约束漏乘单位:求解器返回不可行
现象:模型写得很顺,代码一跑success=False;或者求解成功但结果明显违反题目常识。
原因:目标函数系数和约束矩阵单位不一致,比如成本用元、资源用千克,系数却按吨写;约束方向写反;变量边界和题目“至少”“不超过”冲突。还有一类是漏掉整数限制,连续解取整后不可行。
解决:先做单位表,所有系数换算到同一量纲;拿一组手工可行解代回约束;不可行时逐条删约束定位。求解成功后检查松弛变量和边界,整数题要做取整后的可行性复核。优化模型的错往往不在算法,在翻译。
5.4 分类阈值不写进论文:复核时复现不了
现象:论文写“模型预测准确率 0.86”,复核时换随机种子或默认阈值,结果对不上;正类很少时,准确率好看但召回率很低。
原因:只报了准确率,没报阈值、类别权重和切分方式;阈值在测试集上调完又用同一测试集报成绩,评估乐观。类别不平衡时默认 0.5 阈值往往偏向多数类。
解决:固定随机种子、切分比例和阈值,把混淆矩阵、精确率、召回率一起报;阈值在验证集上选,测试集只评估一次。若题目关心漏报,阈值要向召回率倾斜,并说明误报成本。分类模型的结论要能被人按论文参数复现。
5.5 预测残差自相关:区间估计过窄
现象:预测曲线拟合得不错,但连续几期都偏高或偏低;用残差标准差做的预测区间太窄,实际值频繁落在区间外。
原因:残差存在自相关,普通标准差低估不确定性;模型漏掉趋势或周期;用拟合集残差直接当预测误差,没有做滚动回测。预测模型只报点预测,不报误差范围,论文说服力会弱。
解决:看残差自相关图,必要时差分或加滞后项;用滚动回测误差做区间,而不是拟合残差;论文里写清预测步长和区间覆盖率。预测模型不是把线画漂亮就结束,误差范围才是可交付的部分。
6. 评价模型落地:熵权算完后先做三次权重扰动再交论文
评价模型的常见流程是:指标正向化、标准化、算权重、合成综合得分。熵权法按指标离散程度给权重,TOPSIS 按与理想解的距离排序。流程不难,难在结论稳不稳。我现在的习惯是权重算完后先做三次扰动:权重整体加 5%、整体减 5%、单个指标权重上下调 10%,观察前五名是否换人。若排序大幅变化,就把结论写成“参考排序”,并在论文里给出扰动表。
| 扰动方式 | 观察对象 | 通过标准 |
|---|---|---|
| 权重整体加 5% | 前五名名单 | 名单不变或仅末位互换 |
| 权重整体减 5% | 第一名得分差距 | 差距方向不变 |
| 单指标权重调 10% | 关键对象排序 | 不出现跨档跳变 |
指标正向化要先分清成本型、收益型、区间型。成本型用最大值减当前值,或者取倒数,但取倒数会改变分布,论文里要统一。标准化常用极差法,分母为 0 时要做保护。熵权法算出的权重是信息量权重,不是重要性权重,解释时要谨慎。TOPSIS 的正负理想解要基于标准化后的矩阵,不能混用原始值。
如果只是交一次课程作业,不必堆复杂评价模型;如果要把论文写到别人能复现,这套权重扰动和正向化检查值得花半天搭起来。我吃过一次亏:排序结果很漂亮,答辩时被问“权重改一点会怎样”,现场算不出来。从那以后,评价模型先做扰动表,再写结论。希望帮到你。
本文还有配套的精品资源,点击获取