简介:这份源码资源面向电力系统、能源数据分析及机器学习入门者,提供一套基于BP神经网络的用电量预测完整实现,可用于电网规划、电力调度与节能减排等场景的建模练习。压缩包共2个文件,包含1个mat数据文件和1个m脚本文件,整体约11KB,体量轻便,便于快速导入MATLAB环境运行与调试。其中mat文件承载历史用电量及相关特征数据,m脚本则串联数据读取、预处理、网络构建、训练、验证与预测等核心流程,读者可据此理解输入层、隐藏层与输出层的权重更新及反向传播机制。目前已有182人学习下载,适合希望掌握非线性预测方法、对比RNN或LSTM等改进模型,并进一步开展特征工程与超参数调优的开发者参考。
1. 基于BP神经网络的用电量预测源码:一份能跑通、能改参、能上生产的最小工程
很多做能源管理或电力调度的朋友拿到「基于BP神经网络的用电量预测-源码」这个标题时,第一反应是去搜一份现成的 Python 脚本,跑出来一条拟合曲线,然后发现预测值跟真实负荷差得离谱。问题不在 BP 神经网络本身,而在于用电量序列的周期性、温度敏感性和节假日突变,这三样东西如果没在特征工程里处理,再深的网络也是白搭。这份源码要解决的核心,是把历史负荷、气象因子、日期类型三类输入对齐成监督学习样本,用 BP 做多步预测,并且把训练、验证、推理拆成可独立替换的模块。适合有 Python 基础、手头有至少一年 15 分钟或小时级负荷数据的运维或算法同学,也适合想拿一个真实回归任务练手 BP 调参的人。
2. 用电量预测的输入到底长什么样:从原始表到监督样本
2.1 为什么不能直接把负荷列丢进 BP
BP 神经网络本质上是一个静态映射器,它不认时间顺序。你把过去 24 小时的负荷排成一行喂进去,它只能学到「第 1 个数和第 2 个数有某种关系」,但学不到「每天 8 点会爬坡」这种周期规律。所以第一步必须做滑动窗口,把时间序列切成「输入窗口 → 预测目标」的样本对。常见做法是输入过去 48 个点(小时级就是两天),输出未来 1 个点或未来 24 个点。如果输出 24 个点,就变成多输出回归,BP 的输出层要放 24 个神经元。
另一个坑是量纲。负荷数值可能在 200 到 2000 之间,温度在 -10 到 40 之间,日期类型是 0/1/2 的类别码。如果不做归一化,负荷的梯度会直接压掉温度特征的贡献。我一般对负荷做 Min-Max 归一化到 [0,1],温度做标准化,日期类型做独热编码。注意归一化参数必须用训练集拟合,再应用到验证集和测试集,否则就是数据泄露。
2.2 构造样本的代码实现与参数说明
下面这段代码把一张包含timestamp, load, temp, day_type的 CSV 转成 BP 能吃的 numpy 数组。窗口长度lookback设为 48,预测步长horizon设为 1,你可以按需改成 24。
import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler, StandardScaler def build_samples(df, lookback=48, horizon=1): # df 按时间升序,列:timestamp, load, temp, day_type load = df['load'].values.reshape(-1, 1) temp = df['temp'].values.reshape(-1, 1) day_type = pd.get_dummies(df['day_type'], prefix='dt').values # 归一化:负荷用 MinMax,温度用 Standard load_scaler = MinMaxScaler() temp_scaler = StandardScaler() load_norm = load_scaler.fit_transform(load) temp_norm = temp_scaler.fit_transform(temp) # 拼接特征:负荷 + 温度 + 日期独热 feat = np.hstack([load_norm, temp_norm, day_type]) X, y = [], [] for i in range(lookback, len(feat) - horizon + 1): X.append(feat[i-lookback:i, :].flatten()) y.append(load_norm[i:i+horizon, 0]) return np.array(X), np.array(y), load_scaler, temp_scaler逻辑说明:feat的每一行是一个时间步的全部特征,滑动窗口把lookback行展平成一个一维向量,作为 BP 的输入。y取未来horizon个负荷归一化值。返回的 scaler 要保存下来,推理时对新数据做同样的变换。
参数说明:lookback越大,输入维度越高,BP 第一层权重越多,训练越慢,但能捕捉更长周期。小时级数据建议 48 到 168(一周)。horizon为 1 时是单步预测,为 24 时输出层要改成 24 个神经元,损失函数用 MSE。日期独热编码的维度取决于你有几种日期类型,通常工作日、周末、节假日三类,独热后 3 列。
提示:如果数据里有缺失值,不要直接填 0,负荷为 0 会误导模型。用前向填充或线性插值,并在特征里加一列缺失标记。
3. BP 网络结构怎么定:层数、神经元、激活函数与优化器
3.1 用电量预测场景下的结构选型
BP 神经网络结构图在网上能搜到一大堆,但真正落到用电量预测,我见过太多人直接抄一个[128, 64, 32]的三层全连接,结果验证集 loss 震荡得厉害。用电量序列的输入维度是lookback × 特征数,比如 48×5=240,输出是 1 或 24。隐藏层不是越深越好,两层隐藏层足够拟合大多数负荷曲线。第一层神经元数建议在输入维度的 0.5 到 1.5 倍之间,第二层减半。激活函数隐藏层用 ReLU,输出层如果是单步预测用线性,多步预测也用线性,因为负荷归一化后是连续值,不需要 Sigmoid 压缩。
优化器选 Adam,学习率 1e-3 起步。损失函数用 MSE 或 Huber,Huber 对异常负荷点更鲁棒。Batch size 设 64 或 128,太小梯度噪声大,太大泛化差。早停 patience 设 10 到 15 轮,监控验证集 loss。
3.2 PyTorch 实现与训练循环
下面用 PyTorch 搭一个可配置的 BP 回归器,隐藏层维度通过列表传入,方便你改结构做对比实验。
import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class BPForecaster(nn.Module): def __init__(self, input_dim, hidden_dims, output_dim): super().__init__() layers = [] prev = input_dim for h in hidden_dims: layers.append(nn.Linear(prev, h)) layers.append(nn.ReLU()) layers.append(nn.Dropout(0.1)) # 防过拟合 prev = h layers.append(nn.Linear(prev, output_dim)) self.net = nn.Sequential(*layers) def forward(self, x): return self.net(x) def train_model(X_train, y_train, X_val, y_val, hidden_dims=[256, 128], epochs=200, lr=1e-3): device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = BPForecaster(X_train.shape[1], hidden_dims, y_train.shape[1]).to(device) optimizer = torch.optim.Adam(model.parameters(), lr=lr) criterion = nn.HuberLoss() train_loader = DataLoader(TensorDataset(torch.tensor(X_train, dtype=torch.float32), torch.tensor(y_train, dtype=torch.float32)), batch_size=128, shuffle=True) val_x = torch.tensor(X_val, dtype=torch.float32).to(device) val_y = torch.tensor(y_val, dtype=torch.float32).to(device) best_loss, patience, wait = float('inf'), 15, 0 for epoch in range(epochs): model.train() for bx, by in train_loader: bx, by = bx.to(device), by.to(device) optimizer.zero_grad() loss = criterion(model(bx), by) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_loss = criterion(model(val_x), val_y).item() if val_loss < best_loss: best_loss, wait = val_loss, 0 torch.save(model.state_dict(), 'best_bp.pth') else: wait += 1 if wait >= patience: break return model逻辑说明:BPForecaster用nn.Sequential堆叠全连接、ReLU、Dropout。Dropout 率 0.1 是保守值,数据量小于一万条时可以调到 0.2。训练循环里每个 epoch 后在验证集上算 Huber loss,保存最优权重,连续 15 轮不降就停。
参数说明:hidden_dims默认[256, 128],输入维度 240 时第一层 256 约等于 1.07 倍,合理。lr从 1e-3 开始,如果 loss 下降太慢可以试 3e-3,震荡就降到 5e-4。epochs设 200 是上限,实际早停通常在 50 到 80 轮。batch_size128 适合几千到几万样本。
注意:如果你用 GPU 训练,推理时也要把模型和数据放到同一设备,否则报张量设备不一致。保存的
best_bp.pth只含权重,加载前要先实例化同样结构的模型。
4. 训练完怎么验证:指标、残差图和滚动预测
4.1 别只看 MSE,要看 MAPE 和峰谷误差
MSE 对负荷量级敏感,2000 的负荷和 200 的负荷算出来的 MSE 差两个数量级,没法横向比较。用电量预测业务方最关心的是 MAPE(平均绝对百分比误差)和峰谷时段的误差。我一般同时输出三个指标:MAPE、RMSE、以及早高峰(7-9 点)和晚高峰(18-21 点)的 MAE。如果整体 MAPE 只有 3%,但晚高峰 MAE 是平时的三倍,这个模型上生产照样被骂。
残差图也要画。把验证集的预测值减真实值,按时间画散点。如果残差在某个时间段系统性偏正或偏负,说明特征里缺了那个时间段的特殊信息,比如温度骤降或节假日。
4.2 滚动预测与反归一化代码
下面这段做单步滚动预测:每次用真实历史窗口预测下一个点,然后把预测值填回窗口继续预测。这比一次性多步预测更贴近实际调度场景。
def rolling_predict(model, feat, lookback, scaler, device='cpu'): model.eval() preds = [] window = feat[:lookback].copy() for i in range(lookback, len(feat)): x = torch.tensor(window.flatten(), dtype=torch.float32).unsqueeze(0).to(device) with torch.no_grad(): p = model(x).cpu().numpy()[0, 0] preds.append(p) # 用真实特征更新窗口,只把负荷列替换为预测值 next_row = feat[i].copy() next_row[0] = p window = np.vstack([window[1:], next_row]) preds = np.array(preds).reshape(-1, 1) return scaler.inverse_transform(preds).flatten()逻辑说明:window初始是前lookback个时间步的全部特征。每次预测后,把下一时刻的真实特征行拿过来,但负荷列替换成预测值,再滑入窗口。这样温度、日期类型用的是真实值,负荷是预测值,符合实际中气象可预报、负荷需预测的场景。
参数说明:feat是归一化后的特征矩阵,列顺序必须和训练时一致。scaler是训练时保存的负荷 MinMaxScaler,反归一化后才能和真实负荷比。如果做多步预测,把p换成向量,next_row[0]取第一个预测值即可。
5. 避坑与排查:用电量预测源码落地时最容易翻车的五件事
5.1 现象:验证集 loss 比训练集低很多
原因:数据泄露。最常见的是归一化时用了全量数据拟合 scaler,或者滑动窗口切分时训练集和验证集有重叠时间步。解决:先按时间切分训练/验证/测试,再分别构造样本,scaler 只在训练集上 fit。
5.2 现象:预测曲线整体平移,形状对但数值差一截
原因:反归一化时用错了 scaler,或者推理时输入特征没有做和训练时一致的归一化。解决:把训练时的 scaler 用 joblib 保存,推理脚本加载同一个 scaler,对负荷和温度分别变换。检查特征列顺序是否和训练时完全一致。
5.3 现象:晚高峰预测总是偏低
原因:训练集里晚高峰样本占比少,或者温度特征在晚高峰时段区分度不够。解决:在损失函数里给高峰时段样本加权,或者单独训练一个高峰模型。也可以在特征里加入「距下一个整点小时数」这种时间位置编码。
5.4 现象:换了新数据后 MAPE 突然从 3% 跳到 15%
原因:新数据的量纲或分布变了,比如负荷整体上了一个台阶,MinMax 的 min/max 失效。解决:改用滚动窗口的局部归一化,或者定期用新数据重新 fit scaler。生产环境建议每月更新一次归一化参数。
5.5 现象:训练时 loss 正常下降,推理时输出全是同一个值
原因:输入特征里有一列方差为 0,比如日期独热编码在某个批次里全是同一类,导致 ReLU 全部死亡。解决:检查特征矩阵每列的方差,去掉常数列。如果 ReLU 死亡,换 LeakyReLU 或降低学习率。
6. 把 BP 预测源码用起来:从单点预测到多步与在线更新
单步预测只能告诉你下一小时负荷多少,实际调度要的是未来一天 24 个点的曲线。最直接的办法是把输出层改成 24 维,损失函数用 MSE,一次前向出 24 个值。但这样每个输出点独立,曲线可能不够平滑。我一般会在损失里加一个平滑项,惩罚相邻输出点的二阶差分。
def smooth_loss(pred, target, alpha=0.1): mse = nn.MSELoss()(pred, target) diff = pred[:, 2:] - 2 * pred[:, 1:-1] + pred[:, :-2] return mse + alpha * torch.mean(diff ** 2)alpha控制平滑程度,0.05 到 0.2 之间试。太大曲线会变平,峰谷被削掉。
在线更新方面,不要每天重新训练全量模型,太慢。我的习惯是固定底层权重,只拿最近一周数据微调最后一层,学习率降到 1e-4,跑 10 个 epoch。这样既能跟踪负荷趋势变化,又不会把学到的周期规律冲掉。微调前记得把新数据用同一个 scaler 变换,如果负荷基线漂移超过 10%,先更新 scaler 再微调。
验证多步预测好不好,别只看 MAPE。把预测的 24 点曲线和真实曲线画在一起,看峰谷时刻是否对齐。如果峰谷整体平移了一个小时,说明输入窗口的末端对齐有问题,检查时间戳是否按小时严格递增,有没有缺小时。我踩过最冤的一次坑是数据里混了夏令时切换,凌晨少了一个小时,导致所有窗口错位,调了两天才发现。希望帮到你。
本文还有配套的精品资源,点击获取