资讯动态

BP神经网络MATLAB实战:城市电网负荷预测完整代码与误差评估

发布时间:2026/10/12 21:14:12 来源:尧图企业网站定制
简介这份资源面向城市电网负荷预测方向的研究者与高年级本科生基于MATLAB环境实现BP神经网络建模解决历史负荷数据训练、预测精度评估与结果可视化等典型问题。包内代码结构完整包含主程序、网络训练脚本及误差评价函数并配套实际电网负荷数据可直接运行或替换数据扩展应用。资源压缩包共11个文件涵盖4个Matlab脚本.m、2个MAT数据文件.mat、1个Excel数据表.xlsx及4张结果图.png整体大小585KB文件组织清晰便于对照代码理解预测流程。目前已有175人学习使用适合具备初步神经网络基础、希望快速上手BP负荷预测实践的用户。作者对代码添加了注释若运行遇到问题可私信咨询也可根据个性化场景联系博主进行功能扩展或算法改进。1. 城市电网负荷预测BP 神经网络 MATLAB 完整代码包从数据处理到误差评估全链路跑通城市电网负荷预测里最磨人的一个环节不是 BP 网络那几行训练代码而是从原始 Excel 数据到误差评估的完整链路数据对齐、缺失值处理、归一化、滑动窗口造样本、训练、反归一化、指标计算任何一步出错都会一路传导到最后的预测曲线。这个 MATLAB 代码包索性把整条链路做成了可以直接复跑的项目ww.xlsx 原始负荷数据、maydata.mat 样本集、main0.m 数据处理脚本、mainbp1.m 训练主程序、MSE_RMSE_MBE_MAE.m 误差评估、R_2.m 拟合优度计算代码有注释替换数据就能重新训练预测。适合本科毕设和课程设计也适合已经持有用电数据、想快速搭一个 BP 基线再和 LSTM 做对比的工程师。2. 数据准备与网络设计从 ww.xlsx 原始表到 maydata.mat 样本集2.1 读入 Excel 数据表头、缺失值与结构确认拿到 ww.xlsx 的第一件事不是急着跑网络而是先看清原始表的结构。城市电网负荷表的常规格式是每一行对应一个采样时刻列包含负荷值、日期时间可能还会带温度、星期类型等辅助特征。不同来源的表列名和排列顺序差异很大所以我习惯先用 readtable 打开打印变量名和行数确认哪些列作为输入特征、哪一列是预测目标% 第一步预览 ww.xlsx 的表头与行数确认数据结构 data_table readtable(ww.xlsx); disp(data_table.Properties.VariableNames); % 打印所有列名 disp(height(data_table)); % 打印行数 % 如果表头是中文readtable 会自动转成 Var1、Var2 这种占位列名这一步能提前暴露两个高频问题一是表头被读成 cell 类型后续参与数值运算直接报错二是某列存在缺失值比如凌晨负荷记录因为采集设备故障整段为空。对负荷这类连续时序数据我一般用 fillmissing 做线性插值补齐因为负荷曲线在相邻时刻有连续性线性插值比填零更接近真实变化% 剔除时间列后对每个特征列做线性插值填充缺失值 raw_matrix table2array(data_table(:, 2:end)); for col 1:size(raw_matrix, 2) raw_matrix(:, col) fillmissing(raw_matrix(:, col), linear); end % fillmissing 的 linear 模式遇到边界缺失时会用第一个有效值向后延展插值处理完还要看一眼时间列是否完整。如果连续几天的数据整段缺失线性插值就不合适了我会把这段直接标记为异常用前两年的同期数据替换而不是让网络去学一段完全不存在的过渡曲线。这一步看着繁琐但它决定了后面所有步骤的数据质量值得多花十分钟检查。2.2 归一化与滑动窗口输入矩阵怎么组织才合理归一化的原因要讲清楚负荷值的单位是兆瓦数值可能在几百到几千之间波动而温度只有两位数星期类型是 1 到 7 的整数。量纲差异过大时数值大的特征会主导梯度更新数值小的特征相当于被淹没模型学不到它们和负荷之间的关联。所以统一缩放到一个区间是 BP 网络训练前的标准前置步骤。代码里我选择手写 min-max 公式而不是 mapminmax原因是手写公式每一步都可控反归一化时不用猜内置函数做了什么。mapminmax 按行归一化输入矩阵的行列组织方式一变结果就对不上手写则完全透明% 每个特征列做 min-max 归一化到 [0,1]边界保存用于反归一化 data_min min(raw_matrix, [], 1); data_max max(raw_matrix, [], 1); eps_val 1e-8; % 防止分子分母同时为零时除零 data_norm (raw_matrix - data_min) ./ (data_max - data_min eps_val);归一化之后要把时序数据组织成监督学习格式。BP 做时间序列预测的标准做法是滑动窗口用前 N 个时刻的特征预测第 N1 时刻的负荷。比如用前 24 个小时的负荷与温度预测下一小时负荷输入向量的维度就是 24 乘特征数。循环切窗口生成输入矩阵 X 和目标向量 Y% 滑动窗口构造样本对前 win 个时刻预测下一时刻 n size(data_norm, 1); win 24; % 窗口宽度按数据粒度调整 n_features size(data_norm, 2); X zeros(n - win, win * n_features); % 预先分配避免循环里动态扩容 Y zeros(n - win, 1); for i win1 : n % 窗口内每个时刻的特征逐行展平成一个长向量 X(i-win, :) reshape(data_norm(i-win : i-1, :), 1, []); Y(i-win, :) data_norm(i, 1); % 预测目标取第一列即负荷 end窗口宽度 win 的选择直接影响模型上限。窗口太短模型看不到历史趋势峰谷信息捕捉不到窗口太长输入维度翻倍训练变慢还容易引入冗余噪声。日负荷曲线有 24 小时周期性所以用 24 小时窗口预测下一小时是工程里最常见的起点这份代码里的 maydata.mat 应该也是按类似窗口逻辑组织好的直接 load 就能拿到成形的 X 和 Y。注意滑动窗口切出的样本之间高度重叠相邻两个样本只差一个时刻训练集内部信息重叠是在所难免的。做交叉验证时要意识到这一点随机 K 折会放大信息泄漏时序预测更合理的做法是留出法或按时间块切分。2.3 训练集与测试集划分时序数据不能随机乱序分类任务里随手 randperm 乱序划分是常规操作但这个习惯放到时序预测里就是大坑。负荷数据带时间顺序测试集永远在时间上落在训练集之后才符合“用过去预测未来”的真实场景。如果乱序划分模型等于看到了未来数据测试集误差失真报告里的 R² 再漂亮也没有落地意义。% 按时间顺序划分前 80% 训练后 20% 测试 split_ratio 0.8; idx_split round(split_ratio * size(X, 1)); X_train X(1:idx_split, :); Y_train Y(1:idx_split, :); X_test X(idx_split1:end, :); Y_test Y(idx_split1:end, :);划分完成后把训练集和测试集对应的时间范围打印出来作为日志留底。这一步在实际项目中很实用如果测试集恰好覆盖了春节前后或极端高温日误差偏大是正常的业务现象不代表模型坏了。有了时间范围在手上向别人解释误差来源时也能做到有理有据。3. 网络搭建与训练main0.m 与 mainbp1.m 的分工3.1 三层 BP 网络结构隐藏层神经元个数怎么定BP 网络的设计核心是三层结构输入层节点数、隐藏层节点数、输出层节点数以及每层的激活函数。输入节点数由滑动窗口生成的矩阵宽度决定即 win 乘特征数输出节点数就是预测目标维度单步负荷预测取 1。最需要调的是隐藏层节点数这个问题没有严格公式工程上通常先用经验法则框一个范围再逐个试凑。经验法则隐藏层节点数 h 取输入维度 n_in 与输出维度之间的中间值再叠加一个 1 到 10 的修正量写成 round(sqrt(n_in n_out) a)。试凑不是玄学本质是小范围网格搜索在 5、10、15、20 几个档位各训练一次观察测试集误差选误差最小且训练时间可接受的值。对应 BP 神经网络结构图就是输入层-隐藏层-输出层的经典三层拓扑激活函数隐藏层用 tansig、输出层用 purelin 是回归任务的标准配置% 构造三层前馈网络 n_in size(X_train, 2); h 15; % 隐藏层节点数先按经验值设定 net feedforwardnet(h); % 生成默认结构 net.layers{1}.transferFcn tansig; % 隐藏层双曲正切激活 net.layers{2}.transferFcn purelin; % 输出层线性激活这里有个容易忽略的点输出层如果用 tansig输出会被钳在 -1 到 1 之间。如果 Y 归一化到 [0,1]输出层却是 tansig模型的输出区间和目标区间不匹配误差永远降不到理想水平所以输出层保持 purelin 才稳妥。3.2 训练函数与参数trainlm 为什么是首选学习率怎么调MATLAB 神经网络工具箱提供了多种训练函数trainlm 也就是 Levenberg-Marquardt 算法是中小规模回归问题的经典首选。它融合了梯度下降与高斯-牛顿法收敛快、精度高对单步负荷预测这种输入输出维度不算夸张的场景足够用。代价是内存占用偏高因为它要计算近似的 Hessian 矩阵样本量超过几万条时可以考虑换 trainbr 或 traingd。学习率 lr 是最敏感的调参对象。lr 设太大权重更新震荡损失曲线上下跳动设太小收敛慢跑几百轮还在原地打转。0.01 是我常用的起点训练后看损失曲线的下降节奏再决定往上还是往下调。trainlm 自带二阶信息对 lr 的敏感度低于 traingd所以很多人用 trainlm 时干脆不调 lr 也能出结果但跑到 traingd 就必须认真对待这个参数。% 设置训练参数 net.trainFcn trainlm; % 训练算法Levenberg-Marquardt net.trainParam.lr 0.01; % 学习率 net.trainParam.epochs 1000; % 最大迭代轮数 net.trainParam.goal 1e-5; % 目标误差达到即提前停止 net.trainParam.min_grad 1e-7; % 梯度下限防止训练时间过长训练前还有一个关键操作把 MATLAB 默认的随机数据划分关掉。train 函数默认的 divideFcn 是 dividerand会在内部把数据随机拆成训练、验证、测试三份这和我们 2.3 节里自定义的测试集直接冲突等于测试集形同虚设。正确做法是改成按索引划分% 关闭默认随机划分改用自定义索引 net.divideFcn divideind; net.divideParam.trainInd 1:size(X_train, 1); net.divideParam.testInd size(X_train,1)1 : size(X_train,1)size(X_test,1); net.divideParam.valInd [];这个坑我踩过一次默认划分下训练脚本显示的误差非常漂亮但是用自己真实测试集一验证结果完全对不上排查半天才发现是 divideFcn 在中间截胡。从那以后看到“训练误差低、实测误差高”的报告我的第一反应就是查是不是内部随机划分在起作用。3.3 训练主程序与模型保存maynet.mat 里该存什么数据整理完毕、参数设置完毕调用 train 的代码其实很短% 注意train 函数要求每一列是一个样本与常规矩阵的每行一个样本相反 net train(net, X_train, Y_train); % 训练完成后直接用 sim 做预测输出再转置回来 Y_pred sim(net, X_test); Y_pred Y_pred;这段代码里X_train 的转置是 90% 初学者都会踩的点。MATLAB 神经网络工具箱对样本的约定是“每一列是一个样本”而我们习惯用“每一行是一个样本”差一个转置维度检查就过不去。如果报错 Inputs are incorrectly sized第一优先检查这里。训练完要把网络和归一化参数一起存下来。maynet.mat 里存的就是训练好的 net 对象load 进来直接 sim 就能预测不用重训。除了网络本身归一化的 min、max、窗口宽度、特征列数也应该一起保存否则反归一化时到处找参数很容易拿错数据集% 保存网络与配套参数后续预测脚本只依赖这一个文件 save(maynet.mat, net, data_min, data_max, win, n_features);保存前还应该确认 net 里的输出范围设置和当前归一化方式一致。如果换过数据重新归一化旧 net 的 processSettings 还记着老的边界直接 sim 输出的结果是错的。遇到这种情况要么重新训练再保存要么手动更新输出层的 processSettings不要偷懒复用旧模型。4. 误差评估与可视化MSE、RMSE、MBE、MAE 和 R_2.m 的读法4.1 先反归一化再算误差顺序错了等于白算模型输出是归一化空间里的预测值要和原始负荷对比必须先把预测值和真实值都还原到原始量纲。有人把归一化空间的误差打印出来看着数字很小但没有任何业务意义负荷单位是兆瓦归一化空间的 0.01 误差还原到原始空间可能是几十兆瓦的偏差完全不可用。反归一化时必须使用训练时保存的 data_min 和 data_max不能用测试集的边界。归一化是训练前定义的一个固定数据变换模型学的是这个固定变换下的映射测试时换边界相当于把输入空间平移了一段距离预测结果会整体偏移% 反归一化预测值和真实值都还原到原始负荷量纲 Y_pred_real Y_pred .* (data_max(1) - data_min(1) eps_val) data_min(1); Y_test_real Y_test .* (data_max(1) - data_min(1) eps_val) data_min(1);注意这里用的是 data_max(1) 和 data_min(1)。目标列是 raw_matrix 的第一列归一化时保存的边界向量也是按列顺序存放的取错列就会出现预测曲线比真实曲线整体高出一截或低出一截的现象。4.2 四个误差指标各自含义与适用场景MSE、RMSE、MBE、MAE 四个指标都容易算但互相之间不能完全替代。MSE 是均方误差平方操作放大了大误差的权重RMSE 是 MSE 开根号量纲回到原始单位直观反映典型偏差水平MAE 是绝对误差的平均对大误差不那么敏感MBE 是差值的均值正负号能判断模型是系统性高估还是低估。指标计算方式业务含义对大误差的敏感度MSEerrors 平方的均值训练监控用误差信号高RMSEsqrt(MSE)平均典型偏差单位与负荷一致高MAEerrors 绝对值的均值判断误差分布均匀性低MBEerrors 均值正负号指示高估或低估方向低工程诊断时常用 RMSE 和 MAE 的差值做判断RMSE 明显大于 MAE说明误差分布里有少数特别大的离群点多半对应极端天气日或数据异常日两者接近说明误差分布较均匀。MBE 接近零说明没有明显系统性偏移但 MBE 为较大的正数时模型整体高估了负荷需要回头检查输入特征里有没有随时间漂移的项。MSE_RMSE_MBE_MAE.m 这个脚本就是把四项指标按上述定义一次性算好打印出来% 四项误差指标一次计算 errors Y_pred_real - Y_test_real; MSE mean(errors .^ 2); RMSE sqrt(MSE); MAE mean(abs(errors)); MBE mean(errors); fprintf(MSE: %.4f\nRMSE: %.4f\nMAE: %.4f\nMBE: %.4f\n, MSE, RMSE, MAE, MBE);4.3 R_2.m 的读法决定系数与相关系数的区别R_2.m 算的是决定系数 R²公式为 1 减去残差平方和除以总平方和直观含义是相对直接用测试集均值做预测模型带来了多大比例的误差削减。R² 越接近 1模型对样本的解释力越强。但要分清是训练集上的 R² 还是测试集上的 R²只有测试集 R² 才是泛化能力的真实估计。R² 和相关系数 r 的区别值得注意r 衡量预测与真实值的线性相关程度相关高不代表预测准。假设预测值总是真实值的 1.5 倍r 可能接近 1但误差很大。R² 则直接衡量误差相对基准的改善而且 R² 可能为负数——在测试集上出现负数说明预测效果比直接用测试集均值还要差基本可以断定模型没学到有效信息或测试集分布偏移严重。R_2.m 的常见实现结构% 决定系数 R^2 与相关系数 r 的计算 SS_res sum((Y_test_real - Y_pred_real) .^ 2); SS_tot sum((Y_test_real - mean(Y_test_real)) .^ 2); R2 1 - SS_res / SS_tot; r corr(Y_test_real, Y_pred_real); fprintf(R2: %.4f\nr: %.4f\n, R2, r);包里附带的 1.png、2.png 等截图就是整个流程跑完后的预测对比曲线。复现时建议第一眼看图里曲线的形态峰谷出现的位置对得上说明模型学到了日周期性峰谷的尖点被削平说明隐藏层节点太少模型容量不够。比数字先看图是快速判断模型健康状况的最省钱办法。5. 避坑指南BP 负荷预测里五个高频翻车点与排查方法5.1 现象训练集误差很低测试集曲线严重偏离真实值原因最常见的是隐藏层节点数过多或训练轮数过长模型把训练样本里的噪声也记住了属于过拟合。另一个隐蔽原因是 net.divideFcn 还是默认的 dividerand训练时模型偷偷把测试数据拿去参与验证导致报告的测试误差失真。解决先查 divideFcn 是否已改成 divideind确认测试集没有被内部随机划分截胡。然后依次减小隐藏层节点数、把 trainParam.goal 调松观察测试误差是否回落。如果训练过程的误差曲线先降后升说明早就该早停回头检查 epochs 是不是设得太大。5.2 现象预测曲线整体平移了一个固定值形状却非常贴合原因这不是模型算法问题而是反归一化参数用错了。最常见的是用测试集自身的 min 和 max 做反归一化或者错误引用了其他特征的边界值比如把温度列的边界当成负荷目标列的边界。解决检查 load 进来的 data_min 和 data_max 来源是否与训练时一致索引是否对应目标列。把反归一化改成统一使用 data_max(1)、data_min(1) 的写法重新计算后看预测曲线均值与真实曲线均值的差值这个固定偏移的数值应该正好等于边界错误带来的平移量。5.3 现象运行报错 Inputs are incorrectly sized原因MATLAB 神经网络工具箱要求每一列是一个样本而我们常规矩阵习惯是每一行一个样本忘了转置是这个报错的第一来源。其次是滑动窗口构造的输入维度与前面 feedforwardnet 设置的输入维度不一致。解决训练前在命令行执行 whos X_train查看变量维度。如果 X_train 规模是样本数×特征数传给 train 时就要写成 X_train。再用 size(net.inputs{1}.size) 对照网络期望的输入维度两边一致才继续跑。排查这类维度问题时把关键变量的 size 打印出来是最直接的调试方式。5.4 现象预测结果几乎是一条平线方差明显偏小原因网络没有学到输入到输出的有效映射。常见诱因有三个归一化后目标值差异太小梯度信号弱隐藏层节点太少网络容量不足随机初始权重落在较差的区域陷入局部极小。解决先检查归一化后目标列的取值范围如果 Y 的最大最小值只差 0.01 量级说明目标本身变化微弱需要增加有区分度的特征。然后试 5、10、15、20 四档隐藏层节点数取测试误差最小的一档。如果还是平的用 rng 换几个随机种子重新训练选表现最好的一次。平线不一定全是模型问题有时是数据本身的信息量不够。5.5 现象同一份代码两次运行结果完全不同原因BP 网络的初始权重随机生成每次训练都是从不同起点搜索结果不同是正常现象不代表代码写错。不固定随机数种子意味着任何超参数比较实验里都混入了随机噪声结论不可信。解决在脚本开头加一行 rng(2024)固定随机种子保证两次运行结果一致。做不同隐藏层节点数对比实验时更要统一种子否则误差差异到底是节点数引起的还是随机性引起的完全说不清。我以前做实验时为省一行代码吃了亏后来强制自己在每个脚本开头都写上% 固定随机数种子保证结果可复现 rng(2024);这样每次训练的结果都能复现调参过程中的“好结果”也有据可查不用回头撞大运。6. 进阶扩展MAPE 评估、特征增强与模型选择边界误差指标的下一步是 MAPE也就是平均绝对百分比误差。它的计算方式是把每个点的绝对误差除以真实值取平均后乘 100%。MAPE 的好处是单位无关业务方更容易理解“预测偏差平均百分之几”。但负荷接近零的深夜时段MAPE 会被极小值放大到离谱所以实用做法是先滤掉真实负荷过低的点再统计比如小于全天均值 5% 的时刻直接排除。特征增强可以从两个低成本方向入手一是周类型特征把周一到周日编码成 1 到 7 的有序数列让网络感知周末与工作日负荷形态的差异二是温度特征如果数据里有最高最低气温把温度列引入作为输入可以捕捉制冷和采暖负荷随温度变化的环节注意温度与负荷的响应存在延迟将温度列平移两三个小时再与负荷对齐效果往往更好。这套扩展在 BP 和 LSTM 上都适用属于投入小、回报稳的改动。模型选择边界这件事我用过的经验判断是几千条数据量级下BP 的训练速度和稳定性通常优于 LSTMLSTM 的优势要到小时级多变量、数据上万条的长周期场景才明显。XGBoost 对表格特征友好但同样要构造滞后特征才能用于时序预测。所以我一般把 BP 当作第一运行版本它调参成本低、可解释性强拿到一个可靠的基线后再用 LSTM 或 XGBoost 去挑战这个基线。这份代码包作为基线完全够用自己能扩展出温度和周类型特征就能应付大部分城市电网负荷预测的落地场景。最后说一个我的操作习惯拿到一份新的城市负荷数据第一件事永远是先可视化原始负荷曲线确认数据的周期形态和异常时段然后把 rng 固定、滑动窗口、归一化边界、训练测试划分全部记录在脚本头部再开始跑模型。从那以后我每次做负荷预测实验都强制走一遍这个流程先看图后调参模型翻车的概率低很多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑