简介:短期电力负荷预测中,传统ARIMA与机器学习模型往往只能兼顾时序性或非线性中的一方面。这份PDF以LSTM长短期记忆网络为核心,系统阐述其输入门、遗忘门、输出门机制如何改善循环神经网络梯度消失问题,并给出数据预处理、训练集/验证集/测试集划分、多层LSTM构建及Adam优化等完整预测流程,适合电力调度、电气工程及深度学习初学者参考。资源包共1个PDF文件,大小423KB,为论文全文,可直接阅读或打印。目前已有140人学习下载。读者从中能了解到LSTM相对RNN在长期依赖建模方面的优势,并通过文中短期一个月负荷预测的LSTM与RNN对比曲线,直观看到LSTM在拟合真实数据和缩小预测误差上的效果。该资料可作为开展电力负荷预测实验、课程设计或算法对比研究时的方法论参考。
1. 从 ARIMA 到 LSTM:短期电力负荷预测为什么需要换一种建模思路
电力系统的调度运行、生产计划甚至现货交易报价,全都依赖负荷预测的结果。传统做法里,ARIMA 这类时间序列模型对时序关系考虑得很充分,但面对负荷曲线里节假日、气温和行业用电带来的突变,它的非线性拟合能力明显不够;机器学习模型虽然能硬学出非线性关系,却需要人工构造时间特征,时序相关性又打了折扣。所以这份《基于深度学习LSTM网络的短期电力负荷预测方法研究》把目光放在长短期记忆网络上——LSTM 同时吸收时效性和非线性,用门控结构记住一个月甚至更久的用电模式。文档从 RNN 原理讲到 LSTM 的三门机制,再做了一轮 LSTM 与 RNN 的短期预测对比,结论是 LSTM 误差最小、预测效果最稳。适合电力系统工程师、做时间序列预测的算法工程师,以及刚入门深度学习、想找一份带原理和实验的参考资料的人。
2. LSTM 的门控机制:RNN 梯度消失问题的解法与三个门的分工
2.1 RNN 的结构与它的短板
循环神经网络之所以叫"循环",关键是隐藏层状态 St 不只由当前输入 Xt 决定,还带着上一时刻的 St-1 一起参与计算。三组参数 W、U、V 分别负责输入到隐藏层、隐藏层状态到下一时刻、隐藏层到输出。这意味着每一个时间步的输出,都隐含着之前若干步的信息,看起来非常适合处理时序数据。
但问题也随之而来:当序列足够长,反向传播时梯度要顺着时间维度逐层回传,连乘的链式结构会让梯度指数级衰减。时间间隔越来越大,RNN 就记不住太久远的信息,体现为梯度消失。实际表现是:拿一个月的负荷数据训练 RNN,到后面几个星期的用电规律在预测里几乎不起作用,模型预测出来的曲线往往只反映最近几天的水平,整体偏差很大。这篇方法研究中提到 RNN 的"学习能力会呈梯度消失",本质上就是这个问题。负荷预测需要学习周期比较长的数据,还要在预测前找到输入与输出的映射关系,RNN 在这两点上都不够用。
2.2 三个门的记忆控制机制:用遗忘门与输入门决定记住多少
LSTM 在 RNN 基础上增加了一条贯穿时间步的细胞状态 Ct,它相当于一条"记忆主线"。三个门的职责可以拆开看。
遗忘门决定上一时刻的细胞状态 Ct-1 有多少要保留到当前。输出值被 σ 激活函数压到 [0,1] 之间,0 表示整体丢弃历史信息,1 表示整体保留。针对负荷预测来说,若前一天是普通工作日,后一天恰好是节假日,模型通过遗忘门把"工作日用电模式"大部分丢弃,只留住"节假日模式"相关的历史状态。
输入门决定当前输入 Xt 中有多少新信息写入细胞状态。它负责给记忆主线补充新的用电特征,比如刚出现的温度骤降、某个工业用户临时停产造成的负荷跳变。输出门决定当前细胞状态 Ct 有多少被释放到当前输出 ht,相当于筛选出"与本次预测最相关的记忆",再交给后续网络层或直接作为预测值。
门控的核心是用 σ 函数做开关,再配合 tanh 生成候选记忆。参数矩阵 W 和偏置 b 在训练中不断调整,目的就是让三个门的开关尺度尽量贴合电力负荷的周期性。LSTM 神经网络在进行学习与预测时,主要就是依靠这套三门结构,把神经元的输出、输入和历史依赖都控制住,然后共同作用在负荷数据上。
2.3 为什么 LSTM 适合短期负荷预测而不是其他网络
短期负荷预测的时间单位是小时、天、周、月。以小时级数据为例,一天 24 点构成一个周期,一周 168 点又构成第二个周期,工作日和周末的形态差异很大。LSTM 的长处在于能同时保住这两个周期:细胞状态上的长期记忆保留"连续几周同类型日"的整体趋势,门控机制又允许短期波动进入当前状态。RNN 很难做到这点,因为它没有独立的记忆槽位,记忆和输出混在一起。CNN 在特征提取上很强,但要处理任意长度的时序依赖,必须靠堆叠感受野,结构上反而不如 LSTM 直接。
文献里那组对比实验也证明了这一点:在短期(一个月)负荷预测中,LSTM 和 RNN 的训练数据都是前几周的负荷曲线,预测目标是一周真实值。从结果看,LSTM 的红色预测曲线与蓝色真实曲线贴合度明显更高,而 RNN 在几个负荷峰值处普遍偏低,这正是梯度消失导致远期记忆失效的典型表现。所以选 LSTM 不是因为它"新",而是因为负荷数据本身同时具备时效性和非线性,LSTM 的门控结构恰好能兼顾这两个特点。
3. 负荷数据准备:从原始序列到 LSTM 能吃的样本
3.1 预测时间的尺度划分
严格来说,负荷预测按时间范围分成四类:长期预测十年以上,中期预测五年上下,这两种主要用于电网规划、年度检修计划与运行方式的制定;短期预测按小时、天、周、月为单位,直接服务电力部门的日常调度;超短期预测是分钟级别,用于实时控制。LSTM 这套方法针对的是短期这一档,因为短期数据天生就同时具备时效性和非线性:一天内的负荷峰谷明显,一周内工作日与休息日差异显著,遇到天气突变还会出现非线性跳变。
ARIMA 对线性时序处理得不错,但对这种跳变无能为力;机器学习算法又依赖人工构造时间特征,特征造得不好预测就崩。LSTM 把特征学习交给网络本身去完成,这也是它在选型上最合理的点。如果你拿到的数据本身就是小时级或更细粒度,建议优先走 LSTM 路线;如果你只需要做年度或月度趋势判断,ARIMA 这类传统模型反而是性价比更高的选择。
3.2 数据格式与异常值清洗
做 LSTM 预测之前,第一步是把原始负荷数据整理成规整的时间序列。最理想的是小时级连续数据,一份 CSV 至少要有两列:采集时间 time 和负荷值 load。采集间隔不一致、缺失值太多、重复时间戳都会直接影响后续建模。常见做法是先按小时重采样,缺失值用前后 24 小时的均值插补,重复值直接保留最后一条。
异常值清洗是容易被忽略的一步。负荷表计偶发通信故障会产生零值或尖峰值,直接用这些点训练,LSTM 会把"突然掉零"也当成一种规律学进去。我不太建议用全局均值做 3σ 判断,因为负荷本身有明显的日周期和星期周期,全局均值的参考意义不大;更稳妥的是滑动窗口内的中位数滤波——对每个点取前后 12 小时的窗口,计算窗口中位数,偏离中位数超过 3 倍窗口标准差的点就替换成该中位数。
3.3 归一化与数据集划分
LSTM 内部用 tanh 和 σ 激活函数,输入尺度太大或太小都会让门控失效,所以负荷值在进网络之前必须归一化。一般做法是 Min-Max 缩放,把数值压到 [0,1] 区间。
| 处理项 | 具体做法 | 说明 |
|---|---|---|
| 缩放范围 | [0,1] 或 [-1,1] | 负荷预测常用 [0,1],输出层配合线性激活 |
| 拟合对象 | 只对训练段 fit | 测试段不能参与,否则造成数据泄漏 |
| 反变换 | 预测完成后用同一个 scaler 还原 | 评价指标也要在还原后的尺度上计算 |
训练集、验证集、测试集的切分不能随机打乱。时间序列必须严格按时间顺序切:比如前 70% 做训练,中间 15% 做验证,最后 15% 做测试。验证集用来早停和调超参数,测试集只跑一次,用来给出最终误差结论。这一点和普通分类任务完全不同,随机切分会把未来信息漏进训练集,得到虚高的准确率,后面第 5 章会专门展开。
3.4 构造输入输出序列
LSTM 的输入要求是三维张量 (样本数, 时间步长, 特征数)。短期负荷预测里最常用的做法是:用过去 168 小时(一周)预测未来 24 小时。这个 168 的窗口刚好覆盖整个星期周期,模型能同时看到工作日和周末的用电形态。特征数在纯负荷预测场景下设为 1 即可,如果还加入了气温、湿度、是否节假日等外部特征,特征数就相应扩展。
要特别注意输入窗口和预测长度之间不能重叠:输入区间的最后时间点和输出区间的第一个时间点必须错开至少一个时间步,否则模型学到的是"复制最近值"而不是真正的映射关系。窗口长度的选择不是越大越好——LSTM 虽然能记住长期信息,但过长的窗口会显著增加训练成本,168 是一个经过大量项目验证的折中值。
4. 用 Keras 复现 LSTM 短期预测:一个可直接修改的训练骨架
4.1 加载数据与预处理
如果你已经有一份小时级负荷 CSV,可以按下面这套代码跑通全流程。我的习惯是先把数据处理和模型训练拆成两个脚本,数据处理只负责把 CSV 变成可以直接喂给模型的 numpy 数组,模型脚本专注训练和评估,这样调参时不用每次都重跑数据清洗。
import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # ---------- 数据加载与清洗 ---------- df = pd.read_csv('load_hourly.csv', parse_dates=['time'], index_col='time') df = df.resample('h').mean() # 重采样到小时 df['load'] = df['load'].ffill() # 缺失值向后填充 arr = df['load'].values.reshape(-1, 1) # 滑动窗口中位数滤波 window = 12 clean = arr.copy() for i in range(window, len(arr) - window): seg = arr[i-window:i+window+1] med = np.median(seg) std = np.std(seg) if abs(arr[i][0] - med) > 3 * std: clean[i][0] = med这段代码先做小时重采样和缺失值填充,然后用两侧各 12 小时的窗口做中位数滤波。注意 ffill 只适合少量缺失,如果某一小时缺得太多,建议改成前后均值插补。中位数滤波的参数选了 12 小时窗口加 3 倍标准差,这两个值可以根据数据质量调整:窗口太小滤不掉尖峰,太大又会让真实用电峰谷被抹平。
4.2 切分区间并构造序列样本
# ---------- 数据集切分 ---------- n = len(clean) train_len = int(n * 0.7) val_len = int(n * 0.15) test_len = n - train_len - val_len train_data = clean[:train_len] val_data = clean[train_len:train_len + val_len] test_data = clean[train_len + val_len:] # 归一化只 fit 训练段 scaler = MinMaxScaler(feature_range=(0, 1)) scaled_train = scaler.fit_transform(train_data) scaled_val = scaler.transform(val_data) scaled_test = scaler.transform(test_data) # ---------- 构造序列 ---------- TIMESTEPS = 168 # 过去一周 HORIZON = 24 # 未来一天 def make_sequences(data, timesteps, horizon): X, y = [], [] for i in range(len(data) - timesteps - horizon + 1): X.append(data[i:i+timesteps, 0]) y.append(data[i+timesteps:i+timesteps+horizon, 0]) return np.array(X), np.array(y) X_train, y_train = make_sequences(scaled_train, TIMESTEPS, HORIZON) X_val, y_val = make_sequences(scaled_val, TIMESTEPS, HORIZON) X_test, y_test = make_sequences(scaled_test, TIMESTEPS, HORIZON) # LSTM 要求输入为三维 X_train = X_train.reshape((X_train.shape[0], TIMESTEPS, 1)) X_val = X_val.reshape((X_val.shape[0], TIMESTEPS, 1)) X_test = X_test.reshape((X_test.shape[0], TIMESTEPS, 1))这里的 70/15/15 切分比例是时间序列任务的常见默认值,数据量小可以放大训练占比到 80%。make_sequences 函数里,每第 i 个位置截取 168 个输入点、接着的 24 个点作为标签,两者首尾不相邻、没有任何重叠。最后 reshape 成三维张量 (样本数, 168, 1),其中 1 表示只有负荷一个特征。如果在实际项目中加入了气温、节假日标记,这个维度要对应扩展成多个特征,模型理解负荷变化的能力会明显增强。
4.3 构建、训练与预测
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model = Sequential([ LSTM(128, return_sequences=True, input_shape=(TIMESTEPS, 1)), Dropout(0.2), LSTM(64, return_sequences=False), Dropout(0.2), Dense(64, activation='relu'), Dense(HORIZON) ]) model.compile(optimizer='adam', loss='mse') model.summary() early_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=50, batch_size=64, callbacks=[early_stop], verbose=1 ) # 预测并反归一化 pred_scaled = model.predict(X_test) pred = scaler.inverse_transform(pred_scaled) y_true = scaler.inverse_transform(y_test)模型第一个 LSTM 层设 128 个单元并开启 return_sequences=True,是为了把完整的隐藏状态序列传给第二层;第二层 64 个单元不保留序列,只在最后一个时间步输出一维向量。Dropout 按 0.2 的比例随机丢弃部分神经元,防止负荷数据量不够导致过拟合。优化器用 adam,基本是时间序列预测的默认选择,学习率没显式设置时用默认值即可。loss 用 mse 而不是 mae,是因为 MSE 对大误差点更敏感,能逼着模型把负荷峰值的偏差压下去。
EarlyStopping 的 patience 设为 5,意思是验证集 loss 连续 5 轮不下降就提前结束训练,restore_best_weights=True 保证模型参数回滚到验证集最优的那一轮。批次大小 64 是折中选择:太大收敛慢,太小梯度震荡明显。运行环境上,Windows 或 Ubuntu 20.04 都可以,只要 tensorflow 2.x 装好、CUDA 与显卡驱动版本对应,CPU 也能跑,只是慢一些。如果跑完发现预测曲线整体滞后真实曲线,优先检查 TIMESTEPS 和 HORIZON 是否有重叠;如果预测曲线过于平坦,优先怀疑归一化时混入了未来信息。
5. 避坑与排查:短期负荷预测最容易翻车的五个细节
5.1 验证集 Loss 很低,测试集预测曲线却是一条直线
现象:训练曲线和验证曲线都降得挺漂亮,但把测试集预测结果画出来,预测曲线失去高频波动,只剩一条接近均值的平线。
原因:MinMaxScaler 在数据加载时就对全量数据做了 fit,归一化参数里带上了测试段的统计信息,模型实际上"偷看"了未来数据的均值与方差,学到的是"输出训练集平均负荷"这样最保守的解。
解决:严格按照 3.3 节的做法,只对训练段 fit 一次 scaler,验证集和测试集用同一个 scaler 直接 transform。这个坑在时间序列任务里出现频率极高,因为常规 sklearn 流程里 scaler 通常先于数据切分执行,顺手就 fit 了全量数据,一定要把切分放在缩放之前。
5.2 验证 Loss 连续震荡,模型迟迟不收敛
现象:val_loss 每几个 epoch 就冲高回落,整体下不去,训练时间拉得非常长。
原因:学习率偏大。LSTM 的梯度在时间维度上本来就比普通全连接网络更敏感,Adam 默认的 0.001 在数据量小、序列长的场景下依然可能震荡。另一种可能是两层 LSTM 叠加后的梯度累计量过大。
解决:把学习率显式调低,比如 0.0003 起步;或给优化器加梯度裁剪,比如 clipnorm=1.0,防止梯度模长超过阈值引发爆炸。还有一个容易被忽视的原因——输入特征量纲差异过大,比如把气温和负荷直接拼在一起喂进去,气温的绝对数值会主导梯度方向,这种情况优先对每个特征单独归一化。
5.3 多步预测远期结果漂移得很厉害
现象:按"单步预测然后滚动迭代"的方式做未来 24 小时预测,前几个小时的结果还挺准,到第 12 个小时以后开始明显偏离,越往后越离谱。
原因:每走一步都会把上一轮的预测误差当作下一步的输入,误差沿时间轴累积,最后阶段实际上是在"预测误差的误差"。
解决:最直接的方式是像 4.2 节那样改成多输出结构,一次性输出 24 个预测点,避免误差传递;如果必须做迭代预测,则要在每一轮滚动后用真实值校正输入窗口,而不是把预测值原样塞回去。公开代码里很多滚动预测效果图很漂亮,但实际生产环境里真实值不可能提前拿到,这一点要特别警惕。
5.4 预测曲线比真实曲线慢了一个周期
现象:预测的峰谷形状完全正确,但整体向右平移了一两个小时。
原因:输入窗口和标签之间没有错开,或时间戳对齐出了问题。有一种常见做法是直接把第 t 时刻的负荷值当作 t+1 时刻的输入特征,这相当于告诉模型"下个小时的负荷约等于这个小时",模型抓住这个捷径后就不再学习用电规律,只是做了一步复制。
解决:从训练集里随机抽一个样本,打印输入序列的最后三个点和标签的前三个点。正常情况两者应该是连续的但属于相邻时间步,而不是同一个时间点的重复。如果发现对齐混乱,重新检查 make_sequences 里的索引逻辑。
5.5 同一份代码换一台机器结果差异很大
现象:本地训练的结果和服务器上重新跑的结果在指标上差了一大截,甚至重现不出论文里的曲线。
原因:深度学习框架的随机性。LSTM 权重初始化、Dropout 和随机批次抽取都会引入随机波动;浮点运算环境差异也会影响结果,CPU 与 GPU 上聚合顺序不同,结果会有细微差别。
解决:固定随机种子,numpy 和 tensorflow 两侧都设一遍,例如 np.random.seed(42) 和 tf.random.set_seed(42)。同时不要拿单次结果下结论,同一参数跑 5 次取误差均值。论文里那种漂亮的对比曲线,通常也是挑了一个代表性随机种子画出来的,复现时出现一点上下浮动是正常现象。
6. 验证 LSTM 是否真的比 RNN 好:误差指标与画图技巧
6.1 用 MAE、RMSE、MAPE 三个指标交叉验证
预测做完,不能只用眼睛看曲线贴合度。我一般固定计算三个指标:MAE 反映平均绝对误差,单位是兆瓦,直观但容易被大误差掩盖;RMSE 对越大的偏离给越重的惩罚,专门用来暴露峰值预测不准的问题;MAPE 把误差除以真实值得到百分比,方便在不同量级的负荷区间之间横向对比。三个指标同时下降才算模型真正改善,只降其中一个往往说明模型在"讨好"某个指标的特定样本。
反归一化这一步经常被忽略。如果直接拿归一化空间里的预测值和真实值去算指标,数字会很小,但那是"缩放后的误差",不是真实的兆瓦误差。必须像 4.3 节那样先用 scaler.inverse_transform 还原,再计算三个指标。
6.2 把训练段、真实段、预测段画在同一张图上
判断 LSTM 预测质量,最直接的还是画图。取一份真实的近 30 天负荷数据,按时间顺序画三条曲线:绿色代表训练段前几周的负荷,蓝色代表留出未参与训练的测试段真实值,红色代表模型生成的预测值。如果红色曲线在蓝色曲线周围小幅波动,峰值位置和谷值位置对得上,说明模型学到了周期模式;如果红色明显"跟丢"峰谷,说明门控机制没有把周期记忆保留住。文献里采用的正是这种对比思路,这也是负荷预测实验里最标准、也最容易被接受的可视化方式。
画图时注意时间轴要对齐。最常见的问题是训练段和测试段之间缺了一段缓冲,导致图上蓝色曲线和绿色曲线之间有明显断层,视觉上会误以为模型预测偏差很大。我习惯在训练段末尾和测试段开头之间留出 24 小时的间隔,不画任何数据,让三条曲线在时间轴上自然衔接。
6.3 对多个随机种子取平均,再把边界看清楚
我后来复盘这个项目时养成了一个习惯:同一份数据和同一组超参数,固定 3 个随机种子分别训练,每个种子取测试集上三个指标的均值,再看这 3 组均值之间的极差。极差小说明模型稳定;极差大说明这个结构对初始化太敏感,调结构比调超参数更急迫。从那以后,我每次跑 LSTM 负荷预测都强制走一遍这四步——检查归一化是否只 fit 训练段、检查输入输出窗口有无重叠、固定随机种子、三个指标加画图一起看。哪怕再着急出结果,也不会跳过。希望帮到你。
本文还有配套的精品资源,点击获取