简介:2021电工杯数学建模B题完整解析与案例程序合集,面向备赛大学生及电气工程领域建模爱好者,帮助读者掌握从问题抽象、模型构建到程序求解的完整流程。包体共70个文件,以37个MAT数据文件、12个M函数脚本为主体,并含3个XLSX数据表、7个TIF与3个PNG图示、PDF及Word说明文档等,整体约22.71MB,便于对照学习和二次运行。已有3906人学习下载。内容涵盖B题背景分析、思路拆解及可执行MATLAB代码,涉及电气工程领域的数值建模与算法设计等典型应用;程序结构完整、模块化,既适合新手逐行理解数学建模实现细节,也方便有经验者在此基础上改进优化算法。通过研究这些案例,可以直观体会数学工具与工程问题结合的方法论,为后续参赛或科研实践提供可复用的参考模板,尤其适合赛前冲刺与赛后复盘。
1. 2021电工杯数学建模B题:一份压缩包背后,是一次完整的建模能力检验
2021电工杯数学建模B题,表面看是一道电力系统的优化或预测题,实际上考察的是从数据处理到模型选择再到论文呈现的全流程能力。拿到「2021电工杯数学建模B题及案例程序.rar」这类资源时,新手的第一反应是解压、跑代码、看着结果出来就以为完事,但真正让这份资源产生价值的做法完全不同:先读题、再拆问题、然后拿案例程序当参照系去验证自己的建模思路。我见过太多人在这一步翻车——程序能跑但不懂每一步在算什么,论文写出来和自己的程序对不上,最后评委一眼看穿。这份笔记就围绕「怎么把一道B题做成能拿得出手的方案」展开,覆盖选题判断、问题拆解、模型落地、案例程序复现和避坑,适合正在备战数学建模竞赛、想用真实题目练手的人。
2. 拿到B题先别急着写代码:判断题目类型与问题拆解方法
2.1 为什么B题总让人「看着会做、动手不会」
电工杯B题的常见题材集中在配电网优化、负荷预测、新能源出力调度、设备状态评估这几个方向。和A题偏机理推导不同,B题通常给一堆实际运行数据,要求从数据里挖出规律再做决策,所以它天然带着「数据+优化」和「数据+预测」两种套路。问题在于很多选手一看到Excel里的几千行数据就慌了,直接开始调库跑回归,跑完发现拟合效果很差,却不知道原因是数据清洗没做透还是特征工程方向错了。
先判断题目属于哪一类,直接决定后续用什么模型。预测类题目会明确给历史序列,要求预测未来某时段的值,这种有现成的时序模型套路;优化类题目会给供需关系、成本系数、约束条件,要求出一个调度或配置方案,这种核心是建模和求解算法。还有一类是评价类,给多个指标让你排序或分等,这种往往可以叠加在预测或优化之上,作为辅助结果。拿到题先花半小时做这个三分类,比急着写代码值钱得多。
2.2 从题目文本里提取建模要件的三步走
第一步,把题目的自然语言翻译成数学语言。题目里出现的「不超过」「至少」「在……范围内」全是约束条件;「使得……最小/最大」是目标函数;「某地区某时段」是时空范围。把这些词全部划线标注,逐句转写成约束或目标,这一步做完,模型的骨架就出来了。
第二步,明确数据的每一列是什么角色。B题给的数据表通常分三类:描述性属性(地区、设备类型)、时序特征(日期、时段、历史负荷)、目标变量(待预测值或待优化的量)。我一般会在这一步画一张简易表格,把列名、类型、缺失情况、取值范围全部列出来,这张表后面写论文时可以直接用。
第三步,找出题目里隐藏的约束。这类隐藏约束最常出现在电网场景:线路容量限制不会明说让你从某张表里读,而是藏在设备参数表里;爬坡约束不会出现在问题描述里,但要考虑机组调节速度;备用容量要求往往用一句话带过。漏掉一个隐藏约束,优化结果在数学上可行,在实际工程场景里不可行,评委里只要有业内人士这个硬伤非常明显。
3. 把B题拆成可计算的模型:数据预处理与建模选型
3.1 数据清洗的标准流程:缺失值、异常值、时间对齐
拿到的原始数据几乎不可能直接进模型,第一关是清洗。常见做法是先做缺失值统计,看每列的缺失比例。缺失低于5%的列用插值或均值填充,高于30%的列直接考虑剔除,中间的按业务场景判断。时间序列数据有个坑:单纯用均值填充会让序列在填充点出现跳变,后续做差分时会产生假信号,我一般优先用前后时刻的线性插值,实在不行再退到同类均值。
异常值处理要结合业务规则。负荷数据里出现负值、温度数据里出现超过物理极限的值,都属于明显异常。更隐蔽的是「数值在合理范围但时间点不对」的情况,比如凌晨3点的负荷突然飙到白天峰值,这种要先按时间维度做可视化,把全序列画出来再逐个看异常点,不要用箱线图一刀切。
时间对齐是一个容易忽略的环节。题目给的天气数据和负荷数据可能不在同一个时间粒度上,有的按小时,有的按15分钟,还有的带时区问题。统一时间粒度需要先确定最小公共粒度,再做重采样。重采样时注意聚合方式:负荷数据在降采样时用均值,但风速数据用均值会抹掉极端值,可以考虑用最大或最小值,这取决于题目关注的是峰值还是总量。
import pandas as pd import numpy as np # 读取原始数据,date列先转成datetime类型 df = pd.read_csv('load_data.csv', parse_dates=['date']) df = df.sort_values('date') # 缺失值处理:优先线性插值,保留原序列趋势 df['load'] = df['load'].interpolate(method='linear', limit_direction='both') # 异常值处理:按物理可行性过滤,负荷不为负 df = df[df['load'] >= 0] # 时间对齐:统一重采样为小时粒度,负荷取均值,风速取最大值 df = df.set_index('date').resample('H').agg({ 'load': 'mean', 'wind_speed': 'max', 'temperature': 'mean' }).dropna()这段代码的要点在于interpolate方法的limit_direction设为both,保证序列开头和结尾的缺失也能被处理;重采样时每列用不同的聚合函数,这是很多人在这一步偷懒直接用mean导致特征被抹平的地方。参数上,resample的H表示小时粒度,如果要15分钟就是15min,具体选什么粒度取决于题目给出的预测步长。
3.2 预测类B题的模型选型逻辑与基线模型
预测类B题在电工杯里出现频率最高,选型上有一条务实的路线:先用简单模型做基线,再看是否需要升级到复杂模型。基线模型推荐用历史均值法或线性回归,这两个模型的意义不是拿来当最终答案,而是给出一个「及格线」。如果你后续的模型连及格线都打不过,说明特征工程或数据预处理出了问题,而不是模型不够高级。
在基线上再往上走,常用的是随机森林、XGBoost、LSTM这三档。随机森林适合特征维度中等、数据量几千条的场景,训练快,可解释性好,论文里好写。XGBoost在特征工程做得好的前提下通常能压过随机森林,但要调参,调不好会过拟合。LSTM适合长序列依赖的场景,比如用前72小时预测未来24小时,但训练时间长,样本量不够时很容易不如树模型。
选型的原则是:先用随机森林跑通全流程,包括数据清洗、特征构建、训练、预测、画图、写论文,保证每个环节都通了,再考虑用XGBoost替换提升精度。不要一上来就上LSTM,因为LSTM对数据规模和特征标准化要求高,前面任何一步没做好都会把问题归因到模型上,排查成本很高。
from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error # 构建特征:滞后值是最有效的特征 for lag in [1, 2, 24, 48, 72]: df[f'lag_{lag}'] = df['load'].shift(lag) # 时间特征:小时、星期、是否工作日 df['hour'] = df.index.hour df['weekday'] = df.index.weekday df['is_workday'] = (df['weekday'] < 5).astype(int) # 去掉带NaN的行,按时间顺序切分,不用随机切分 df_feat = df.dropna().sort_index() features = ['lag_1', 'lag_2', 'lag_24', 'lag_48', 'lag_72', 'hour', 'weekday', 'is_workday', 'temperature'] X = df_feat[features] y = df_feat['load'] # 时间序列切分:前80%训练,后20%验证 split_idx = int(len(X) * 0.8) X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_val = y.iloc[:split_idx], y.iloc[split_idx:] model = RandomForestRegressor(n_estimators=300, max_depth=10, random_state=42) model.fit(X_train, y_train) y_pred = model.predict(X_val) print(f'验证集MAE: {mean_absolute_error(y_val, y_pred):.2f}')滞后值特征在负荷预测里几乎是最强的特征组合,因为电力负荷有强烈的自相关性,今天的整点负荷和昨天同一时刻高度相关。lag_1和lag_2捕捉短期惯性,lag_24和lag_48捕捉日周期性,lag_72捕捉星期效应。时间特征里的is_workday对电工类场景特别重要,工作日和周末的负荷曲线形态明显不同。切分用时间顺序而非随机打乱,这是时序任务和普通回归任务最本质的区别。
4. 案例程序怎么用:从跑通到读懂再到改造
4.1 拿到.rar先做的三件事:解压结构分析、环境检查、数据核对
解压之后不要急着双击运行,先看目录结构。一个规范的案例程序包通常包含这样几个部分:题目原文PDF、参考论文或说明文档、数据文件夹、主程序脚本、结果输出文件夹。看一眼目录能判断这个包是完整交付还是残缺资源,如果只有代码没有数据,那跑通了也没有意义,因为换一套数据代码大概率不可直接运行。
环境检查是下一步。看代码头部的import语句,把依赖库版本记下来,优先用虚拟环境安装。数学建模类程序最常见的依赖是numpy、pandas、matplotlib、scikit-learn、scipy,如果涉及深度学习会多出torch或tensorflow。版本问题在数学建模场景里很容易被忽略,比如sklearn的API在不同大版本间经常改参数名,老代码在新环境里直接报错。
数据核对最容易被跳过。案例程序里的数据是原始数据还是已经预处理过的数据,直接决定了你复现的路径。我一般会把数据文件单独拷出来,先做一次缺失值和量纲检查,再拿程序跑出来的结果和论文里给出的结果对比,如果对不上,优先怀疑数据版本不一致,其次是随机种子没有固定。
4.2 读懂案例程序的模块化拆解方法
案例程序通常几百行起步,从头到尾读完效率很低。我的做法是按数据流把程序切成四个模块:数据读取与清洗、特征构建、模型训练、结果输出与可视化。先找到main函数或程序入口,沿着它的调用顺序标出每个模块的起止行号,然后再逐模块读。
读代码时只盯三个问题:每个模块的输入是什么、输出是什么、和题目要求的对应关系是什么。特别是结果输出的部分,要对比题目要求的提交格式,很多案例程序输出的结果格式和竞赛要求的格式不一致,直接套用会在提交环节出问题。
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.metrics import mean_absolute_percentage_error def evaluate_prediction(y_true, y_pred): """计算多个误差指标,用于论文中的模型对比表""" mae = np.mean(np.abs(y_true - y_pred)) mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100 rmse = np.sqrt(np.mean((y_true - y_pred) ** 2)) return mae, mape, rmse # 假设data是已经跑好的预测结果 y_true = data['actual'].values y_pred = data['predict'].values mae, mape, rmse = evaluate_prediction(y_true, y_pred) print(f'MAE={mae:.2f}, MAPE={mape:.2f}%, RMSE={rmse:.2f}') # 将实际值与预测值对比图画出来,论文里必备 plt.figure(figsize=(12, 5)) plt.plot(y_true[:96], label='actual', linewidth=1.5) plt.plot(y_pred[:96], label='predict', linewidth=1.5, linestyle='--') plt.legend() plt.ylabel('load (MW)') plt.xlabel('hour') plt.savefig('prediction_compare.png', dpi=300) plt.close()这一段代码不是案例程序里会直接给的,但每个参赛队伍最终都需要自己补上。误差指标不能只算一个MAE,MAPE在负荷预测里更常用,因为它是不受量级影响的相对误差,写论文时不同模型的MAPE可以直接对比。画对比图时只画前96个小时,即前4天,因为曲线太多时图会糊成一团,肉眼无法判断好坏。
4.3 把案例程序改写成自己方案的四个落点
读懂之后就要动手改,四个地方几乎每次都要动。第一个是数据读取路径,把写死的绝对路径改成相对路径,或者改成命令行参数传入,这样换机器跑不用改代码。第二个是特征工程,案例程序里的特征组合是针对原题目的,你的题目如果换了时段或数据列名,特征必须跟着换。第三个是模型参数,n_estimators、learning_rate这类参数要重调,不能直接用原值。第四个是结果输出,输出格式必须对齐自己题目的提交要求。
改写的原则是「小步验证」。每改一个模块就运行一次,确认输出没有异常再改下一个,不要一次性大改然后从头调错。我见过有人把案例程序里所有模型都换掉了,结果报错之后不知道是新模型的参数问题还是数据问题,这种排查能把自己耗死。
5. 数学建模B题避坑指南:五个最容易丢分的隐蔽问题
5.1 数据泄露:滞后特征里藏着「未来信息」
现象:验证集MAE低得离谱,甚至接近0,但换到另一个时间段预测就完全废掉。 原因:构建滞后特征时,用整个数据集做了shift操作,包括验证集那一部分。训练时模型已经「见过」了验证集上一时刻的真实值,相当于提前拿到了答案。这个错误在案例程序里经常存在,因为它自己做的时候可能没有仔细切分。解决:必须在切分之后再构建特征,或者构建特征时按时间顺序逐段处理,确保训练集和验证集之间没有任何交叉。多提一句,这个坑堪称「数据泄露」,我见过有金奖入围论文在这里翻车。
5.2 时间序列切分用了随机打乱
现象:模型在训练集合验证集上都表现很好,但论文里评委追问「你的验证集为什么和训练集分布一样」,现场演示时模型预测完全跑偏。 原因:把数据随机切分,导致验证集里混入了训练集相邻时刻的数据,相关性太高。时间序列必须按时间顺序切分,或者用时间序列交叉验证。解决:前面代码里用的就是顺序切分,这个改动很简单,但很多人顺手就用了train_test_split的默认shuffle=True。检查自己的代码,确认shuffle=False或直接手动按索引切分。
5.3 优化类题目约束条件漏项
现象:优化结果非常完美,但仔细一核对发现某个设备容量超限了,或者某个时段的调节速度不合理。 原因:读题时只提取了显式约束,漏掉了藏在参数表或附录文字里的隐含约束。这类问题在电工杯B题里几乎是必考的,因为这些约束才是区分「学过建模」和「懂行业」的分水岭。解决:列约束清单时逐项标注出处,哪个约束来自哪个表哪个段落,论文里把约束和出处对应写清楚。
5.4 结果格式与题目要求不一致
现象:代码跑完,结果也在论文里画了图,但提交时发现题目标明了「以附件形式提交Excel,第一列时间,第二列数值」,你的输出是图片或顺序不对的表格。 原因:没有在前期抽出题目里的提交格式要求。数学建模竞赛里这类问题每年都有人丢分,属于最可惜的丢分项。解决:在拆题阶段就把「提交物清单」单独列出来,包括格式、列名、文件类型,程序最后直接生成对应格式的文件。
5.5 论文里的图和代码对不上
现象:答辩时评委指着论文里某个数据点问「这个值的来源是什么」,然后发现和分析过程对不上。 原因:改过代码重跑之后,没有同步更新论文里的图和数据。这类问题在临时改模型参数后最高发,论文里写的是随机森林结果,最后提交的代码是XGBoost版本的,截图还是旧图。解决:定稿前把论文里每个关键数字和图表重新核对一遍,我一般会把论文里的截图文件在重跑代码后全部重新生成,不保留旧版本。
6. 让案例程序真正变成你的能力:一个验证技巧和收尾习惯
用别人的案例程序做比赛,最大的风险不是看不懂,而是「看得懂但不会迁移」。我每次拿到一份案例程序,除了跑通和读懂之外,还会做一次「刻意破坏」:把数据里的一个关键特征删掉,看模型性能掉多少;把训练集从80%改成50%,看模型是否还能稳定收敛;把题目里的预测目标从小时粒度改成15分钟粒度,看代码结构的哪些部分需要动。这三件破坏性操作做完,你对这个程序的边界就有数了,知道哪里是核心逻辑不能动,哪里是胶水代码可以随便改。这个「降级」测试对你熟悉程序起的作用比细读十遍代码更大,也是你验证一个案例程序质量好坏的标尺——如果删掉一个特征模型性能几乎不变,说明特征冗余;如果数据减少一半就彻底崩了,说明模型容量和样本量本来就是勉强匹配。
建模能力的提升是靠「照着改、改到不像、再改回来」这三个循环完成的。我自己的习惯是每提交一份方案,会把中间过程里所有画过的图、调过的参数、报过的错截图存放在一个「过程记录」文件夹里,下次遇到相似题目时直接翻它,比回忆有效得多。
做数学建模比赛,跑通别人的程序是最容易的一步,把它变成能应对新题目的能力才是真正的收获。希望这份拆解能帮你少走几个弯路,尤其是切分和特征处理这两个坑,值得多花半小时验证清楚。
本文还有配套的精品资源,点击获取