ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MATLAB神经网络预测模型实战:电力负荷回归预测与源码解析

MATLAB神经网络预测模型实战:电力负荷回归预测与源码解析 简介面向电力预测与神经网络入门者提供基于MATLAB实现的神经网络预测模型针对电力需求预测场景覆盖从历史数据清洗、网络结构设计、反向传播训练到模型评估与未来负荷预估的完整流程适用于电力系统管理、电力交易和能源调度等场景。压缩包内共两个文件其中一个m脚本负责数据预处理、输入输出层配置、激活函数选择、训练参数设置及预测输出另一个xlsx数据表提供可用的历史电力负荷及相关变量数据整体仅40KB轻量易上手。已有695人学习/下载适合初学神经网络回归预测或电力负荷预测的读者按源码逐步复现。通过学习可掌握数据归一化、Sigmoid/ReLU激活、误差指标计算等核心要点并可直接替换数据集扩展到其他时序预测任务兼具教学参考与工程实用价值。1. 神经网络预测模型落地MATLAB源码与电力预测数据的实际搭配电力负荷预测是一个很典型的回归问题手里有过去几个月甚至几年的负荷记录外加上温度、节假日这些外部变量要预测明天下午三点电网会承受多少兆瓦的压力。人工估算波动太大传统时间序列模型对非线性特征的拟合又不够所以很多同学和从业者会把目光放到神经网络预测模型上。这份资源包含一个 MATLAB 脚本 sjwl1wrq.m 和一张数据汇总表1.xlsx恰好覆盖了从数据处理、网络构建到误差评估的完整流程不用自己从零开始凑代码和样本。适合谁正在做电力预测方向毕业设计的学生刚接触 MATLAB 神经网络工具箱的初学者以及电力行业里想快速搭一个预测基线模型的数据工程师。神经网络的基本结构无非是输入层、隐藏层、输出层训练过程就是调整神经元之间的权重让预测值和真实值之间的误差越来越小。电力预测用到的特征一般包括历史负荷、温度、湿度、星期几、是否节假日这些变量在数据表里大多能找到对应列。拿到资源后我建议先别急着双击 sjwl1wrq.m 运行先花十分钟打开 xlsx 看一眼数据长什么样再对照脚本里 net.trainParam 的设置理解参数意图否则很容易出现训练误差很好看、一到测试集就崩的情况。后面几章我会按数据预处理、网络搭建、训练评估、避坑排查的顺序把这条链路完整拆开。2. 读懂数据汇总表1.xlsx电力预测数据集的字段与预处理2.1 数据表里常见的字段负荷、温度、日期与节假日标记打开数据汇总表1.xlsx 的第一步不是直接导入 MATLAB而是先确认字段结构。电力负荷数据表最常见的列包括日期时间、实际负荷单位通常是 MW、干球温度、湿度、星期几、是否节假日标记。有些表还会带峰谷平电价但那份资源的核心还是负荷预测所以重点关注负荷列和外部变量列即可。读取用 readtable 最省事它能自动把 Excel 里的表识别成 table 类型列名原样保留。如果只读数值矩阵也可以用 xlsread但 table 在后续处理缺失值、按列操作时更顺手。data readtable(数据汇总表1.xlsx, VariableNamingRule, preserve); summary(data); head(data, 10)逻辑说明readtable 的第一个参数是文件路径VariableNamingRule 设为 preserve 是为了让中文列名不被自动转换成 Var1 之类的占位符summary 打印每列的数据类型、缺失值数量、最大值最小值这一步能让你对数据质量有个初步印象head 函数看前 10 行主要确认日期格式、负荷量级和特征是否齐全。注意 VariableNamingRule 是 R2019b 之后才有的参数老版本 MATLAB 直接用 readtable(数据汇总表1.xlsx) 即可列名乱码时再手动赋变量名。字段识别完了还得检查时间列是不是 datetime 类型。Excel 里日期经常被读成序列号数字直接拿去做特征会出问题。我一般会加一句 data.日期时间 datetime(data.日期时间) 做类型转换然后按日期排序确保后续划分训练集测试集时时间顺序是连续递增的。2.2 缺失值与异常值处理插值法还是直接剔除电力数据脏得很稳定凌晨时段采集器离线、雷雨天信号跳变、偶尔出现 0 值或负数。这些异常值如果直接喂给神经网络会在训练时产生巨大的梯度扰动。处理顺序有讲究先看缺失再看毛刺。常用做法是先用 ismissing 定位缺失位置再用 fillmissing 做线性插值对于突变毛刺用 movmedian 滑动中值滤波比直接改数值更稳妥因为它能保留真实负荷曲线的走势。data.负荷 fillmissing(data.负荷, linear); data.负荷 movmedian(data.负荷, 5); data.负荷 max(data.负荷, 0); % 负荷不可能是负值负值按0截断逻辑说明fillmissing 的 linear 方法是在缺失点前后各取一个有效值连直线适用于短间隔缺失比如单个小时缺测如果连续缺失十几个小时线性插值会拉出一条平直线反而不真实这时可以考虑 spline 样条插值。movmedian 的第二个参数 5 表示窗口大小对小时级数据来说 57 比较合适窗口太大会把真实的负荷尖峰削平窗口太小又滤不掉毛刺。最后那行 max 操作是把明显异常的负值截断到 0逻辑上说得通但如果你发现原始数据里有大段负值不能光靠截断要回源头查采集问题。处理完缺失和毛刺之后画图确认一下。plot(data.日期时间, data.负荷) 看一眼曲线是否平滑连续重点关注有没有断崖式跳变。这一步是纯视觉检查数值指标算得再漂亮也替代不了眼睛对曲线的判断。2.3 归一化用 mapminmax 的默认区间就够了吗神经网络对输入特征的尺度非常敏感。tansig、logsig 这类激活函数在输入绝对值很大的时候会进入饱和区梯度趋近于零训练基本停摆。电力负荷数值可能从几十 MW 到几千 MW温度从零下到三十多度量纲差异很大不归一化直接训练loss 曲线大概率是一条水平线。MATLAB 里最常用的归一化函数是 mapminmax它的默认映射区间是 [-1, 1]。但对于电力负荷这种恒为正的回归目标我更习惯显式指定映射到 [0, 1]这样反归一化之后数值解释更直观输出层用 purelin 线性激活时也不会出现负值预测的尴尬。X_raw data{:, {温度, 湿度, 星期, 是否节假日}}; Y_raw data.负荷; [X, ps_x] mapminmax(X_raw, 0, 1); [Y, ps_y] mapminmax(Y_raw, 0, 1);逻辑说明mapminmax 的调用格式是 [Y, PS] mapminmax(X, ymin, ymax)PS 里存了训练集的 min 和 max。这组 PS 在后面预测阶段反归一化时必须要复用千万不能拿测试集的 min/max 重新归一化否则训练集和测试集的数值范围不一致模型等价于在考试时看到了答案。电力预测里温度是外部气象变量这部分特征相对稳定但负荷的 min/max 会随季节漂移所以 Y 的 ps_y 必须绑定在训练集上计算。至于归一化区间选 [-1, 1] 还是 [0, 1]对最终误差影响不大关键是保证特征尺度一致。有一种常见误区是只归一个目标列特征列保持原样。这会导致损失函数里负荷的梯度远大于温度的梯度训练过程被负荷主导。正确做法是所有进网络的数值列统一归一化类别型特征比如星期几可以用 one-hot也可以直接归一化到 [0, 1]在数据量不大的时候两者差异不大。3. 拆解 sjwl1wrq.m网络架构与核心参数的设置逻辑3.1 网络创建newff 的输入维度与隐藏层设计sjwl1wrq.m 的核心大概率是这么几行构造输入矩阵、调用 newff 建网络、设置 trainParam、train 训练、sim 预测。MATLAB 神经网络工具箱里老牌函数 newff 到现在依然兼容虽然新版的 feedforwardnet 更面向对象但 newff 的参数结构更直观教学和论文里用得也更多。创建网络之前要先想清楚输入长什么样。做时间序列预测不能直接把一整列负荷当输入要把过去若干个时刻构造成一个样本。比如用前一周每天同一时刻的负荷预测明天同一时刻那输入维度就是 7。加上温度、节假日这些特征输入维度会扩展到 10 左右。常见做法是把特征按行排列每一列是一个样本。hiddenLayerSize 10; net newff(X_train, Y_train, hiddenLayerSize, {tansig, purelin}, trainlm); net init(net); % 显式初始化权重后面便于复现逻辑说明newff 的原型是 newff(P, T, S, TF, BTF)P 是输入矩阵T 是目标矩阵S 是隐藏层神经元个数TF 是传递函数 cell 数组BTF 是训练函数。这里隐藏层用 tansig 双曲正切输出层用 purelin 纯线性是回归问题的标准配置。为什么不用 logsiglogsig 输出被限制在 [0, 1]反归一化后相当于预测区间被人为截断遇到极端负荷点会失真purelin 没有上下界让网络自己学输出范围。隐藏层节点数怎么定经验公式一般是 sqrt(输入维度 输出维度) 1~10或者取输入输出维度的平均值附近。电力预测输入维度在 515 之间时hiddenLayerSize 取 815 都能跑通。节点太少拟合不了负荷曲线的非线性节点太多容易过拟合而且训练时间暴涨。先取 10 跑通全流程再按误差曲线调整不要一上来就堆 50 个节点。3.2 激活函数与训练函数选型tansig 与 trainlm 怎么配激活函数决定网络的非线性表达能力。如果只用 purelin不管堆多少层网络在数学上等价于一个线性回归模型拟合不了负荷在早晚高峰和温度突变时的复杂关系。所以隐藏层至少要有一层带 tansig 或 logsig。tansig 输出范围 [-1, 1]关于原点对称在电力这类无明显对称性的数据上收敛通常比 logsig 快。ReLU 在 MATLAB 里对应 poslin收敛快但训练初期容易出现神经元死亡小数据集上不如 tansig 稳。训练函数的选择直接影响收敛速度和内存占用。trainlm 也就是 Levenberg-Marquardt 算法是中小规模数据集上的默认首选几百到几千个样本训练非常快精度高。它的代价是内存因为要计算雅可比矩阵并做近似求逆输入维度高、样本量大时容易 out of memory。数据量大到几万条或者特征维度上百该换成 trainscg 或 traingdm。net.trainFcn trainlm; net.trainParam.showWindow true; net.trainParam.showCommandLine false;逻辑说明trainFcn 是 net 的一个属性随时可以改成 trainscg 或者 traingdm。showWindow 设为 true 会弹出训练窗口实时显示误差曲线和梯度变化初学者建议打开跑熟了可以关掉省资源。trainlm 自带自适应学习率机制不需要手动设置 lr它通过阻尼因子调节步长如果你换到 traingdm就得关注 lr 和 mc动量系数了。常见配置是 lr 取 0.010.1mc 取 0.90.95学习率太快发散太慢收敛不动。3.3 学习率、迭代次数与目标误差参数之间的联动net.trainParam 里的参数不是孤立存在的。epochs 是最大迭代轮数goal 是目标均方误差min_grad 是梯度下限net 达到三个条件中的任意一个就会停止训练。很多人只改 epochs 不管 goal结果训练早早触发 min_grad 提前停下还以为是轮数不够。net.trainParam.epochs 1000; net.trainParam.goal 1e-5; net.trainParam.min_grad 1e-7;逻辑说明epochs 设 1000 对中小数据集已经足够trainlm 通常在几十轮到几百轮内就能收敛设太小比如 50 轮网络还没收敛就被强行打断。goal 设 1e-5 是一个比较务实的值追求更高精度可以试 1e-6但很容易进入过拟合区间测试集误差反升。min_grad 设 1e-7 是通用值再小的梯度说明已经进入平缓区继续迭代收益有限。还有一个常见误区把学习率调来调去但训练函数是 trainlm它根本不怎么用 lr。trainlm 的步长由雅可比矩阵的阻尼系数控制属于二阶方法和 traingd 的一阶梯度下降在参数调节上完全不是一套逻辑。看清楚 net.trainFcn 到底是哪个函数再决定调整哪个参数。训练函数的权重初始化也是玄学newff 默认用 Nguygen-Widrow 规则初始化效果通常不错但每次运行结果都会有波动因为初始权重带随机性。要复现别人的实验结果必须在脚本开头固定随机种子这个在第 5 章会详细说。4. 把模型跑起来数据划分、训练过程与结果评估4.1 训练集、验证集、测试集划分按时间切还是随机切电力预测场景下数据划分的原则是时间顺序优先。很多初学者习惯用 randperm 把样本随机打乱然后按比例切训练测试集。这在一般分类问题上没毛病但对于时间序列预测是灾难——它会让你把 1 月的样本混进训练集、把 7 月的样本留在测试集模型相当于提前见过未来的负荷水平测试误差虚低。真实业务里不可能用未来的数据预测过去。正确的做法是保持样本的原始时间顺序前 70% 做训练集中间 15% 做验证集最后 15% 做测试集。数据汇总表1.xlsx 里的记录如果是按日期排列的直接按列索引切就行。n size(X, 2); train_end floor(n * 0.7); val_end floor(n * 0.85); X_train X(:, 1:train_end); Y_train Y(:, 1:train_end); X_val X(:, train_end1:val_end); Y_val Y(:, train_end1:val_end); X_test X(:, val_end1:end); Y_test Y(:, val_end1:end);逻辑说明X 的每一列是一个样本所以列索引就是时间索引。train_end 取前 70% 的列序号val_end 取前 85%剩下的归测试集。为什么验证集不能省trainlm 训练过程中会实时看验证集误差当验证误差开始连续上升时它会提前停止并根据验证误差最小的时刻保存权重这就是防止过拟合的 early stopping 机制。如果不单独划验证集train 就会自动把训练集随机分成三份打乱时间顺序效果适得其反。切完之后最好顺手检查一下三个集合的负荷均值是不是在合理范围内。如果测试集恰好好落在夏季高温期均值明显高于训练集那也是正常的说明模型要应对的是分布外数据预测误差参考意义更大而不是让模型作弊。4.2 训练过程监控误差曲线在告诉你什么手动切好数据之后还要让 train 函数知道你要用这些索引而不是默认随机划。MATLAB 的做法是通过 net.divideFcn 指定划分函数divideind 就是按索引划分。net.divideFcn divideind; net.divideParam.trainInd 1:train_end; net.divideParam.valInd train_end1:val_end; net.divideParam.testInd val_end1:n; [net, tr] train(net, X, Y); plotperform(tr)逻辑说明train 函数传入的是全部 X 和 Y它内部根据 divideParam 的索引自己切数据。trainInd、valInd、testInd 分别是训练、验证、测试样本的列索引这里必须和前面手动切分的范围保持一致。tr 是训练记录结构体里面存了每一步的误差、梯度、验证误差。plotperform(tr) 会画三条曲线训练误差、验证误差、测试误差随迭代次数的变化。读这条曲线是判断训练是否健康的唯一直观手段。训练误差和验证误差都持续下降说明网络在正常学习训练误差继续降但验证误差在第 50 轮开始拐头上升说明过拟合已经开始此时应该回退到验证误差最低的那个迭代点。tr.best_epoch 记录的就是验证误差最小的迭代轮次训练结束后网络权重自动恢复到这个点你不用手动操作。如果验证误差从一开始就在震荡不下降大概率是学习率偏大或归一化没做好。4.3 预测结果与反归一化RMSE、MAE、MAPE 怎么算训练结束后用 sim 或者直接 net(X_test) 做预测。注意网络输出的是归一化域的值必须用训练时保存的 ps_y 做反归一化才能得到真实的 MW 数值。Y_pred_norm sim(net, X_test); Y_pred postmnmx(Y_pred_norm, ps_y); Y_true postmnmx(Y_test, ps_y); rmse sqrt(mean((Y_pred - Y_true).^2)); mae mean(abs(Y_pred - Y_true)); mape mean(abs((Y_pred - Y_true) ./ Y_true)) * 100; fprintf(RMSE: %.2f MW, MAE: %.2f MW, MAPE: %.2f%%\n, rmse, mae, mape);逻辑说明postmnmx 是 mapminmax 的逆变换第二参数必须传训练时得到的 ps_y。这里 Y_test 也是归一化后的值所以要先反归一化再算误差。RMSE 是均方根误差对大误差特别敏感适合暴露极端预测偏差MAE 是平均绝对误差反映整体偏差水平MAPE 是平均绝对百分比误差电力行业考核最常用的指标单位是百分比。如果你的 Y_true 里有 0 值MAPE 会算出无穷大需要先把 0 值剔除或者改用 SMAPE。看误差指标的时候要结合负荷量级。如果平均负荷 800 MWRMSE 50 MW 算下来 MAPE 大概 5%6%在电力预测领域算可用如果 RMSE 200 MW那曲线基本是歪的回去检查特征工程。还有一种情况是 RMSE 很高但 MAE 很低说明存在少量极端点预测严重失误这种时候看 MAPE 容易被平均骗过去最好把预测误差按小时段拆开看找出到底是凌晨低谷段还是傍晚高峰段在拖后腿。5. 避坑与常见问题电力预测里五个典型翻车现场5.1 现象训练误差趋向于 0测试误差却大得离谱训练出来 MSE 接近 goal测试集上 MAPE 超过 30%这是数据泄漏的典型症状。最常见的原因是数据划分用了随机打乱或者特征里混进了未来信息。之前带过一个学生他构造特征时把当天的实际负荷做成了输入预测目标也是当天负荷相当于直接告诉网络答案。解决方法是严格按时间顺序划分数据然后逐个检查特征列凡是和目标时刻重叠或滞后的列全部剔掉。5.2 现象预测曲线几乎是一条平线落在负荷均值附近网络没学到非线性关系输出逼近训练集均值。原因基本是隐藏层节点太少或者输出层误用了 logsig。logsig 的输出区间是 [0, 1]如果真实负荷在 200800 MW反归一化后所有预测都挤在区间中间附近曲线自然就平了。解决方法是输出层换 purelin隐藏层节点从 10 往上加同时检查 tansig 是否真的用在了隐藏层而不是输出层。这个坑我踩过不止一次代码里 TF 参数顺序写反一眼看不出来画图才暴露。5.3 现象trainlm 训练到一半报错 Out of memoryLevenberg-Marquardt 算法每一步要算雅可比矩阵乘以其转置内存开销随网络参数数量平方级增长。隐藏层 50 个节点加上 20 维输入参数量就到上千训练集几万条样本时很容易撑爆内存。解决方法是换 trainscg它属于共轭梯度法不需要存大矩阵内存占用低得多或者减少隐藏层节点数把样本抽稀比如每 15 分钟一条的数据重采样成小时级。MATLAB 报错信息里如果出现 Out of memory先别急着加内存条先看 trainFcn。5.4 现象同一份数据每次跑出来的结果差异很大网络权重初始化是随机的train 的数据划分如果用了默认的 dividerand每次运行还会换一批验证集结果自然不稳定。不少论文里报告的结果可能是某一次跑出来的最好值复现不出来很正常。解决方法是脚本开头固定随机种子 rng(42)这样 newff 的初始权重和 dividende 的划分顺序都能复现。注意 rng 要放在 newff 之前调用否则还是不生效。做参数对比实验时尤其重要不固定种子你根本分不清误差变化是参数引起的还是随机波动引起的。5.5 现象测试期恰好包含春节预测误差全线崩溃节假日负荷模式和平时工作日差太远工厂停工、商场人流暴涨模型在训练集里没见过足够多的节假日样本预测自然失灵。这是数据分布问题不是算法问题。解决方法是把节假日做成一个独立的 0/1 特征让网络至少知道今天不是工作日如果节假日样本量太少可以把历史三年的春节、国庆单独提出来增加采样权重或者干脆针对节假日单独训一个模型。不要指望一个万能模型吃下所有日期类型这不符合电力负荷的物理规律。6. 进阶验证技巧用滚动预测检验模型的真实泛化能力静态测试集上算出来的 RMSE 再好看也只代表一步预测的能力。真实业务里的电力预测是滚动推进的先用前 14 天预测明天明天过完有了真值窗口滑动再用最近 14 天预测后天。这个过程中预测误差会累积因为窗口输入里混着之前预测出来的值模型是在自己给自己喂数据。我见过不少静态测试 MAPE 4% 的模型一上滚动验证直接飙到 12%差距全来自误差累积。滚动预测的实现思路很简单保持训练好的网络不变只改变输入窗口的内容。每预测完一步把预测值回填到特征序列里作为下一步预测的输入之一模拟真实业务的在线预测过程。horizon 7; % 连续预测未来7天 window 14; % 每次输入包含过去14天的特征 n_total size(X, 2); roll_pred zeros(1, horizon); for k 1:horizon X_roll X(:, n_total - window k : n_total - horizon k); Y_norm sim(net, X_roll); roll_pred(k) postmnmx(Y_norm, ps_y); % 回填预测值到输入序列模拟滚动预测 X(:, n_total - horizon k 1) Y_norm; end这段代码里 window 是输入窗口宽度horizon 是预测步长。先取最后一段真实数据做初始输入每预测一步就把输出 Y_norm 插回 X 的对应位置让后续预测使用含预测值的输入。这里有一个容易被忽略的细节回填的是归一化后的 Y_norm而不是反归一化后的 MW 值因为 X 矩阵内部是归一化域混入不同尺度的数值会导致输入分布错乱。我一开始就是没注意这一点回填了真实 MW 值结果滚动预测第二天误差就爆了。滚动验证的结果要和静态测试对比着看。如果滚动预测的 MAPE 比静态测试高出一倍以上说明模型对输入误差太敏感通常是特征里历史负荷的占比过高网络过度依赖昨天的数值。改进方向有两个一是增大输入窗口宽度给模型更多上下文二是降低高频特征的权重在构造输入时对历史负荷做平滑处理。从那以后我每跑一个电力预测模型都会强制走一遍滚动验证结果差的直接毙掉再好看的静态指标也不再全信。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进