简介:电力负荷预测是电网调度与能源管理中的核心任务,这份压缩包聚焦LSTM、BILSTM、GRU、BIGRU及BP五种模型的MATLAB实现,适合电力系统研究人员、数据分析师以及深度学习初学者对照学习。包内共32个文件,以9个可运行的m脚本为主,覆盖数据归一化、模型训练、评估指标计算等环节;另有7个mat数据集、2个xlsx电力负荷与价格数据、12张png结果图及2个asv备份文件,压缩包整体约5.49MB,小巧紧凑。目前已有489人学习下载。通过这些脚本,读者可以直接运行各模型对应的主程序,结合澳大利亚电力负荷与价格预测数据,对比不同循环神经网络在负荷预测任务上的效果,并借助MSE、RMSE、R²等指标评估模型性能,适合用于课程设计、论文实验或工程预研。
1. 把五个负荷预测模型打包进一套 MATLAB 代码:LSTM、BILSTM、GRU、BIGRU 与 BP 神经网络对比实验的落地过程
电力负荷预测这件事,做过的人都知道:降压站要按小时申报用电计划,算少了要考核,算多了白白买电。传统时序方法碰上工作日和节假日的突变,预测误差能到百分之十几。这套资源给的不是单个模型,而是把 LSTM、BILSTM、GRU、BIGRU、BP 神经网络五个模型分别写好 main 脚本、统一评估函数的 MATLAB 工程包,直接就能跑对比实验。它能解决的核心问题只有一个:让你在同一个数据集、同一套评估口径下,搞清楚五种网络结构在你的负荷数据上到底谁更靠谱。适合正在做毕设、写小论文、或者刚接手负荷预测项目需要快速出基线结果的工程师,不需要你从头搭代码,改改数据路径和网络参数就能看到训练曲线、预测对比图和误差指标。
2. 先摸清数据和代码文件结构:哪个文件负责训练、哪个文件负责评估、怎么替换你自己的负荷数据
2.1 按文件名拆解这套工程包的职责边界
拿到压缩包之后,第一件事不是双击打开某个 .m 脚本就开始跑,而是先把文件按职责归类。这套包的命名习惯相当直白:main开头的都是训练与预测主脚本,MSE_RMSE_MBE_MAE.m和R_2.m是评估函数,.mat文件是预处理好的训练数据,xlsx是原始数据。从代码组织上来说,这符合 MATLAB 项目的一般惯例——主脚本、函数文件、数据文件三层分离。
五个主脚本对应五个模型,这是核心入口:
mainlstm.m:标准 LSTM 单层或多层网络训练与预测脚本mainbilstm.m:双向 LSTM,两个方向同时编码时序特征maingru.m:门控循环单元,比 LSTM 少一个门,计算开销更低mainbigru.m:双向 GRU,原理对应 BILSTMmainbp.m:三层或四层 BP 前馈网络,作为非时序基线模型
注意mainz.m和mainz.asv,.asv是 MATLAB 的自动保存文件,说明作者在调试过程中手动备份过,这个文件可以直接不看。main0.m大概率是数据加载与预处理的辅助脚本,打开看一眼就知道它是把 Excel 转成.mat流程。
数据文件方面,lstmdata.mat、bilstmdata.mat、grudata.mat、bigrudata.mat、maydata.mat都是已经归一化好的训练输入输出对。maynet.mat看名字像是保存好的训练结果网络,可以做加载测试用。澳大利亚电力负荷与价格预测数据.xlsx是原始数据,包含负荷和电价两列以上字段,data.xlsx是精简版或经过初步清洗的数据。
2.2 从 Excel 到 .mat:数据预处理的三种常规做法
main0.m这类预处理脚本的核心职责,是把原始负荷序列转换成神经网络能吃的样本对。常见做法是滑窗构造输入输出:用过去 24 个小时的负荷数据预测下一个小时。这涉及到两个关键参数——滑动窗口长度input_len和预测步长output_len。窗口设太短,网络学不到日周期规律;设太长,训练数据量骤减,而且引入过多噪声。
我用 MATLAB 处理这个环节时,一般会先固定随机种子,避免每次运行切分训练集和测试集的结果不一样。之后做归一化,把负荷数据映射到[0, 1]区间。这里有个容易翻车的细节:归一化的最大值和最小值必须单独保存,预测完成后要做反归一化还原,否则预测曲线会被压缩在一个极小的范围内,画出来的图用户根本看不懂。
% 滑窗构造样本对,间隔为1小时,窗口24小时预测未来1小时 function [XTrain, YTrain, XTest, YTest] = prepare_data(data, input_len, output_len, train_ratio) % 归一化到[0,1],注意用全量数据的min/max,而不是训练集的 mu = min(data); sigma = max(data) - min(data); data_norm = (data - mu) / sigma; % 构造输入输出矩阵 X = []; Y = []; for i = 1 : length(data_norm) - input_len - output_len + 1 X = [X; data_norm(i : i + input_len - 1)']; Y = [Y; data_norm(i + input_len : i + input_len + output_len - 1)']; end % 按比例切分训练集和测试集 num_train = floor(size(X, 1) * train_ratio); XTrain = X(1 : num_train, :); YTrain = Y(1 : num_train, :); XTest = X(num_train + 1 : end, :); YTest = Y(num_train + 1 : end, :); end这段滑窗代码的逻辑不复杂,但mu和sigma用全量数据计算是关键决策。如果只用训练集的 min/max 来归一化,而测试集里有超出范围的数据,归一化后的值会跑到[0, 1]区间外面,激活函数的输出饱和,预测误差直接被放大。第 2 行到第 4 行做的事情就是先求归一化参数,中间循环构造滑窗样本,最后按train_ratio切分。input_len=24和output_len=1是负荷预测最常见的默认参数,对应日周期预测下一小时。
2.3 原始数据长什么样:时间戳、负荷、电价三维度
打开澳大利亚电力负荷与价格预测数据.xlsx,你大概率看到三列数据:日期时间、电力负荷值、电价。负荷数据从几百到几千兆瓦量级波动,电价则会出现尖峰,这两个特征量纲完全不同。如果直接把两列拼在一起喂给模型,负荷特征会被电价特征的数值范围淹没,所以预处理必须分列归一化,也就是对负荷和电价分别取 min/max。
这套数据在实际实验中还有一个特性:它包含明显的日周期性、周周期性和季节性。LSTM 家族模型能学到这些周期性,但前提是滑窗长度不能太短。如果你是拿这套数据做论文对照实验,建议把数据集切成连续的时间段来划分训练集和测试集,而不要随机打乱再切分——时序数据一旦打乱,就等于是用了未来信息去预测过去,实验结论完全失真。
3. 五个模型的核心差异和 MATLAB 实现骨架:改哪里就能换模型
3.1 LSTM 与 GRU 的结构取舍:门控数量不是唯一差距
LSTM 有三个门——输入门、遗忘门、输出门,每个门都有一个 sigmoid 层和一个 tanh 层组合。GRU 只有两个门——更新门和重置门,参数数量大约减少四分之一到三分之一。这个差异在训练速度上体现得最直观:同样一个数据集,GRU 收敛通常比 LSTM 快 10% 到 20%。
但快并不代表更好。LSTM 的三个门机制允许网络更精细地控制记忆的写入、保留和读取,在序列特别长、依赖关系跨越很长时间步的场景下稍微占优。负荷预测的数据往往是一个月甚至一年的小时级数据,长期依赖真实存在,比如上周同一天的负荷模式,LSTM 理论上能刻画得更好。不过这种优势要在数据量足够大的时候才明显,数据量只有几千条时,GRU 反而因为参数少、不容易过拟合,表现常常更好。
在 MATLAB 里实现这两者的差异非常小,核心就在网络层的替换。下面这段代码是mainlstm.m的核心段落:
% 定义 LSTM 网络结构:序列输入 -> LSTM层 -> 全连接 -> 回归输出 layers = [ sequenceInputLayer(24) % 输入维度是24,对应24小时滑窗 lstmLayer(128, 'OutputMode', 'last') % 128个隐藏单元,只输出最后一步 fullyConnectedLayer(1) % 单步预测所以输出维度为1 regressionLayer]; % 回归任务,损失函数用MSE % 训练参数:Adam优化器,初始学习率0.005,120轮迭代 options = trainingOptions('adam', ... 'MaxEpochs', 120, ... 'InitialLearnRate', 0.005, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropPeriod', 60, ... 'LearnRateDropFactor', 0.2, ... 'MiniBatchSize', 64, ... 'Verbose', 0, ... 'Plots', 'training-progress');把 LSTM 换成 GRU 只需把lstmLayer改成gruLayer,其余结构完全不用动。第 2 行的sequenceInputLayer(24)必须和滑窗长度保持一致,如果prepare_data里input_len改成 48,这里的 24 也要跟着改,否则 MATLAB 直接报维度不匹配错误。lstmLayer(128)的 128 是隐藏单元数量,这个参数越大,模型表达能力越强,但训练时间成倍增加且更容易过拟合。小数据集从 64 开始试,大数据集 128 或 256 都行。
3.2 从单向到双向:BILSTM 和 BIGRU 的上下文增强逻辑
双向网络的核心思路是在时间维度上跑两个独立的循环网络,一个从左往右读序列,另一个从右往左读序列,然后把两个方向输出的特征拼接或相加送入下一层。这样做的前提假设是:未来的上下文信息有助于理解当前时刻的负荷状态。对负荷预测来说,这通常成立——比如午后负荷的下降趋势,网络如果能看到下午的上升曲线,对当前尖峰时刻的估计就会不同。
双向结构的代价是训练时间翻倍。因为每一层实际上跑了两个方向的网络,参数量的增加和训练和推理时程翻倍是同步的。如果你用的是bilstmLayer,MATLAB 内部会自动处理两个方向的序列拼接,不需要额外调整数据格式。
% 双向 LSTM:两个方向的隐藏状态拼接输出 layers = [ sequenceInputLayer(24) bilstmLayer(128, 'OutputMode', 'last') fullyConnectedLayer(1) regressionLayer];在实际对比实验里,一个常见现象是:双向网络在测试集上的误差略低于单向网络,但容易在训练初期出现损失震荡。原因在于双向网络需要同时学习两个方向的特征,梯度更新路径更长,初始学习率设置稍微偏大就会出现不稳定。我的建议是双向网络把初始学习率下调一半,比如从 0.005 改成 0.002,再把LearnRateDropPeriod缩短,让学习率更快衰减下来。
3.3 BP 神经网络的定位:不是陪跑,而是基线参照
BP 网络在这个工程包里的作用经常被低估。很多人把它当成最简单的对比模型,实际上它是整个实验里不可缺失的判定基准。LSTM、GRU 这些模型在测试集上的预测误差,如果连 BP 都没跑赢,说明你的数据和预处理存在严重问题。
BP 网络是纯前馈结构,输入是 24 个负荷值展平成向量,输出一个预测值,中间层用 sigmoid 或 ReLU。它不感知时间顺序,24 个输入位置调换了顺序,预测结果不受影响。这既是它的短板也是它的特点:用 BP 做对比,看的是多出来的性能到底来自网络结构的时序建模能力,还是仅仅是数据预处理的功劳。
MATLAB 里实现 BP 有两种路径,一种是用feedforwardnet函数快速构建,另一种是手动定义网络层。前者适合快速出结果,后者适合精确参数控制。建议用feedforwardnet,因为它的内置训练算法trainlm(Levenberg-Marquardt)在小数据集上收敛速度远快于 Adam,而且收敛稳定性好。这个特性是 BP 网络在这个项目里最大的工程价值——它是你调试数据预处理是否正确的最快工具。
4. 评估指标函数拆解:MSE、RMSE、MBE、MAE 和 R² 在 MATLAB 里的计算方法与判读逻辑
4.1 四个误差指标分别暴露什么问题
压缩包里的MSE_RMSE_MBE_MAE.m和R_2.m是评估模型预测效果的核心函数。MSE 是均方误差,把所有预测误差平方后取平均,它对大误差极其敏感——如果模型在某些时刻出现严重偏离,MSE 会被这几个异常点拉高。RMSE 是 MSE 开方后的值,量纲回归到负荷数值本身,比如 30 MW 的 RMSE 就是可解释的。MBE 是平均偏差误差,预测值减真实值取平均,它的符号有明确含义:正值说明模型系统性地高估,负值则说明系统性低估。MAE 是平均绝对误差,不区分预测误差是来自个别大偏差还是整体小偏差。
这四个指标放一起看,能暴露不同问题。MBE 接近 0 但 RMSE 很大的组合,说明模型正负误差抵消严重,整体趋势对但波动幅度没抓住;MBE 明显偏正,说明模型有系统偏差,需要通过调整预测结果的偏置来修正。在实际实验中,LSTM 容易出现 RMSE 大但 MAE 小的组合——意味着大部分时间都预测得很准,但偶尔会有几个时刻完全偏离,这通常发生在负荷曲线的陡升陡降段。
% 统一评估函数:输入真实值和预测值,输出四个误差指标 function [mse, rmse, mbe, mae] = MSE_RMSE_MBE_MAE(y_true, y_pred) % 去除NaN值,预测结果中可能因边界效应产生无效值 valid = ~isnan(y_true) & ~isnan(y_pred); y_true = y_true(valid); y_pred = y_pred(valid); % 逐项计算误差 errors = y_pred - y_true; mse = mean(errors.^2); rmse = sqrt(mse); mbe = mean(errors); mae = mean(abs(errors)); end第 2 行的valid逻辑索引是在做执行检测,防止输入序列里混入 NaN。这个过滤操作非常必要,因为训练完的网络在预测时,初始时刻的隐藏状态是随机初始化的,前几步预测往往偏离严重,有些同学会保留这些异常值参与指标计算,导致评估结果虚高。第 4 到第 8 行分别计算四个指标,逻辑直接到不需要额外注释。
4.2 R² 的判定边界:95% 代表什么?90% 代表什么?
R_2.m计算的是决定系数,公式是 1 减去残差平方和与总平方和的比值。它在负荷预测中的含义可以通俗理解为:模型捕捉到了时间序列中多大比例的方差变化。R² 达到 0.95 意味着模型解释了 95% 的负荷波动,剩余 5% 的波动是模型抓不到的。
但有个很容易被忽视的问题:R² 很容易被数据的整体方差大小误导。如果测试集所在的时间段刚好包含一个极大的负荷突变,总平方和会被拉大,R² 在分子不变的情况下会自动变高。所以 R² 必须和 RMSE 一起看,R² 很高但 RMSE 也不低的情况完全可能同时存在,这时候 R² 的参考价值就很低了。
利用 R² 还有一个作用:判断多个模型的差异是否有意义。如果 LSTM 的 R² 是 0.973,GRU 是 0.968,这个 0.005 的差距通常不足以说明 LSTM 更优,因为换一次随机种子,这个差距可能就反过来了。工程上判断模型优劣,更靠谱的标准是多次实验取平均值,再用 t 检验或至少看波动范围,这套代码跑出来的单次指标差异在 0.01 以内都算同一水平模型。
5. 避坑与排查:五个必踩的坑——从数据泄漏到归一化边界
5.1 坑一:训练损失下降正常,但预测曲线是一条平线
现象:模型训练完成,loss 曲线稳定下降,但画出的预测结果几乎是一条水平直线,贴近训练集的均值。
原因:最常见的诱因是滑窗构造样本时,输入特征和输出标签之间没有有效的时间依赖。另一种情况是归一化时误用了在线归一化——测试集使用了训练集的均值和标准差,如果测试集分布偏离太大,模型学到的隐状态几乎不随输入变化。
解决:先检查prepare_data里X和Y的构造逻辑,确认Y取的是未来时刻而非当前时刻。再看data_norm的归一化代码,把全量的 min/max 打印出来和实际负荷数据的最大值核对一遍。如果平线出现在测试集的前几个时刻而后续正常,这是隐藏状态初值问题,直接把预测序列的前 5 个点舍弃再画图就好。
5.2 坑二:BILSTM 训练时间比 LSTM 多出不止一倍
现象:mainbilstm.m的训练时间远超mainlstm.m,上图一看显著多出 200% 以上。
原因:双向网络虽然在参数量上是单向的约两倍,但训练时间翻倍属于正常现象,如果超出太多,问题出在 GPU 资源没有吃满,MATLAB 默认用 CPU 训练 LSTM 层,而 CPU 训练下双向网络的计算复杂度会被成倍放大。
解决:检查trainingOptions里有没有设置'ExecutionEnvironment', 'gpu'。安装 Deep Learning Toolbox 之后,用 GPU 训练这五个模型的速度差异会明显缩小。值得注意的是,BP 网络在 CPU 上训练很快,但 LSTM 家族在 CPU 上速度差异巨大,如果手头没有 NVIDIA 显卡,可以先跑通 GRU,把 BILSTM 放到最后跑。
5.3 坑三:评估指标算出来 MBE 为正负 50 以上,模型预测整体偏高
现象:MBE 指标异常大且符号一致,预测曲线整体在真实曲线上方。
原因:反归一化环节出了问题。训练时用归一化数据计算误差,预测完成后把结果反归一化时,如果用的是训练集的最大值和最小值,但这个最大值远小于测试集里出现的负荷尖峰,反归一化后的预测值会被压得过低或整体偏移,MBE 随之被污染。
解决:反归一化必须使用训练前保存的全量数据的mu和sigma,而且必须把保存的参数从工作区导出为.mat或csv文件,不要在预测脚本里重新计算。另一个排查方向是检查训练目标YTrain是不是单列向量,如果YTrain是列向量而预测输出是行向量,MATLAB 的隐式扩展可能导致逐元素运算错位。
5.4 坑四:训练到一半 loss 变 NaN
现象:训练过程显示 loss 突变成 NaN,训练曲线断裂,后面不再更新。
原因:学习率设置过高导致梯度爆炸,特别是数据归一化不彻底时最常遇到。负荷数据如果有缺失值没有处理,NaN 会通过滑窗传播到训练样本中,反向传播时梯度计算直接崩掉。
解决:先把原始数据中的缺失值和异常尖峰处理掉,常见做法是线性插值或前向填充。然后把InitialLearnRate从 0.005 下调到 0.001,并把GradientThreshold设置成 1,在trainingOptions里加一行'GradientThreshold', 1,这样即使出现大梯度也会被裁剪,保住训练过程不中断。
5.5 坑五:运行mainlstm.m时报维度无法广播的错误
现象:MATLAB 直接红字报错,提示两个矩阵的行数或列数不一致,指向fullyConnectedLayer或trainNetwork的位置。
原因:sequenceInputLayer(24)定义的输入维度和实际滑窗数据不对应。比如prepare_data里input_len设置成了 24,但滑窗循环里用了i + input_len - 1的边界条件,生成X的列数是 24,而某个脚本里改成了 48 没同步,维度就对不上了。
解决:把prepare_data的输出size(XTrain)打印出来,确认第二维数值和sequenceInputLayer的第一个参数一致。修改滑窗长度时,唯一需要同步改的三个地方分别是sequenceInputLayer的维度、prepare_data的input_len、以及全连接的输出维度如果输出步长大于 1 也要调整。
6. 进阶用法:用同一套数据做五模型横向对比与显著性判断
6.1 写一个循环脚本统一跑五个主脚本,把指标汇总成表格
这五个模型跑完单次实验,下一个合理的动作是横向对比。我的习惯是写一个汇总脚本,调用五个主脚本后深呼吸,然后把每个模型的四个指标收集起来,求多次独立实验的平均值和标准差。因为单次实验的随机性不可忽略,至少在相同数据和相同参数设置下,每个模型独立跑三次,取MSE、RMSE、MAE、R²的均值与标准差,才能判断 LSTM 到底比 GRU 好多少。
% 循环调用五个模型的入口脚本,收集并汇总评估指标 model_names = {'lstm', 'bilstm', 'gru', 'bigru', 'bp'}; metrics = zeros(length(model_names), 4); for k = 1 : length(model_names) y_pred = eval(['main_' model_names{k}]); % 调用对应主脚本并返回预测值 [mse, rmse, mbe, mae] = MSE_RMSE_MBE_MAE(y_true, y_pred); metrics(k, :) = [mse, rmse, mbe, mae]; end % 打印对比表,注意MBE符号含义:正偏则模型高估 T = table(model_names', metrics(:,1), metrics(:,2), ... metrics(:,3), metrics(:,4), ... 'VariableNames', {'Model', 'MSE', 'RMSE', 'MBE', 'MAE'}); disp(T);这段脚本的思路是把五个主脚本的预测结果统一回收。实际执行的时候,你不需要真的用eval去调用别人的脚本,更好的做法是手动运行每个main*.m脚本,然后把y_pred保存成pred_lstm.mat、pred_gru.mat之类的文件,汇总脚本只做加载和评估。这样能规避一个坑:eval('main_lstm')这个调用形式要求函数返回值和脚本内部变量名匹配,五个脚本的预测输出变量名大概率不一致,用load方式更稳。
6.2 怎么判定两个模型的 0.005 差距是不是真的
不少人在论文或报告里写「本文提出的 BIGRU 比 LSTM 的 RMSE 降低了 0.5%」作为结论,但从不告诉读者这个 0.5% 是否稳定。一个务实的验证手法是使用五折交叉验证的思路:把时间序列数据切成五个连续段落,每次拿四段训练、一段测试,分别计算 RMSE,最后求五个 RMSE 的平均值和标准差。如果 BIGRU 的五次平均低于 LSTM,且标准差区间不重叠,那么这个差距才算有说服力。
另一个值得留意的点是最优参数的转移性。在 LSTM 上调好的隐藏单元数量和初始学习率,直接搬给 BIGRU 用通常不是最优的。双向网络因为两个方向的梯度路径叠加,隐藏单元数量可以适当减小,初始学习率也建议调低,否则 BIGRU 的收敛稳定性会不如 LSTM,最终误差反而更大。这就是你跑完这五个模型后,最值得回顾和总结出的经验。
这套包我整体跑过一遍的感觉是:LSTM 和 GRU 输出容易解释,BILSTM 与 BIGRU 的精度提升有限但在论文里作为复杂度对比很有价值,BP 网络则是整个实验设计里不可或缺的基线参照。从那以后,我每拿到一份新的负荷数据都会先跑一遍这套流程,先把 BP 跑出基线,再逐个上循环模型,最后才在需要的场景里启用双向结构,这样每一层模型的收益都能算清楚。希望帮到你。
本文还有配套的精品资源,点击获取