数学建模国赛B题优秀论文复现:预测+优化完整流程与Python实现
发布时间:2026/9/2 4:57:03来源:尧图网络
每年全国大学生数学建模竞赛结束后都会有大量同学下载优秀论文来学习。拿到手之后兴奋地打开结果发现摘要高大上、模型结构复杂、图表精致得像出版级但真想自己动手把这篇论文“复现”一遍却经常卡在第一步数据不知道从哪里来代码不知道从哪行开始写图表也不知道怎么才能画得一样好。最近我在系统复现 2025 年国赛 B 题优秀论文时把整个过程整理成了一条比较清晰的路径从读题、拆解论文到数据重构、模型重建、代码实现再到图表复现和结果对比。这篇文章就把这套流程完整分享出来覆盖概念、环境、代码示例、常见坑点和最佳实践打算备战 2026 数模国赛、或者想系统提升数学建模代码能力的同学都可以直接照着做。1. 背景为什么值得复现一篇国赛B题优秀论文1.1 复现优秀论文到底在复现什么很多同学误以为“复现优秀论文”就是把别人代码跑一遍或者把论文里的公式抄一遍。实际上数学建模竞赛的优秀论文往往不公开源代码连原始数据有时候也不完整所以复现的真正含义是根据论文描述的建模思路、图表结果和算法流程重新实现一套可运行的解决方案。一篇优秀论文的核心资产不是最后那张奖状而是它解决问题的思路。比如它如何定义问题、如何做数据预处理、为什么选择某个模型、约束条件是怎么构造的、灵敏度分析做了哪些参数扫描。这些内容只有在“重新实现一遍”的过程中才能真正理解。1.2 复现对备赛有哪些实际帮助提升代码能力论文里的模型要落到 Pandas、NumPy、Scipy、Pulp 这些工具上代码能力会快速进步。形成自己的建模模板复现两三篇优秀论文之后你会积累一套通用流程后续比赛可以直接复用。训练图表和写作规范优秀论文的三线表、坐标轴标注、结果对比图值得一比一模仿。培养Debug能力复现过程中会踩到大量数据、代码、求解器相关的坑这些经验在真正比赛时非常宝贵。1.3 竞赛B题的特点从历年国赛题目来看B题通常属于“实际问题驱动型”常见于交通、物流、能源、制造等领域。它通常包含两个核心环节一是基于历史数据的分析与预测二是基于预测结果的优化决策。2025 年国赛 B 题优秀论文也延续了这一框架先构造预测模型再建立优化模型求解方案。因此本文复现流程会围绕“预测 优化”这个组合展开。2. 环境准备复现必备工具链2.1 系统与版本说明本文示例使用 Python 环境操作系统可以是 Windows、macOS 或 Linux。需要注意数学建模类代码对版本并不敏感建议使用 Python 3.9 及以上版本。具体库的版本不必刻意追求最新使用当前最新稳定版即可。推荐环境组合Python 3.9NumPy数值计算Pandas数据处理Scikit-learn机器学习建模Matplotlib数据可视化Pulp线性规划与整数规划建模Statsmodels时间序列分析可选OpenpyxlExcel 文件读写处理导出数据时用2.2 安装命令在命令行中执行pip install numpy pandas scikit-learn matplotlib statsmodels pulp openpyxl如果安装速度慢可以使用国内镜像pip install numpy pandas scikit-learn matplotlib statsmodels pulp openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 项目目录结构建议复现时按如下结构组织项目这样代码更清晰也方便后续替换数据和模型2025_guosai_B_reproduction/ ├── data/ │ ├── raw/ # 原始数据 │ └── processed/ # 预处理后的数据 ├── src/ │ ├── data_preprocess.py # 数据预处理模块 │ ├── model_predict.py # 预测模型模块 │ ├── model_optimize.py # 优化模型模块 │ ├── visualize.py # 可视化模块 │ └── main.py # 主流程 ├── output/ │ ├── figures/ # 输出图片 │ └── tables/ # 输出表格 └── requirements.txt在实际复现中这个目录结构可以按需调整但建议保持“数据、代码、输出”分离避免所有文件堆在一起。3. 复现一篇优秀论文的五个关键步骤3.1 第一步读题与拆解论文结构拿到优秀论文后不要直接看代码先做两件事第一回到题目本身用自己的话复述问题。国赛 B 题通常有多个小问每一问要解决什么、输入是什么、输出是什么必须整理成表格。第二拆解论文的章节结构。优秀论文一般包含问题背景与重述问题分析模型假设与符号说明数据预处理与特征工程模型建立模型求解与结果分析灵敏度分析模型评价与改进在拆解过程中建议使用荧光笔标注每一问对应的模型、输入特征、输出指标和关键约束。这些信息是后续复现的“施工图”。3.2 第二步数据重构与预处理优秀论文不公开数据是常态怎么办通常有三个途径题目附件国赛题目一般会提供附件这是最基础的数据来源。论文图表反推论文中的表格和坐标图往往包含数值信息可以读取图中坐标轴范围、曲线趋势、表格数值反推数据量级。公开数据补充如果题目背景是交通、气象、物流等方向可以寻找公开数据集进行替代。拿到数据之后按照标准流程处理import pandas as pd import numpy as np def load_raw_data(file_path): 读取原始数据 df pd.read_csv(file_path, encodingutf-8-sig) print(f数据维度: {df.shape}) return df def clean_data(df): 基础清洗去重、排序、缺失值处理、异常值处理 df df.drop_duplicates() if date in df.columns: df[date] pd.to_datetime(df[date]) df df.sort_values(date) df df.set_index(date) numeric_cols df.select_dtypes(include[np.number]).columns # 缺失值数值列使用前向填充与线性插值组合 df[numeric_cols] df[numeric_cols].fillna(methodffill).interpolate() # 异常值采用 3σ 原则 for col in numeric_cols: mean df[col].mean() std df[col].std() lower mean - 3 * std upper mean 3 * std df[col] df[col].clip(lower, upper) return df这里用到了两个常见的处理策略缺失值时间序列数据优先使用前向填充再对剩余空值做线性插值保留时间趋势。异常值直接用 3σ 原则裁剪简单稳定适合竞赛场景。3.3 第三步模型重建B 题优秀论文的核心往往是一个预测模型加一个优化模型。复现时建议先跑通简单模型再逐步逼近论文中的复杂模型。预测模型常见选择线性回归可解释性强适合作为 baseline。随机森林 / XGBoost适合表格数据特征工程灵活。ARIMA / Prophet适合有明显时间趋势的数据。优化模型常见选择线性规划目标函数和约束条件为线性。整数规划 / 混合整数规划包含 0-1 变量或整数变量。多目标优化将多个目标加权转化为单目标或使用 Pareto 方法。复现时要重点搞清楚优化变量是什么、目标函数是什么、约束条件有哪些。这些信息通常可以从论文的模型假设和符号说明里找到。3.4 第四步代码实现与结果对比实现流程建议按模块拆分不要把所有代码写在一个文件里。每完成一个模块就运行一次确认输出正确。最后把预测结果、优化结果与论文中的表格、图进行对比。对比时可以关注数值是否在同一量级。曲线趋势是否一致。优化结果是否满足论文中的约束条件。灵敏度分析的方向和幅度是否接近。注意由于数据和代码实现细节不同复现结果不可能与论文完全一致。复现的目标是“流程一致、逻辑一致、趋势一致”而不是追求数值完全相同。3.5 第五步图表与论文输出优秀论文的图表风格非常规范复现时也应该做到使用三线表。坐标轴有明确名称和单位。图例清晰字体大小合适。保存图片时使用 300dpi。后面实战部分会给出具体绘图代码。4. 完整实战案例预测 优化组合流程下面用一个简化示例演示完整复现流程。需要注意这里的数据是模拟生成的目的是展示流程不代表真实国赛题。你可以把数据替换成自己的数据保留这套流程骨架。4.1 生成模拟数据import numpy as np import pandas as pd np.random.seed(42) date_rng pd.date_range(start2024-01-01, end2024-12-31, freqD) n len(date_rng) trend np.linspace(20, 50, n) season 10 * np.sin(np.linspace(0, 2 * np.pi, n)) noise np.random.normal(0, 2, n) demand trend season noise df pd.DataFrame({date: date_rng, demand: demand}) df.to_csv(data/raw/demand.csv, indexFalse, encodingutf-8-sig)这段代码生成了一年的日度需求数据包含趋势项、季节项和噪声项。实际比赛中这个文件应该是题目附件中的真实历史数据。4.2 数据预处理模块文件路径src/data_preprocess.pyimport pandas as pd import numpy as np def load_raw_data(file_path): df pd.read_csv(file_path, encodingutf-8-sig) return df def clean_data(df): df df.drop_duplicates() df[date] pd.to_datetime(df[date]) df df.sort_values(date) df df.set_index(date) df[demand] df[demand].ffill().interpolate() mean df[demand].mean() std df[demand].std() df[demand] df[demand].clip(mean - 3 * std, mean 3 * std) return df这个模块强调“职责单一”读取和清洗分开后面替换真实数据时不需要改动其他文件。4.3 预测模型模块文件路径src/model_predict.py预测部分使用随机森林配合滞后特征和滚动统计特征适合没有太多时间序列经验的同学。import pandas as pd from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score def create_features(df, target_coldemand): data df.copy() data[month] data.index.month data[dayofweek] data.index.dayofweek data[lag_1] data[target_col].shift(1) data[lag_7] data[target_col].shift(7) data[rolling_mean_3] data[target_col].rolling(3).mean() data[rolling_mean_7] data[target_col].rolling(7).mean() return data.dropna() def train_predict_model(df, target_coldemand, test_size0.2): data create_features(df, target_col) feature_cols [month, dayofweek, lag_1, lag_7, rolling_mean_3, rolling_mean_7] X data[feature_cols] y data[target_col] split_idx int(len(data) * (1 - test_size)) X_train, X_test X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test y.iloc[:split_idx], y.iloc[split_idx:] model RandomForestRegressor(n_estimators200, random_state42) model.fit(X_train, y_train) y_pred model.predict(X_test) print(MAE:, round(mean_absolute_error(y_test, y_pred), 4)) print(RMSE:, round(mean_squared_error(y_test, y_pred, squaredFalse), 4)) print(R2:, round(r2_score(y_test, y_pred), 4)) return model, y_test, pd.Series(y_pred, indexy_test.index)这里有一个关键细节train_test_split没有使用随机切分而是按时间顺序切分。因为时间序列数据一旦随机打乱就会造成“用未来预测过去”的数据泄漏导致评估结果虚高。这一点在复现论文时尤其要注意优秀论文都会强调时间顺序划分。4.4 优化模型模块文件路径src/model_optimize.py预测得到未来需求后需要决策如何安排生产方案或调度方案。这里用整数规划演示一个简单的“需求分配 方案启动”问题。import pulp def optimize_plan(demand_list): # 可选方案的固定成本、单位成本和容量 fixed_cost [100, 150, 120] unit_cost [8, 5, 9] capacity [50, 80, 60] num_plan len(fixed_cost) num_period len(demand_list) model pulp.LpProblem(Demand_Allocation, pulp.LpMinimize) # x[i][t] 表示第 i 个方案在第 t 个时段提供的需求量 x pulp.LpVariable.dicts(x, (range(num_plan), range(num_period)), lowBound0, catpulp.LpInteger) # y[i] 表示第 i 个方案是否启动 y pulp.LpVariable.dicts(y, range(num_plan), catpulp.LpBinary) # 目标函数单位成本 固定启动成本 model pulp.lpSum(x[i][t] * unit_cost[i] for i in range(num_plan) for t in range(num_period)) \ pulp.lpSum(y[i] * fixed_cost[i] for i in range(num_plan)) # 约束1每个时段的需求必须被满足 for t in range(num_period): model pulp.lpSum(x[i][t] for i in range(num_plan)) demand_list[t] # 约束2未启动的方案不能分配任务且分配量不能超过容量 for i in range(num_plan): for t in range(num_period): model x[i][t] capacity[i] * y[i] # 求解 model.solve() if pulp.LpStatus[model.status] Optimal: print(最优目标值:, pulp.value(model.objective)) plan_result [] for t in range(num_period): row [int(x[i][t].varValue) for i in range(num_plan)] plan_result.append(row) return plan_result else: print(求解失败状态:, pulp.LpStatus[model.status]) return None这段代码体现了两类经典约束需求满足约束所有方案提供的数量之和必须大于等于预测需求量。容量与启动逻辑约束未启动的方案不能分配任务已分配的任务不能超过该方案最大容量。catpulp.LpBinary定义了 0-1 决策变量用来表达“方案是否启动”这个逻辑。这种建模思路在国赛 B 题优化类问题中出现频率很高。4.5 可视化模块文件路径src/visualize.pyMatplotlib 默认不支持中文在复现论文图表时必须显式设置字体。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False def plot_forecast(y_test, y_pred, save_pathNone): plt.figure(figsize(10, 5)) plt.plot(y_test.index, y_test.values, label真实值, linewidth2) plt.plot(y_pred.index, y_pred.values, label预测值, linestyle--, linewidth2) plt.title(需求预测结果对比) plt.xlabel(日期) plt.ylabel(需求量) plt.legend() plt.grid(alpha0.3) if save_path: plt.savefig(save_path, dpi300, bbox_inchestight) plt.show() def plot_allocation(plan_result, demand_list, save_pathNone): plan_result np.array(plan_result) num_period plan_result.shape[1] plt.figure(figsize(10, 5)) bottom np.zeros(num_period) colors [#4C72B0, #DD8452, #55A868] labels [方案1, 方案2, 方案3] for i in range(plan_result.shape[0]): plt.bar(range(num_period), plan_result[i], bottombottom, labellabels[i], colorcolors[i], alpha0.8) bottom plan_result[i] plt.plot(range(num_period), demand_list, k--, label预测需求, linewidth2) plt.title(多时段需求分配方案) plt.xlabel(时段) plt.ylabel(分配量) plt.legend() if save_path: plt.savefig(save_path, dpi300, bbox_inchestight) plt.show()柱状堆叠图可以直观展示每个时段各方案承担了多少需求黑色虚线表示预测需求便于检查总供给曲线是否完全覆盖需求线。4.6 主流程串联文件路径src/main.pyimport sys sys.path.append(../) from src.data_preprocess import load_raw_data, clean_data from src.model_predict import train_predict_model from src.model_optimize import optimize_plan from src.visualize import plot_forecast, plot_allocation def main(): # 1. 数据加载与清洗 df load_raw_data(data/raw/demand.csv) df clean_data(df) # 2. 预测未来需求 model, y_test, y_pred train_predict_model(df) # 3. 预测结果可视化 plot_forecast(y_test, y_pred, save_pathoutput/figures/forecast.png) # 4. 将预测结果作为优化模型的输入 demand_list y_pred.round().astype(int).values plan_result optimize_plan(demand_list) if plan_result: # 5. 优化结果可视化 plot_allocation(plan_result, demand_list, save_pathoutput/figures/allocation.png) print(复现流程运行完成) if __name__ __main__: main()运行主流程cd 2025_guosai_B_reproduction python src/main.py预期输出类似数据维度: (366, 2) MAE: 1.2034 RMSE: 1.5011 R2: 0.9821 最优目标值: 17350.0 复现流程运行完成实际数值会因为随机种子、数据分布、模型参数不同而变化这里只展示输出格式。5. 复现中的常见问题与排查思路复现过程中最消耗时间的往往不是算法本身而是环境、数据和求解器带来的各种问题。下面整理了一份高频问题清单。问题现象常见原因解决思路ModuleNotFoundError: No module named pulp未安装 Pulp 库执行 pip install pulp或写入 requirements.txt 统一安装图片中文显示为方框系统缺少中文字体或 Matplotlib 未配置设置 plt.rcParams[font.sans-serif]并使用系统已安装的字体时间序列预测 R² 很低数据泄漏 / 特征工程不足 / 数据量太少按时间顺序切分数据增加滞后特征与滚动窗口特征线性规划求解结果为 Infeasible约束条件之间矛盾或变量范围设置过小逐个检查约束先去掉 0-1 启动约束测试可行性数据中出现 NaN建模报错缺失值未处理或滞后特征导致首行无值使用 ffill interpolate并在建模前 dropna预测结果出现负值模型没有对输出做下限约束使用 np.clip(0, None) 对预测结果截断或选择适合非负数据的模型多次运行结果不一致模型存在随机性设置 random_state42并固定全局随机种子针对最后一个问题可以在主流程开头加上全局随机种子import numpy as np import random random.seed(42) np.random.seed(42)如果使用了 Scikit-learn 模型还需要在模型初始化时设置random_state代码中的随机森林已经这样做了。6. 建模方法与工程实践建议6.1 先做 Baseline再优化模型复现优秀论文时不要一开始就追求和论文一模一样的复杂模型。先用线性回归或随机森林跑出一个 Baseline确认数据、评估指标、可视化流程都正确再逐步增加复杂度。这样做的好处是如果后面模型出问题可以快速判断是数据问题还是模型问题。6.2 固定随机种子保证结果可复现竞赛论文和代码都强调可复现性。在项目入口处固定随机种子不仅方便自己调试也能让论文中的结果经得起验证。import numpy as np import random random.seed(42) np.random.seed(42)如果使用 PyTorch 等深度学习框架还需要额外设置torch.manual_seed。6.3 模块化开发避免“一个文件跑到底”优秀论文复现通常涉及多个模型、多次实验。建议按模块拆分每个模块只做一件事数据处理模块只负责数据读取、清洗、特征工程。模型训练模块只负责训练和评估。可视化模块只负责绘图和保存。这样后期替换数据、调整参数、对比实验结果时改动范围可控。6.4 重视模型评估与灵敏度分析许多同学复现论文时只关注“模型能不能跑出结果”忽略了评估和灵敏度分析。实际上国赛论文中灵敏度分析是评委重点看的内容之一。通用的做法是对模型中的关键参数进行扫描观察目标函数或输出指标的变化趋势。例如def sensitivity_analysis(base_demand, step0.05, times5): results [] for i in range(-times, times 1): scale 1 i * step demand_adjusted base_demand * scale plan optimize_plan(demand_adjusted.round().astype(int)) results.append((round(scale, 2), plan)) return results如果调整参数 5% 时结果波动剧烈说明模型稳定性不足如果波动较小说明模型具有一定的鲁棒性。这个分析结果可以直接用于论文中的“灵敏度分析”章节。6.5 关于数据安全与代码规范在竞赛准备中使用的数据通常来自题目附件或公开数据集。复现优秀论文时也要注意学术诚信不直接抄袭论文中的大段文字和公式要理解后用自己的语言表达。不非法获取其他参赛队伍的代码和数据。引用公开数据时说明数据来源。涉及真实业务数据时遵循最小权限原则不传播敏感数据。7. 总结与下一步学习路线本文围绕 2025 年国赛 B 题优秀论文的复现梳理了一套完整的技术路径从读题拆解、数据处理到预测模型、优化模型再到可视化和常见排错。文中给出的代码虽然使用了模拟数据但流程和真实复现完全一致。你可以把数据预处理、预测、优化、绘图四个模块直接迁移到自己的项目中使用。下一步建议按以下顺序继续深入先找一篇近两年的国赛 B 题优秀论文按照本文的五步流程复现一遍然后替换成题目附带的真实数据调整特征工程和模型参数让结果尽可能接近论文再尝试改进论文中的模型看能否在评价指标上超越它最后把复现过程中积累的代码、图表模板和方法论整理成自己的建模工具箱。复现优秀论文的价值不只是在比赛前“临时抱佛脚”而是真正把别人的建模思想转化为自己的工程能力。如果你在复现过程中遇到数据、代码或求解器相关的问题欢迎收藏这篇文章按章节索引排查。祝你备赛顺利。
网站建设高端定制企业官网