深圳杯数学建模D题完整分析:从数据清洗到模型复现全攻略
简介2025深圳杯数学建模竞赛D题完整分析论文面向参赛团队与建模研究者提供从问题重述、问题分析到模型假设、符号定义的完整解题框架。资源为单个PDF文档大小2.71MB文档中给出可运行代码与相关数据内容方便对照论文步骤学习复现。已有330人学习/下载。论文重点围绕三个问题展开通过特征工程与分类模型解决核心分类任务并对模型总体性能及不同贡献者数量下的表现进行对比后续还涉及贡献者兼容性筛选、混合比例优化与组合匹配评分内容覆盖建模、求解、评估全流程。读者可借助目录中的问题分析、模型建立与求解、性能对比、重要特征分析等模块快速定位关键内容既可作为深圳杯竞赛的完整参考也是学习数学建模与机器学习落地思路的实用案例。1. 一份D题完整分析论文的本质模型、可运行代码与数据三件套把2025深圳杯数学建模竞赛D题完整分析论文这份PDF打开之前我建议你换一个心态它不是一篇用来“读”的论文而是一套用来“复现”的工程交付物。深圳杯的D题通常给的是真实场景的数据题目里很少有标准答案你交上去的是一整套“分析论文模型可运行代码数据”。这篇PDF就是这套交付物的打包形态——论文负责讲清楚你为什么这么做模型和代码负责证明这么做能跑通数据负责让每一步都可被检验。它适合没有完整走过一遍建模全流程、想照着一套成熟方案快速入门的人也适合拿了一套老赛题想复用到新题上的人。读完你至少能回答三个问题这道题在算什么、代码跑通要调哪些参数、交上去的论文里哪些数字必须和代码输出对得上。2. 从赛题到可计算问题数据字典、清洗与规范化处理的落地路径2.1 读题第一遍只做三件事变量、目标与约束D题之所以和课本习题不同是因为题干里经常藏着“伪变量”——看起来是数值其实是分类编码还有“延迟口径”——某些字段当天拿不到当天建模就不能用。我拿到题目第一遍绝不碰数据先用笔在纸上写下三件事题目让我预测或决策的对象是什么目标变量我有哪些可用的输入字段变量交付时对时间、单位、量纲有没有隐性约束。这三件事必须各写一句人话比如“D题要我们在给定的一段序列上预测下一时刻的排队长度”或“根据历史观测数据反推设备退化方程里的参数”。这句话要能复述给不参加比赛的同学听对方能听懂才算读完题。然后才开始翻附件。我一般会把这句人话贴在代码文件头部当注释后面所有清洗、建模都对着它做。这一步的真正作用是防止做到一半才发现目标变量选错——建模竞赛里这个错的代价是整个模型推倒重来。还要确认题目给的是单张表还是多张表是否需要按主键合并。D题常见情况是压缩包里放了十几个CSV命名还不统一。先把文件清单列出来记录每个文件的行数和列数这是后面写论文“数据来源与预处理”一节的第一手素材。2.2 用数据字典把原始数据变成可计算字段我建数据字典的推荐形式是表格每一条记录一个字段的四件事原始字段名、字段含义、数据类型、是否可直接用于建模。注意这里说的是“是否可直接用于建模”很多原始字段要先做一步变换比如时间戳要拆成小时、星期几ID编码要决定是保留还是丢弃。字段名含义类型是否可直接建模处理动作ts记录时间戳datetime否拆分出hour、weekday、is_weekenddevice_id设备/对象编号str否先看基数基数过大转目标编码value观测数值float是缺失与异常值另行处理label目标标签int是仅训练集预测目标测试集不提供造完字典后的清洗代码我的习惯是每一步都留一行注释因为这份代码会被评委或后续接手的人读。import pandas as pd import numpy as np df pd.read_csv(train_data.csv, parse_dates[ts]) # 从时间戳拆出建模可用的时间特征 df[hour] df[ts].dt.hour df[weekday] df[ts].dt.weekday df[is_weekend] df[weekday].isin([5, 6]).astype(int) # 缺失值先看比例超过30%的列直接考虑丢弃或重点标记 miss df.isna().mean().sort_values(ascendingFalse) print(miss[miss 0]) # 数值型字段做分位数裁剪防个别离群点带偏模型 for col in [value]: lo, hi df[col].quantile([0.01, 0.99]) df[col] df[col].clip(lo, hi)逻辑说明拆分时间字段是为了让模型能学习周期性规律缺失值比例这行print是决策依据不是装饰分位数裁剪把极端离群点限制在1%和99%分位数内比直接删行更保守。参数说明clip的01/99是常见默认如果数据噪声大可用0.5%/99.5%但阈值设太小会误伤正常极值写论文时记得注明这个比例并说明为什么。提示数据字典这张表在论文里可以直接复用为“变量说明表”也是答辩时被问得最多的一页字段口径一定要在前期定死。2.3 数据规范化滑动窗口滤波与缺失值插补的最小方案D题数据多是采集类数据来自传感器、排队记录、调度日志噪声和不完整是常态。我处理这类现场数据的固定组合是滑动窗口滤波去掉毛刺再对缺失点做时间上合理的插补。滑动窗口滤波模型在这里的正确用法不是套一个高深算法而是看字段特性平稳缓变的量用滚动均值有突变峰谷的量用滚动中值。窗口大小先看数据的时间间隔采样间隔5分钟的数据窗口设15到30分钟3到6个点就够窗口设太大真信号也会被抹平这是最常见的翻车原因。# 对 value 做滑动窗口中值滤波窗口5对应25分钟 df[value_filt] df[value].rolling(window5, centerTrue, min_periods1).median() # 缺失点按时间顺序线性插补两侧都允许补齐 df[value_filt] df[value_filt].interpolate(methodlinear, limit_directionboth) # 滤波前后标准差对比用于写论文的预处理效果说明 print(df[value].std(), df[value_filt].std())逻辑说明centerTrue让窗口对齐当前点而不产生滞后min_periods1保证窗口边缘不直接变成NaNinterpolate处理滤波后可能残留的空洞。print那行是给论文准备的用一个数字说明去噪幅度比空泛的“有效降低噪声”有说服力得多。参数说明窗口5对应25分钟只在这个数据粒度下成立如果采样间隔变成10秒窗口5只覆盖50秒噪声根本压不住所以窗口必须按真实时间跨度重新算别照抄数字。数据规范化处理归一化或标准化要放在特征做完之后、模型训练之前不要放在清洗阶段。因为测试集必须复用训练集算出的均值和标准差顺序错了后面埋的雷会让你复现时分数对不上。到这一步你已经把“原始数据”变成了“可计算字段”下一章才是选模型。3. 模型选型与实现回归基线、时序特征与参数校准三条路线3.1 回归/预测类问题LightGBM做baseline的三个必调参数D题大多数题眼最终落在“预测一个连续值”这类问题我不用花哨模型打底LightGBM回归模型是性价比最高的baseline训练快、对表格数据几乎没有预处理要求、能输出特征重要性给论文用。三个必调参数我一般先定下来n_estimators先给300配合early_stopping看最优轮数learning_rate从0.05起步低了训练慢高了验证集分数不稳num_leaves在小样本上给31以下大样本给63以内这个参数直接控制过拟合风险。为什么只先调这三个它们对应“学多少轮、每步学多大、每棵树多宽”是梯度提升树里最影响最终分数的一组。其他像feature_fraction、reg_alpha这些等验证集分数进入平台期再动。import lightgbm as lgb from sklearn.model_selection import train_test_split feats [c for c in df.columns if c not in [label, ts]] X df[feats] y df[label] X_tr, X_va, y_tr, y_va train_test_split(X, y, test_size0.2, random_state42) model lgb.LGBMRegressor( n_estimators300, learning_rate0.05, num_leaves31, random_state42, verbose-1, ) model.fit( X_tr, y_tr, eval_set[(X_va, y_va)], callbacks[lgb.early_stopping(50), lgb.log_evaluation(50)], ) # 验证集RMSE与特征重要性两项都会进论文 from sklearn.metrics import mean_squared_error pred model.predict(X_va) print(mean_squared_error(y_va, pred) ** 0.5) print(sorted(zip(feats, model.feature_importances_), keylambda x: -x[1]))逻辑说明train_test_split固定random_state是必须的否则复现时分数对不上early_stopping的50表示连续50轮验证集不提升就停它会打印实际用到的树轮数用来反推n_estimators设置是否合理RMSE和特征重要性这两行输出直接就是论文“模型结果分析与讨论”章节的素材不要只跑到分数满意就收手。参数说明如果数据量小于5000建议num_leaves降到15并设置max_depth6如果训练集和验证集分布差异明显把test_size提到0.3分数会更稳。3.2 时序趋势类问题滑动窗口构造特征与LSTM兜底如果D题明确说“按时间顺序预测未来一段”就要警惕普通随机划分造成的时序泄漏。常见做法是先把上一节做的滑动窗口统计量变成新特征——滞后1步、2步、3步的值窗口均值、窗口标准差——让树模型也能看到“历史形状”。只有当题目强调长序列依赖、且数据量在万级以上才值得上LSTM。我第一次做这类题就踩过坑数据量只有几千条就上了LSTM训练集分数漂亮对未来的预测还不如滑动窗口加LightGBM。现在我的原则是先用特征版树模型打出基线LSTM作为对比模型写进论文而不是作为唯一答案。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.callbacks import EarlyStopping def make_windows(seq, lookback12): Xs, ys [], [] for i in range(lookback, len(seq)): Xs.append(seq[i - lookback:i]) ys.append(seq[i]) return np.array(Xs), np.array(ys) # data_seq 需要是已做完清洗与规范化的单变量序列 X_lstm, y_lstm make_windows(data_seq, lookback12) split int(len(X_lstm) * 0.8) X_tr, X_va X_lstm[:split], X_lstm[split:] y_tr, y_va y_lstm[:split], y_lstm[split:] model Sequential([ LSTM(32, input_shape(X_tr.shape[1], X_tr.shape[2])), Dense(1), ]) model.compile(optimizeradam, lossmse) model.fit( X_tr, y_tr, validation_data(X_va, y_va), epochs30, batch_size64, verbose0, callbacks[EarlyStopping(patience5, restore_best_weightsTrue)], )逻辑说明make_windows用lookback12把连续12个时间步拼成一个样本LSTM看到的就是这一段历史时间序列不能随机切分所以前面手动按位置切80/20而不是用train_test_splitLSTM(32)里的32是隐状态维度对这个规模的题够用加了EarlyStopping后训练轮数会自动停在合适位置。参数说明lookback先按数据周期设——如果有日周期且数据是小时级lookback24至少覆盖一天batch_size在数据量小时调到32更稳。3.3 机理/优化类问题参数校准才是得分点有些D题不是纯数据题会给出物理、经济或运筹背景的方程要求根据观测数据反推方程里的参数。这种题的关键不在模型多高级而在参数校准做没做干净。常见做法是先把赛题给的方程写成Python函数再用scipy的curve_fit或网格搜索去拟合观测数据得到一组参数后还要做残差分析。评审看这类论文时最在意的不是拟合曲线多漂亮而是你给没给出参数的不确定范围、有没有说明初值怎么来的。from scipy.optimize import curve_fit # 赛题给定的机理模型y a * exp(-b * t) c def model_func(t, a, b, c): return a * np.exp(-b * t) c # t_obs、y_obs 是从附件读出的观测列 p0 [1.0, 0.1, 0.0] # 初值先画散点估计量级不能全给1 popt, pcov curve_fit(model_func, t_obs, y_obs, p0p0, maxfev10000) print(popt) # 校准出的模型参数 print(np.sqrt(np.diag(pcov))) # 参数标准差论文里给区间用逻辑说明p0初值是curve_fit最容易翻车的地方指数函数对初值敏感给全1经常不收敛print标准差这行是论文里参数置信区间的来源不要忽略。参数说明maxfev默认值在复杂方程上不够5000到10000是常用量级如果拟合残差呈明显曲线状而不是随机散布说明方程形式不对要回到赛题重新审视假设而不是继续调参。4. 把分析论文变成可复现交付代码骨架、参数表与图表导出4.1 一套跑通全流程的最小代码骨架D题交付时接手的人第一眼看的是“能不能一次跑通”。所以我习惯把代码组织成五段式的单一脚本读数据、清洗、特征、建模、导出。比赛期间可以拆成多个notebook随意折腾但交付版必须合并成能从命令行直接跑的脚本这是“完整分析论文含模型、可运行代码、数据”这类文件包里最核心的约定。# run_solution.pyD题完整流程骨架 # 段1 读入数据 df pd.read_csv(data/train.csv, parse_dates[ts]) # 段2 清洗与规范化 # 复用2.3的滑动窗口滤波与插补逻辑 # 段3 特征构造 # 时间特征 滞后特征 窗口统计量 # 段4 建模与验证 # LGBM / LSTM / 参数校准按赛题类型选一条路线 # 段5 导出结果 test pd.read_csv(data/test.csv, parse_dates[ts]) test[pred_label] model.predict(test[feats]) test[[id, pred_label]].to_csv(submit.csv, indexFalse) print(saved submit.csv)逻辑说明五段各有一个小标题注释运行报错时能一眼定位到段测试集预测必须放在最后一段避免前面改特征时手滑污染测试集。实际交付时把2.3和3.x的代码按段粘进来即可这种“注释分段单一入口”的组织方式比十几个notebook散着放更容易让评委信任。4.2 关键参数表决定论文数字的那几个参数可复现的代码必须配一张参数表否则三个月后你自己都说不清当时怎么跑出那个分数的。我在论文里习惯放一张“关键参数及设定理由”表它同时服务两个目的让读者知道哪些旋钮能调也让自己的复现过程有据可查。模块参数取值设定理由清洗滑动窗口大小5约25分钟中值滤波去毛刺保留信号形状特征滞后步数3覆盖最近3个采样点的趋势信息模型num_leaves31控制树宽防止小样本过拟合模型learning_rate0.05验证集分数与训练时间平衡点模型early_stopping50连续50轮不改善则停这张表的另一个作用是排错如果换了一版特征后分数异常下跌比对参数表就能判断是不是参数被无意改掉。我犯过把learning_rate从0.05改回0.1导致分数跳变的错有了参数表这类问题五分钟内就能定位。4.3 结果导出与图表论文里的图和表怎么生成论文里最不能编的就是图和表它们必须能从代码输出直接复现。我一般固定生成三张图预测值与真实值散点图、残差分布图、特征重要性条形图。散点图看整体拟合残差图看是否存在系统性偏差特征重要性图支撑论文里的“影响因素分析”小节。这三张图用一套代码就能出。import matplotlib.pyplot as plt fig, ax plt.subplots(1, 2, figsize(10, 4)) ax[0].scatter(y_va, pred, s8, alpha0.5) ax[0].plot([y_va.min(), y_va.max()], [y_va.min(), y_va.max()], r--) ax[0].set_xlabel(true) ax[0].set_ylabel(pred) resid y_va - pred ax[1].hist(resid, bins50) ax[1].set_xlabel(residual) plt.tight_layout() plt.savefig(fig_result.png, dpi300) print(saved fig_result.png)逻辑说明红色虚线是yx参考线点越贴线说明预测越准残差直方图若明显偏在一侧、均值不为0说明模型有系统偏差要回到特征或模型去调而不是继续画图。参数说明dpi300是论文印刷提交的标准值别用屏幕默认的72否则图放进PDF里会发虚。所有图表文件名做到见名知意fig_result、fig_residual、fig_importance和论文里的插图编号一一对应。5. 避坑数据、模型与写论文最容易翻车的5个现场5.1 复现时分数对不上随机种子与数据规范化顺序现象同一份代码昨天跑和今天跑RMSE差一截换个同学跑又不一样。 原因没固定随机种子或者数据规范化处理是在训练集和测试集合并后一起做的导致测试集信息泄漏换环境重跑就露馅。 解决在脚本最前面固定随机种子先fit训练集的标准化器再用同一个标准化器变换测试集。这两条写进4.2的参数表论文附录里也贴出来答辩时被问“能否复现”可以直接指给评审看。5.2 模型分数虚高但交付被质疑用未来数据做了特征现象论文里验证集分数非常漂亮但换到测试集上一塌糊涂评委一问就露馅。 原因构造滞后特征时把当前时刻之后的数据混进了样本最常见的是用整个序列的均值、最大值做特征或者是用了未对齐的未来窗口统计量。 解决滞后特征只允许取t-1、t-2这样的历史值窗口统计量必须用centerFalse的滑动窗口确保只看见过去。我一般在特征函数里加一行断言检查窗口终点索引不大于样本时刻跑错了直接报错而不是带着错误特征训练完。5.3 滑动窗口把信号抹平了现象滤波后预测曲线比真实曲线平滑很多峰谷全没了论文图表很难看。 原因窗口设太大或者对突变型字段用了均值滤波。均值对离群点敏感一个尖峰会拉高整段窗口把真实突变也滤掉了。 解决按字段特性选窗口类型和大小。缓变量用均值突变变量用中值窗口跨度保持在数据主体周期的一半以内。比如有日周期的小时数据窗口不超过12小时采样间隔5分钟的数据窗口设5到7个点足够。5.4 LSTM在小样本上过拟合现象训练集loss一路下降验证集loss先降后升预测未来时输出几乎是一条直线。 原因数据量不够模型把训练集的噪声当成规律记住了。几千条数据的时序硬上LSTM是这类题里最高频的翻车。 解决先跑LightGBM拿基线LSTM作为对比模型LSTM隐状态维度降到16到32加Dropout层配早停。论文里把两个模型的结果并列展示评委反而觉得你做了充分的模型对比而不是只押宝一个黑匣子。5.5 论文里的数字和代码输出对不上现象论文结果表写着RMSE0.512但代码跑出来是0.534答辩被要求现场跑代码就下不来台。 原因论文写的是某次调参中最好的数字或手误复制了旧结果代码改过但论文没同步改。 解决论文定稿前一天把代码从头跑一遍所有论文表格里的数字用代码打印结果覆盖跑不通就改代码不要改论文。这是数学建模竞赛论文最硬的一条纪律。血泪经验是凡是能编出来的数字现场复现时都会找你讨债。6. 验证方法与进阶技巧让手上的模型和数据能复用到底验证这套方案能不能复用我推荐做两件事。第一用K折交叉验证替代单次划分时间序数据用滚动起点交叉验证从不同起点切训练段和验证段看多次结果均值而不是赌一次随机划分的运气。第二做一个“最小复现测试”把训练数据裁到1000行、特征裁到5个确认脚本能从零跑到出结果。这个测试能在交付前暴露八成环境问题——依赖缺失、路径写死、缓存污染都在这个环节现形。我习惯交付前把代码放进一个干净空目录只保留数据和脚本跑一遍再打包这个操作救过我很多次。进阶技巧是给特征和模型各留一个易开关的配置特征开关控制是否启用滞后特征模型开关控制走LGBM还是LSTM还是参数校准路线。这样复用新赛题时不用重写代码只要把数据字典和参数表替换掉。我上一次复用这套骨架解新题清洗段和建模段原样保留只重写了特征段和参数表从拿到题到出第一版结果只花了一个晚上。验证结果时我习惯用一个最简单的判据建立的模型在验证集上的误差必须显著小于“用昨天值直接当今天预测值”的朴素基线。如果连这个都赢不了说明特征或模型环节有问题不要急着写论文。把这个判断标准写成一段话放进论文的模型验证小节比贴一张很大的loss曲线更有说服力。最后说一个我的个人习惯所有代码里只保留最终交付版不要留几十个notebook副本那份D题完整分析论文交出去之前我只允许自己保留一个主脚本、一张参数表、一个结果输出目录。这样无论是回顾还是被追问都不会发生“这份数字到底是哪个notebook跑出来的”这种尴尬。希望帮到你。本文还有配套的精品资源点击获取