ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于BP神经网络的城市电网负荷预测:从数据构造到滚动预测的完整实战

基于BP神经网络的城市电网负荷预测:从数据构造到滚动预测的完整实战 简介这份资源面向电气工程、自动化及相关专业的本科生与研究生以及从事电网调度、负荷分析的技术人员提供一套基于MATLAB实现的BP神经网络城市电网负荷预测方案。资源包共11个文件约585KB包含4个m脚本文件用于构建网络、训练模型与计算MSE、RMSE、MBE、MAE及R²等误差指标2个mat数据文件存放训练样本与训练好的网络参数1个xlsx表格提供原始负荷数据另有4张png图片展示预测结果与拟合曲线便于直观核对模型表现。代码结构完整、注释清晰可直接运行并在此基础上调整网络层数、学习率或输入特征扩展为短期或中期负荷预测应用。目前已有174人学习下载适合作为课程设计、毕业设计或科研入门的基础模板帮助读者快速理解BP神经网络在电力负荷预测中的建模流程与评估方法。1. 城市电网负荷预测为什么BP神经网络依然是绕不开的基线做城市电网负荷预测的人迟早会撞上同一个尴尬LSTM、Transformer、时序基础模型轮番上阵论文指标刷得飞起可一旦落到某个地级市供电公司的日负荷曲线MAPE 反而被一个三层 BP 神经网络按在地上摩擦。这不是玄学是数据量、噪声结构和预测粒度共同决定的。城市电网负荷受气温、节假日、工作日类型、电价政策多重耦合单条 96 点或 24 点曲线里混着大量随机扰动深度模型参数量一上去就过拟合而 BP 网络结构简单、训练快、对中小样本友好天然适合做基线。这篇笔记就围绕「基于 BP 神经网络的城市电网负荷预测代码完整数据齐全」这个方向把数据怎么造、网络怎么搭、参数怎么调、坑在哪一条条讲清楚。适合手里有历史负荷数据、想快速跑通一套可复现预测流程的电气与算法工程师也适合刚接触 bp 神经网络 python 代码、想找一个真实场景练手的人。2. 先把负荷预测问题翻译成 BP 能吃的矩阵2.1 城市电网负荷预测到底在预测什么城市电网负荷预测按时间尺度分三类超短期未来 5 分钟到 1 小时、短期未来 1 天到 1 周、中长期未来数月到数年。BP 神经网络最常落地的场景是短期日负荷预测也就是用过去若干天的 24 点或 96 点负荷加上温度、星期类型等外生变量预测明天同一时刻的负荷值。这里有个容易翻车的认知很多人把负荷预测当成纯时间序列外推只喂历史负荷结果周末和工作日混在一起网络学出一个四不像的平均曲线。正确做法是把「时间标签」显式编码成特征让网络知道今天是不是周六、是不是国庆假期。从数学上看我们要拟合的是这样一个映射给定输入向量 x历史负荷窗口 气象 日历特征输出 y待预测时刻的负荷。BP 神经网络做的事就是用多层感知机逼近这个非线性函数。它的优势在于不需要显式建模负荷的周期性和趋势性只要特征给得够隐层能自己学出这些模式。但代价是它对输入特征的尺度非常敏感这也是后面要重点讲的归一化问题。2.2 输入输出矩阵的构造滑动窗口与特征拼接假设你手里有一份某市 2022 年 1 月 1 日到 2024 年 12 月 31 日的 15 分钟粒度负荷数据每天 96 个点。直接把这 96 个点全塞给网络预测下一天 96 个点输入维度 96、输出维度 96参数量爆炸且样本数只有一千出头必然过拟合。常见做法是滑动窗口用过去 7 天同一时刻及前后时刻的负荷预测未来 1 天对应时刻。下面这段代码展示如何把原始宽表转成 BP 可用的监督学习样本。import numpy as np import pandas as pd def build_sliding_window(load_series, window_days7, points_per_day96, horizon1): load_series: 一维负荷序列长度 window_days*points_per_day horizon window_days: 历史窗口天数 points_per_day: 每天采样点数15分钟粒度是96 horizon: 预测未来第几个点1表示预测下一个15分钟点 返回 X: (样本数, window_days*points_per_day), y: (样本数,) window_size window_days * points_per_day X, y [], [] for i in range(len(load_series) - window_size - horizon 1): X.append(load_series[i:i window_size]) y.append(load_series[i window_size horizon - 1]) return np.array(X), np.array(y) # 假设 df 是 DataFrame列 load 是负荷索引是时间戳 # 先按时间排序再取 values load df.sort_index()[load].values.astype(np.float32) X, y build_sliding_window(load, window_days7, points_per_day96, horizon1) print(X.shape, y.shape) # 例如 (103000, 672) (103000,)这段代码的逻辑是把连续负荷序列切成一个个长度为 6727×96的窗口每个窗口对应一个标签即窗口后第 horizon 个点的负荷。参数 window_days 决定网络能看到多长的历史太小则学不到周周期太大则样本数锐减且维度灾难。我一般从 7 天起步如果数据跨度超过两年且节假日效应明显可以加到 14 天。points_per_day 必须和你的数据粒度一致15 分钟是 961 小时是 24搞错了整个矩阵就错位。horizon 控制预测步长做日前 96 点预测时通常训练 96 个模型或改成多输出结构这里先按单点预测讲清楚原理。提示滑动窗口构造样本时务必保证时间顺序不要 shuffle 后再切窗口否则会引入未来信息泄漏线下指标好看线上直接崩。2.3 外生特征怎么加温度、星期与节假日编码只靠历史负荷网络学不到「明天降温 10 度所以负荷要涨」这种因果。城市电网负荷对温度极其敏感夏季空调负荷能占峰值 30% 以上。所以输入矩阵里必须拼上气象和日历特征。常见做法是对每个预测目标时刻取对应日期的最高温、最低温、平均温以及星期几的 one-hot 编码、是否节假日的 0/1 标记。下面给出拼接示例。def add_exogenous_features(X, timestamps, temp_series, holiday_set): X: 滑动窗口负荷矩阵 (n_samples, window_size) timestamps: 每个样本对应预测时刻的时间戳列表 temp_series: 与负荷同索引的温度 Series holiday_set: 节假日日期集合如 {2024-10-01, ...} 返回 X_aug: 拼接后的特征矩阵 feats [] for i, ts in enumerate(timestamps): date ts.date() # 温度特征取当天最高、最低、平均 day_temp temp_series[temp_series.index.date date] t_max day_temp.max() if len(day_temp) else 25.0 t_min day_temp.min() if len(day_temp) else 15.0 t_avg day_temp.mean() if len(day_temp) else 20.0 # 星期 one-hot weekday ts.weekday() weekday_onehot [1 if weekday d else 0 for d in range(7)] # 节假日标记 is_holiday 1 if str(date) in holiday_set else 0 feats.append([t_max, t_min, t_avg] weekday_onehot [is_holiday]) feats np.array(feats, dtypenp.float32) return np.hstack([X, feats]) # 调用示例 timestamps df.sort_index().index[7*961-1 : 7*961-1len(X)] X_aug add_exogenous_features(X, timestamps, df[temp], holiday_set{2024-01-01,2024-10-01}) print(X_aug.shape) # (n_samples, 67211)这里温度取当天统计值而不是逐点温度是因为预测明天负荷时逐点温度预报本身就有误差用日统计量更稳健。星期 one-hot 用 7 维而不是 1 维序数是为了避免网络把星期三当成星期三倍的关系。节假日标记单独一列让网络自己学节假日对负荷的抬升或压低。参数上温度特征建议做标准化星期和节假日保持 0/1 即可。拼接后特征维度变成 67211683如果显存或内存吃紧可以对历史负荷窗口做降采样比如每 4 个点取平均把 672 压到 168。3. BP 神经网络结构设计与训练参数怎么定3.1 三层还是四层隐层节点数的经验公式与试错BP 神经网络结构图里最常见的是输入层-隐层-输出层但隐层到底几层、每层多少节点没有万能公式。对于城市电网负荷预测这种输入 600 多维、输出 1 维的任务我一般先用单隐层试水节点数从 64 起步按 2 的幂次往上加64、128、256。经验公式有sqrt(输入维度输出维度)alpha算出来大概 26 左右但实际用这么少节点欠拟合严重因为负荷曲线的高频波动需要足够容量去拟合。下面给出一个可配置的 PyTorch 实现。import torch import torch.nn as nn class LoadBPNet(nn.Module): def __init__(self, input_dim, hidden_dims[128, 64], output_dim1, dropout0.2): super().__init__() layers [] prev input_dim for h in hidden_dims: layers.append(nn.Linear(prev, h)) layers.append(nn.BatchNorm1d(h)) layers.append(nn.ReLU()) layers.append(nn.Dropout(dropout)) prev h layers.append(nn.Linear(prev, output_dim)) self.net nn.Sequential(*layers) def forward(self, x): return self.net(x) # 使用示例 model LoadBPNet(input_dimX_aug.shape[1], hidden_dims[256, 128], output_dim1, dropout0.2) print(model)这段代码里 hidden_dims 控制隐层结构和节点数[256,128] 表示两个隐层。BatchNorm1d 对每批数据做归一化能显著加快收敛尤其当输入特征尺度不一致时。ReLU 比 Sigmoid 更不容易梯度消失是现在的默认选择。Dropout 设 0.2 到 0.5 之间数据量小就调大数据量大就调小。输出层不加激活函数因为负荷预测是回归任务输出范围是实际负荷值。参数上input_dim 必须等于 X_aug 的第二维搞错了直接报维度错误。如果单隐层 256 节点验证集 loss 还在降就加第二层如果训练 loss 降但验证 loss 反弹说明过拟合加 dropout 或减节点。3.2 归一化别让温度数值把负荷特征淹没BP 神经网络对输入尺度敏感温度是 0 到 40负荷可能是 5000 到 15000星期是 0 或 1。如果不做归一化梯度更新会被大数值特征主导网络学到的权重全偏向负荷绝对值温度特征形同虚设。常见做法是对负荷和温度做 Min-Max 归一化到 [0,1]或者 Z-Score 标准化。我一般用 Min-Max因为负荷有明确上下界归一化后反归一化也直观。下面给出训练集和测试集分开归一化的代码注意测试集要用训练集的 min/max不能用自己的否则泄漏。from sklearn.preprocessing import MinMaxScaler # 假设 X_train, X_test, y_train, y_test 已经切分好 # 对负荷窗口部分和温度部分分别归一化 scaler_load MinMaxScaler() scaler_temp MinMaxScaler() # 前 672 维是负荷窗口后面是温度等特征 X_train_load scaler_load.fit_transform(X_train[:, :672]) X_test_load scaler_load.transform(X_test[:, :672]) # 温度列假设在 672:675 X_train_temp scaler_temp.fit_transform(X_train[:, 672:675]) X_test_temp scaler_temp.transform(X_test[:, 672:675]) # 拼接回去星期和节假日保持原样 X_train_scaled np.hstack([X_train_load, X_train_temp, X_train[:, 675:]]) X_test_scaled np.hstack([X_test_load, X_test_temp, X_test[:, 675:]]) # 标签也要归一化 scaler_y MinMaxScaler() y_train_scaled scaler_y.fit_transform(y_train.reshape(-1,1)).ravel() y_test_scaled scaler_y.transform(y_test.reshape(-1,1)).ravel()逻辑说明fit_transform 只在训练集上做测试集用 transform这样模拟真实部署时只有历史数据可用的场景。负荷窗口和温度分开归一化是因为它们物理量纲不同混在一起归一化会让负荷的波动范围被温度拉偏。星期和节假日是 0/1不需要再归一化。标签归一化后网络输出也在 [0,1]预测时用 scaler_y.inverse_transform 还原。参数上MinMaxScaler 默认缩放到 [0,1]如果数据有极端离群点可以改用 RobustScaler用中位数和四分位距抗噪更好。3.3 训练循环与早停验证集 loss 不降就停BP 网络训练最怕两件事学不够和学过头。学不够是迭代次数太少学过头是过拟合。早停是性价比最高的正则化手段。下面给出完整训练循环包含早停和模型保存。import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset # 转成 Tensor X_train_t torch.tensor(X_train_scaled, dtypetorch.float32) y_train_t torch.tensor(y_train_scaled, dtypetorch.float32).unsqueeze(1) X_val_t torch.tensor(X_test_scaled, dtypetorch.float32) y_val_t torch.tensor(y_test_scaled, dtypetorch.float32).unsqueeze(1) train_loader DataLoader(TensorDataset(X_train_t, y_train_t), batch_size256, shuffleTrue) val_loader DataLoader(TensorDataset(X_val_t, y_val_t), batch_size512, shuffleFalse) model LoadBPNet(input_dimX_train_scaled.shape[1], hidden_dims[256,128]) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) best_val_loss float(inf) patience 20 counter 0 for epoch in range(300): model.train() train_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() optimizer.step() train_loss loss.item() * xb.size(0) train_loss / len(train_loader.dataset) model.eval() val_loss 0.0 with torch.no_grad(): for xb, yb in val_loader: pred model(xb) val_loss criterion(pred, yb).item() * xb.size(0) val_loss / len(val_loader.dataset) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_bp_load.pt) counter 0 else: counter 1 if counter patience: print(fEarly stop at epoch {epoch}) break if epoch % 20 0: print(fEpoch {epoch}, train_loss {train_loss:.6f}, val_loss {val_loss:.6f})逻辑说明每个 epoch 先训练后验证验证集不参与梯度更新。早停的 patience 设 20意思是验证 loss 连续 20 个 epoch 不创新低就停。batch_size 256 是经验值数据量小可以降到 64数据量大可以升到 1024。学习率 1e-3 是 Adam 的默认值如果 loss 震荡就降到 1e-4如果收敛太慢就升到 5e-3。weight_decay 是 L2 正则1e-5 到 1e-3 之间调越大越抑制过拟合。保存 best 模型而不是最后一个 epoch 的模型是因为最后一个可能已经过拟合了。4. 避坑与排查城市电网负荷预测里最容易翻车的五件事4.1 现象验证集 MAPE 只有 2%上线后误差飙到 15%原因训练集和测试集按随机切分而不是按时间切分。负荷数据有强时序自相关随机切分会让未来数据泄漏到训练集线下指标虚高。解决必须按时间顺序切分比如前 80% 时间做训练后 20% 做测试中间留一段 gap 避免窗口重叠。代码上就是split_idx int(len(X)*0.8)不要用train_test_split(shuffleTrue)。4.2 现象网络输出几乎是一条直线完全不跟踪负荷波动原因归一化没做对或者学习率太小导致欠拟合。常见的是只对负荷做了归一化温度还是原始值梯度被温度主导。解决检查所有输入特征的取值范围确保都在 [0,1] 或均值 0 方差 1 附近。另外把学习率从 1e-4 提到 1e-3 试试观察训练 loss 是否下降。如果 loss 不降检查标签是否归一化以及输出层是否误加了 Sigmoid。4.3 现象节假日预测误差特别大平时准得离谱原因节假日样本太少网络没见过足够多的春节、国庆模式。解决两个方向一是把节假日标记做得更细比如区分春节、国庆、周末、普通工作日四类二是对节假日样本做过采样或者在 loss 里给节假日样本更高权重。我一般用加权 MSE节假日权重设 3 到 5 倍。4.4 现象训练 loss 一直降验证 loss 从第 10 个 epoch 开始涨原因过拟合。BP 网络参数量相对于样本数太大或者 dropout 没开。解决先加 dropout 0.3再加 weight_decay 1e-4还不行就减隐层节点从 256 降到 128。另外检查是不是用了太多历史天数7 天窗口如果样本只有几千条改成 3 天试试。4.5 现象预测曲线整体偏高或偏低形状对但数值有系统偏差原因归一化反变换时用错了 scaler或者训练集和测试集的负荷分布不一致。解决确认反归一化用的是训练集的 scaler_y而不是重新 fit 的。如果测试集负荷整体比训练集高一个台阶说明数据有趋势漂移需要对负荷做差分再预测或者定期用新数据微调模型。5. 把 BP 基线做扎实滚动预测与误差归因的两个技巧5.1 滚动预测用预测值喂回输入窗口单点预测只能预测下一个 15 分钟实际业务要的是未来 24 小时 96 个点。常见做法是滚动预测先预测第 1 个点把预测值拼到历史窗口末尾再预测第 2 个点依次类推。这样做误差会累积但实现简单。下面给出滚动预测代码。def rolling_forecast(model, last_window, scaler_load, scaler_temp, scaler_y, steps96): last_window: 归一化后的最后一个历史窗口形状 (1, 67211) steps: 预测步数96 表示未来 24 小时 返回反归一化后的预测序列 model.eval() preds [] window last_window.copy() for _ in range(steps): with torch.no_grad(): x torch.tensor(window, dtypetorch.float32) pred_scaled model(x).item() preds.append(pred_scaled) # 把预测值拼到负荷窗口末尾去掉最老的一个点 new_load scaler_load.inverse_transform([[pred_scaled]])[0][0] new_load_scaled scaler_load.transform([[new_load]])[0][0] window np.roll(window, -1, axis1) window[0, -12] new_load_scaled # 假设负荷窗口最后一位在 -12 之前 # 反归一化 preds np.array(preds).reshape(-1,1) return scaler_y.inverse_transform(preds).ravel()逻辑说明每次预测一个点后把预测值反归一化再重新归一化拼回窗口。np.roll 把窗口左移一位腾出末尾位置。这里要注意窗口里负荷部分和特征部分的索引别把温度特征覆盖了。滚动预测的误差会累积96 步后可能偏 5% 到 10%所以实际业务里更稳的做法是训练一个多输出网络直接输出 96 个点或者对每个预测步单独训一个模型。5.2 误差归因按温度区间和星期类型拆 MAPE模型上线后别只看整体 MAPE。把测试集按温度区间5度、5-15度、15-25度、25度和星期类型工作日、周末、节假日分组分别算 MAPE能快速定位模型短板。我一般用 pandas 做分组统计下面是一个示例表。分组维度分组MAPE样本数温度5度3.2%1200温度5-15度2.1%3500温度15-25度1.8%4200温度25度4.5%2100星期工作日2.0%8000星期周末2.8%2500星期节假日6.7%500从这张表能看出高温和节假日是误差大头。高温段误差大是因为空调负荷非线性强BP 网络在极端温度样本少时学不好。节假日误差大是样本不平衡。针对这两个短板可以单独对高温样本和节假日样本做微调或者引入温度平方项作为额外特征。这个归因习惯是我踩了无数次坑之后养成的比盲目调参有用得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表