
简介基于LSTM的电力负荷预测项目实践资源面向电力系统分析人员、机器学习学习者以及需要解决时间序列预测问题的算法工程师。项目围绕负荷数据特性完整梳理长短期记忆网络建模流程将天气、季节、节假日等影响因子融入特征处理同时体现LSTM通过门机制控制信息流动、缓解长序列梯度问题的设计思路帮助读者理解从业务问题到深度学习方案的转化过程。压缩包共三百五十个文件约十一点三四兆以一百七十个CSV数据文件为核心配套十四个PY脚本完成数据清洗、归一化、特征工程、模型构建、超参数调优与评估并含PNG可视化、模型检查点及元数据文件目录结构便于按步骤复现。资源已有二千四百二十四人浏览学习训练记录保留三千步至一万步不同阶段对应的误差对照可用于权衡训练成本与精度。读者可获得能够直接运行的LSTM预测工程框架覆盖数据预处理、训练验证、结果可视化的完整链路适合作为电力负荷预测竞赛、课程设计或实际项目落地的参考基线。1. 基于LSTM的电力负荷预测这个AI方向解决什么问题电力负荷预测不是什么新鲜课题但把它交给LSTM来做近几年几乎成了人工智能落地能源领域的标准入门动作。原因很简单负荷数据是典型时间序列早晚高峰、工作日与周末、季节更替这些规律既长又杂传统ARIMA需要人工抽特征而LSTM能直接从历史序列里学。这篇文章不跟你讲泛泛的神经网络原理只讲怎么从一份原始用电记录出发完成数据清洗、滑窗切片、模型训练、指标评估并绕过我在实际项目中踩过的坑。无论你是做毕业设计还是刚进电力行业做AI算法照着这套流程都能在本地跑通一个能用的负荷预测模型。2. 负荷数据预处理把原始用电记录变成LSTM能吃的样本很多人拿到数据就急着搭LSTM结果模型死活不收敛最后发现是原始数据里全是坑。电力负荷数据来自SCADA系统或智能电表经常有采集缺失、通信跳变、人工补录等情况。预处理不是可有可无的步骤它直接决定LSTM看到的是“规律”还是“噪声”。2.1 缺失值与异常值清洗先解决“脏数据”再谈模型先说缺失值。常见做法是用前后时刻的均值插值但如果连续缺失好几个小时线性插值比均值更稳。异常值更麻烦负荷曲线在凌晨突然飙到三倍多半是电表故障或数据上报错误不是真实用电。我用一个简单阈值法先算每个点与前后两天同一时刻的差值超过该时刻均方差的3倍就判为异常替换成中位数。import pandas as pd import numpy as np df pd.read_csv(load.csv, parse_dates[timestamp], index_coltimestamp) df df.resample(15min).mean() # 统一到15分钟间隔 # 缺失值连续缺失超过2个点用线性插值否则用邻值填充 df[value] df[value].interpolate(limit8, limit_directionboth) df[value] df[value].fillna(methodffill) # 兜底 # 异常值与前后两天同时刻作差 s df[value].copy() base (s.shift(96) s.shift(-96)) / 2 # 96个15分钟 1天 diff (s - base).abs() threshold 3 * (diff.rolling(96, min_periods1).std()) df[value] s.where(diff threshold, base)这段代码里有几个讲究。resample(15min)先把数据对齐到固定频率后续滑窗才不会因为时间戳错位而出问题。interpolate(limit8)表示最多连续插值8个点也就是2小时再多就说明采集系统有大故障直接填充默认值更安全。异常值检测里shift(96)是硬编码的因为一天的15分钟点数正好是96如果你的数据是小时级这里要改成24。清洗完成后一定要画一遍曲线肉眼扫一遍有没有“断崖”或“尖刺”。我见过有人用3σ标准去筛负荷结果把春节假期真实的高负荷当成异常洗掉了。所以阈值不要拍脑袋先看历史数据分布。2.2 归一化方法选择MinMax与Z-Score哪个适合负荷LSTM用tanh和sigmoid做激活函数输入数值范围过大或过小都会让梯度消失。负荷数据的特征是有量纲、正数、有时出现瞬时尖峰。MinMax归一化能把数据压到[0,1]适合负荷这种有明确上下界的序列但如果数据里有极端尖峰MinMax会把正常段的数值压缩得很小。Z-Score对尖峰鲁棒性更好但归一化后的值没有上下界LSTM最后全连接层的输出可能会摆动。我的默认选择是MinMax但要对尖峰做截断取99.9%分位数作为上限超过的算异常。注意归一化必须只用训练集的统计量测试集只能调用训练集已经算好的min和max否则会引入未来信息相当于作弊。from sklearn.preprocessing import MinMaxScaler upper np.percentile(train[value], 99.9) lower train[value].min() train_clean train[value].clip(lowerlower, upperupper) scaler MinMaxScaler(feature_range(0, 1)) scaler.fit(train_clean.values.reshape(-1, 1)) train_scaled scaler.transform(train_clean.values.reshape(-1, 1)) test_scaled scaler.transform(test[value].values.reshape(-1, 1))clip(lowerlower, upperupper)这一步极其重要。你不提前截断MinMax会认为那个尖峰是正常最大值把所有真实高负荷段压到0.5附近模型训练时特征区分度肉眼可见地下降。scaler.fit只能喂训练集测试集用同一个scaler直接transform这是时间序列预测的底线。2.3 滑窗切片与训练集划分时间序列不能随机打乱LSTM输入是固定长度的窗口。比如用过去48个小时的负荷预测未来15分钟窗口长度就是192如果数据是15分钟频率。滑窗切片就是一个移动的截取动作窗口从0到191预测192然后窗口从1到192预测193。这里有个新手必翻车的点训练集和测试集必须按时间顺序切不能像图像分类那样随机打乱。随机打乱等于让模型看到了“未来”的统计数据验证指标会虚高。def create_sequences(data, seq_len96, pred_len1): xs, ys [], [] for i in range(len(data) - seq_len - pred_len 1): xs.append(data[i:i seq_len]) ys.append(data[i seq_len:i seq_len pred_len]) return np.array(xs), np.array(ys) # seq_len96表示用过去一天96个15分钟预测下一时刻 X_train, y_train create_sequences(train_scaled.squeeze(), seq_len96) X_test, y_test create_sequences(test_scaled.squeeze(), seq_len96) # 保持时间顺序不要shuffleseq_len不是拍脑袋定的。如果数据是15分钟粒度96就是一天可以考虑如果是1小时粒度24就是一天。做负荷预测至少要包含一个完整日周期不然LSTM学不到“今天下午2点和昨天下午2点用电相似”这种规律。pred_len设为1就是单步预测设成n就是n步预测但多步预测的误差会随着步长指数放大后面会专门讲这个问题。训练集、验证集、测试集的比例我习惯用7:1:2验证集放在训练集之后、测试集之前三个集合不允许有重叠时间点。特别注意测试集必须是最后一段连续时间不能随机抽取这样才能模拟“模型在当前时刻预测未来”的真实场景。3. 用PyTorch实现LSTM负荷预测模型结构与训练代码这一章是核心动作。我直接给出能跑的PyTorch代码并解释每个参数为什么这么设。你跑通之后调整维度就能用到自己的数据上。3.1 网络搭建从nn.LSTM到全连接输出层LSTM层负责提取时间依赖但最后要输出一个具体负荷数值所以后面必须接一个全连接层。常见做法是输入(batch, seq_len, 1)经过LSTM后取最后一个时刻的hidden state再通过Linear输出预测值。为什么不取所有时刻的输出因为我们要预测的是未来不是序列标注最后时刻的hidden已经编码了整个窗口的信息。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, output_size1): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x shape: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # out: (batch, seq_len, hidden_size) last_out out[:, -1, :] # 取最后一个时刻的输出 return self.fc(last_out)hidden_size是LSTM记忆容量负荷预测这种单变量序列64或128足够。num_layers堆叠两层能捕捉不同时间尺度的模式但更多层容易过拟合而且训练慢一倍。batch_firstTrue是为了让输入形状更直观尺寸是(batch, seq_len, features)不用去记输入转置有没有做对。输入特征数input_size1是因为我们处理的是单变量负荷后面进阶做多变量时改成2或3。3.2 训练参数batch_size、学习率与epoch如何设置很多教程直接写optimizer.Adam(model.parameters(), lr0.001)但在负荷预测任务上这个默认学习率经常不够稳。我的经验是如果数据量小于几万条Adam的默认0.001偏大容易在损失曲线上看到“锯齿”震荡。先用0.0005跑几十个epoch不降再调大。batch_size直接影响梯度噪声。时间序列数据有连续性batch喂太大模型会偏向短期均值喂太小训练波动大。我一般用32或64窗口长度96时64个batch对显存要求很低。epoch不要一开始就设200配合早停通常跑50100个epoch就能看到结果。model LSTMPredictor(input_size1, hidden_size64, num_layers2, output_size1) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.0005) X_train_t torch.from_numpy(X_train).float().unsqueeze(-1) # (样本数, 96, 1) y_train_t torch.from_numpy(y_train).float().unsqueeze(-1)unsqueeze(-1)是在最末尾加一维把 (样本数, 96) 变成 (样本数, 96, 1)这样才能匹配input_size1。如果你忘了这步PyTorch会直接报三维输入不匹配的错。3.3 训练循环与早停用验证集防止过拟合训练循环本身不复杂但负荷预测里验证集的作用不只是看看指标它决定你什么时候停。我见过很多人用固定epoch训练结果验证集损失在第30个epoch就开始上升硬是跑完100个epoch最终模型把噪声都背下来了。早停的做法是每个epoch算验证集损失如果连续5个epoch没有改善就恢复到历史最优的模型权重。best_val_loss float(inf) patience 5 wait 0 for epoch in range(100): model.train() train_loss 0 for i in range(0, len(X_train_t), batch_size): X_batch X_train_t[i:ibatch_size] y_batch y_train_t[i:ibatch_size] optimizer.zero_grad() pred model(X_batch) loss criterion(pred, y_batch) loss.backward() optimizer.step() train_loss loss.item() # 验证集 model.eval() with torch.no_grad(): val_pred model(X_val_t) val_loss criterion(val_pred, y_val_t).item() if val_loss best_val_loss: best_val_loss val_loss best_state model.state_dict() wait 0 else: wait 1 if wait patience: model.load_state_dict(best_state) print(fEarly stop at epoch {epoch}) breakmodel.train()和model.eval()必须成对出现。LSTM层的dropout在train模式下才生效eval模式下自动关闭如果你忘记切换验证集损失会被扰动得忽高忽低早停跟着误判。patience5是个保守值如果你的数据噪声大可以放到7相反如果验证集损失一直在降只是降得慢不要触发早停让它跑完。4. 从预测曲线到评估指标单步预测的落地验证模型训练完你得到的是归一化范围内的数字必须反归一化转回实际的兆瓦MW或千瓦kW量级才能评估误差。这一章讲清楚评价指标怎么算、预测结果怎么画以及单步预测和多步预测在业务上的区别。4.1 反归一化与评价指标MAE、RMSE、MAPE怎么算反归一化很简单pred_actual pred_scaled * (upper - lower) lower注意这里upper和lower是训练集截断后的分位数不是测试集的最大最小值。如果搞混误差计算会凭空多出几个百分点。from sklearn.metrics import mean_absolute_error, mean_squared_error pred_np pred.cpu().numpy().squeeze() true_np y_test.squeeze().squeeze() # 反归一化 pred_actual pred_np * (upper - lower) lower true_actual true_np * (upper - lower) lower mae mean_absolute_error(true_actual, pred_actual) rmse np.sqrt(mean_squared_error(true_actual, pred_actual)) mape np.mean(np.abs((true_actual - pred_actual) / true_actual)) * 100MAPE在负荷预测里有个坑凌晨低谷负荷接近0分母很小一点点绝对误差就会放大成巨大的百分比。我看指标时先看MAE和RMSEMAPE只作为参考。如果测试集里包含夜间时段MAPE能飙到几十这不代表模型不行而是评价指标本身对低基数不友好。这时候可以用MAE / 平均负荷作为替代百分比误差。4.2 预测结果可视化一眼看出模型是否“偷懒”数值指标再好也必须画图。负荷预测最常见的问题是“滞后性”预测曲线比真实曲线晚一个采样周期。画图能直接看出这个现象。我会选取测试集里连续3天把真实负荷和预测负荷画在一起分工作日和周末对比。import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(true_actual[:288], labelActual, linewidth1.5) plt.plot(pred_actual[:288], labelPredicted, linewidth1.5, linestyle--) plt.legend() plt.xlabel(15min points (3 days)) plt.ylabel(Load (MW)) plt.title(Single-step LSTM Load Forecast) plt.show()取前288个点代表3天96×3如果预测曲线整体向右偏移就说明模型学成了“上一时刻的复制品”而不是真正的预测。这种滞后现象在单步预测里极其常见原因是LSTM在最小化MSE时发现历史最后一个值本身就是对下一时刻的最好估计于是模型偷懒了。解决思路在第5章。4.3 单步预测与多步预测的差别业务上该用哪种单步预测是“用过去96个点预测下1个点”每次预测完把真实值滑进窗口再测下一天。这在评估模型能力时是公平的但在实际业务里不实用。调度员想知道未来24小时最多168小时的负荷曲线他们需要多步预测。多步预测有两种做法递推多步和直接多步。递推多步是把预测值当成下一步的输入误差会累积直接多步是让LSTM输出多个值改最后一层Linear的output_size但会丢失时间相关性。我的建议是项目验证阶段用单步因为指标干净便于模型对比上线阶段如果非要多步常用是Seq2Seq结构或Transformer但那是后话。这篇基于LSTM的实践先守住单步预测这个基线再谈扩展。5. LSTM电力负荷预测避坑指南5个高频翻车现场这一章写我实际调参和给别人review代码时反复见到的问题。每条都按“现象→原因→解决”来写可以直接对照你的报错和预测曲线。5.1 预测曲线滞后严重模型学成了“上一个值”现象预测曲线整体比真实曲线晚一个点峰值和谷值都错位但误差却不大。 原因LSTM在MSE损失下发现输出历史最后一个值是最低成本的预测尤其当数据有强自相关性时。这本质是模型退化不是网络结构问题。 解决第一加入差分输入把负荷增量作为额外特征强制模型关注变化率第二调整滑窗让窗口末尾不要紧贴预测点比如留出几个点的间隔第三将单步预测的目标从“下一时刻值”改成“未来k步的值”扩展时间跨度。5.2 反归一化后预测值被压扁整段曲线都偏小现象预测结果画出来是一条水平直线数值大概在训练集的平均值附近。 原因最常见是归一化时用了整个数据集包括测试集的min和max导致测试集被压缩到很小的区间模型输出的方差被缩小。另一种是训练时数据里有极端尖峰MinMax被拉宽正常段数值都接近0。 解决严格只用训练集做fit测试集只做transform。对尖峰做clip设99.9%分位数为上限防止归一化区间被异常值挟持。5.3 验证集损失先降后涨早停参数设得太晚现象前20个epoch验证损失稳定下降之后突然掉头上升训练损失还在降。 原因模型开始过拟合训练噪声。如果patience设成10或更大早停会犹豫很久最终保留的是已经过拟合的权重。 解决把patience设为57并且每5个epoch打印一次训练/验证损失先手动观察趋势再定阈值。我一般还会限制LSTM层数不超过3层hidden_size不超过256从结构上降低过拟合空间。5.4 训练速度慢且不收敛学习率与LSTM层数互相牵制现象损失曲线长时间不下降或者一降就跳成NaN。 原因学习率偏大梯度在LSTM这种链式结构里容易爆炸。多层LSTM如果没有dropout梯度传播不稳定。 解决先用0.0001做测试如果损失能降再调到0.0005如果NaN就降低学习率到0.00001。同时检查输入数据是否包含NaN或无穷值归一化后如果还有清洗步骤没做干净。5.5 节假日与极端天气预测偏差大单变量模型的天花板现象平时误差5%春节假期误差飙到20%台风天更惨。 原因单变量LSTM只见过“历史负荷”没见过日历和天气。节假日负荷模式和普通工作日完全不同模型从历史序列里找不到相似样本。 解决加入外部变量比如星期几、是否节假日、温度、湿度。在输入端把input_size从1改成多维比如负荷、温度、节假日编码三路输入。这一招能从根上缓解但数据采集和特征对齐的活会多不少。这也是从“能跑通”走向“能用”的必经一步。6. 进阶让LSTM预测更实用的三个技巧最后聊几个不增加太多代码量却能明显提升效果的技巧。第一个是多变量输入把温度、湿度、星期几拼到特征里。做法是输入张量从 (batch, seq_len, 1) 变成 (batch, seq_len, 4)LSTM的input_size同步改成4。注意外部特征也要归一化且必须和负荷用同一个时间索引对齐否则模型学到的是错位因果。第二个是双向LSTM。虽然负荷预测是因果任务理论上不能用未来但如果你做的是事后分析或填补历史数据双向结构能利用前后文误差通常比单向低10%20%。如果是线上实时预测别用双向。第三个是模型融合。训练一个LSTM和一个LightGBMLSTM擅长捕捉连续趋势LightGBM擅长处理节假日、星期几这种离散特征两者加权平均通常比单个模型稳。我试过最简单的做法LSTM预测值乘0.7GBM预测值乘0.3RMSE比单模型降了8%左右。我的习惯是每次改完模型先跑测试集第1天到第7天画图看峰谷再看MAE和RMSE最后才盯着MAPE较真。做负荷预测久了就会明白指标只是参考真正考验你的是凌晨两点数据采集断了、第二天调度员拿着你的预测值去排机组——那种时候你就会感谢自己当初把预处理和避坑步骤做扎实了。希望这篇基于LSTM的电力负荷预测实践能帮到你哪怕只是少踩一个坑也值了。本文还有配套的精品资源点击获取