简介:这份PDF面向电力系统从业者、深度学习入门者与时间序列预测研究者,聚焦短期电力负荷预测这一典型场景,讲解如何借助TensorFlow搭建LSTM循环神经网络完成高精度预测。资源为单文件PDF,约2.27MB,内容源自正式期刊论文,结构完整、论述规范。文中系统梳理了LSTM的输入门、输出门与忘记门机制,深度学习自动提取高阶特征的优势,以及TensorFlow框架在建模训练中的工程实现;同时结合某地区发电厂实际负荷数据,说明时间、节假日、温度、降水量与极端天气等特征选取,以及异常值处理、归一化、数据迭代与参数更新、模型训练与预测的完整流程。实验对比表明,该算法预测效果明显优于传统机器学习方法,且随数据量增大展现出良好鲁棒性。已有793人学习,适合希望将深度学习落地于智能电网与电力负荷预测的读者参考。
1. 一份 2018 年的 LSTM 负荷预测论文,放到今天还能不能跑
手头这份《基于TensorFlow的LSTM循环神经网络短期电力负荷预测》,是 2018 年发在《上海节能》上的一篇工程实践文,作者李松岭,核心就一件事:拿某市某发电厂的真实负荷数据,用 TensorFlow 搭一个 LSTM 循环神经网络,做 15 分钟粒度的短期电力负荷预测,然后跟随机森林、SVM 做对比。结论很直接——LSTM 的 MAPE 做到 1.81%,随机森林 3.32%,SVM 4.24%,而且数据量从 500 万条涨到 4000 万条时,误差还在往下走,鲁棒性站得住。
这东西适合谁?做电力、能源、综合能源调度的算法工程师,尤其是被传统时序模型精度卡住、想上深度学习又不知道从哪下手的人。它不是什么新框架教程,而是一份带真实实验数据、带对比基线、带评价指标的完整落地记录。你照着复现,能拿到一条从数据清洗到模型评估的完整链路,而不是只跑通一个 demo。下面我按「这份资源讲什么 → 怎么把它跑起来 → 哪里容易翻车」的顺序拆一遍。
2. 特征工程与数据预处理:LSTM 吃进去的到底是什么
2.1 五类输入特征,缺一个精度就掉
这篇论文在特征选择上没玩虚的,直接列了五类:时间因素(粒度 15 分钟)、节假日因素(0 工作日 / 1 节假日)、温度因素(日最高温 + 日最低温)、降水量、是否极端天气。为什么是这五个?因为短期负荷的本质是「人什么时候用电」,而人的行为被作息、假期、气温、天气事件同时牵着走。
时间粒度定在 15 分钟,意味着一天 96 个点,这是电力负荷预测里最常见的分辨率,再细对预测精度边际收益不大,再粗就丢掉了峰谷切换的细节。节假日做成 0/1 离散变量,是因为它对负荷的影响是阶跃式的,不是线性的——工作日和春节当天的负荷曲线根本不是一个形状。温度取最高最低两个值而不是均值,是因为负荷对极端温度的响应是非线性的,夏天 38 度和 32 度,空调负荷差一大截,均值会把这种差异抹平。
提示:如果你手上的数据没有极端天气字段,别硬造。常见做法是用天气现象编码(晴/雨/雪/雷暴)替代,或者干脆先不加,跑一版基线看误差,再决定要不要补。
2.2 坏数据处理的四种手法,顺序不能乱
论文里把异常数据处理拆成四步:概率统计法识别置信区间外的点、曲线替换法删掉明显异于正常日负荷曲线的整天、经验修正法按正常曲线补某时段的值、平均值填补法处理缺失。这个顺序有讲究——先识别、再整日替换、再局部修正、最后填缺失,是从粗到细的漏斗。
概率统计法一般用 3σ 原则或者箱线图 IQR,把超出范围的负荷点标出来。曲线替换法针对的是「整天都不对」的情况,比如某天数据采集设备故障,96 个点全是平的或者全是尖刺,这种没法逐点修,直接拿前后相似日的曲线替换。经验修正法处理的是「某几个小时不对」,比如下午 2 点到 4 点数据跳变,就参照同类型日的对应时段修正。平均值填补法只用于零星缺失,取前后相邻点的均值。
import numpy as np import pandas as pd def clean_load_data(df, load_col='load', time_col='timestamp'): # 1. 概率统计法:3σ 识别异常点 mu, sigma = df[load_col].mean(), df[load_col].std() df['is_outlier'] = np.abs(df[load_col] - mu) > 3 * sigma # 2. 曲线替换法:按天统计异常点占比,超过 30% 整天标记 df['date'] = pd.to_datetime(df[time_col]).dt.date daily_outlier_ratio = df.groupby('date')['is_outlier'].mean() bad_days = daily_outlier_ratio[daily_outlier_ratio > 0.3].index df.loc[df['date'].isin(bad_days), load_col] = np.nan # 3. 经验修正法 + 4. 平均值填补法:线性插值兜底 df[load_col] = df[load_col].interpolate(method='linear', limit_direction='both') return df.drop(columns=['is_outlier', 'date'])这段代码里,3 * sigma是异常判定阈值,电力负荷数据一般用 3σ,太松会漏掉尖刺,太紧会误杀正常峰谷。0.3是整天替换的触发比例,超过三成点位异常就认为这天整体不可信。interpolate的limit_direction='both'保证首尾缺失也能补上。跑完这一步,数据里不该有 NaN,也不该有肉眼可见的离群尖峰。
2.3 归一化不是可选项,是必选项
论文里明确写了:不同量纲直接喂进模型,很容易不收敛。负荷是兆瓦级,温度是摄氏度,降水量是毫米,节假日是 0/1,量纲差好几个数量级。归一化公式就是最朴素的 min-max:
def min_max_normalize(series): return (series - series.min()) / (series.max() - series.min())逻辑很直白,把每个特征压到 [0,1]。参数就两个:series.min()和series.max()。注意,这两个值必须从训练集算出来,然后应用到验证集和测试集,不能各自归一化——否则测试集的信息就泄漏进训练过程了,这是时序预测里最隐蔽的坑之一。
3. 用 TensorFlow 搭 LSTM:从张量形状到门控参数
3.1 先把输入张量的形状想清楚
LSTM 的输入是三维张量:[样本数, 时间步长, 特征数]。这篇论文里,如果按 15 分钟粒度、用过去 96 个点(一天)预测下一个点,那时间步长就是 96,特征数是 5(时间、节假日、最高温、最低温、降水、极端天气,实际是 6 个,按论文列的是 5 类但温度拆两个)。样本数就是总数据量除以滑动窗口。
def create_sequences(data, seq_length=96, pred_length=1): X, y = [], [] for i in range(len(data) - seq_length - pred_length + 1): X.append(data[i:i + seq_length]) y.append(data[i + seq_length:i + seq_length + pred_length, 0]) # 第0列是负荷 return np.array(X), np.array(y)seq_length=96对应一天的历史窗口,pred_length=1是预测下一个 15 分钟点。data[:, 0]取负荷列作为预测目标。这个函数决定了你整个模型的输入维度,改seq_length就是改「用多长的历史预测未来」,电力负荷里 96 到 672(一周)都有人用,越长捕捉的周期信息越多,但训练也越慢。
3.2 LSTM 层的三个门,在代码里长什么样
论文用公式描述了输入门、遗忘门、输出门,但没贴代码。TensorFlow 里tf.keras.layers.LSTM把这些都封装好了,你只需要关心几个关键参数:
import tensorflow as tf model = tf.keras.Sequential([ tf.keras.layers.LSTM( units=64, # 隐藏层神经元数 input_shape=(96, 6), # 时间步长 96,特征数 6 return_sequences=False, # 只取最后一个时间步输出 dropout=0.2, # 输入 dropout recurrent_dropout=0.2 # 循环 dropout ), tf.keras.layers.Dense(1) # 输出单个负荷值 ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='mse', metrics=['mae'] )units=64是隐藏状态维度,论文没写具体值,但 2018 年那批电力负荷 LSTM 实验里 64 和 128 最常见,64 在 8GB 内存的机器上更稳。return_sequences=False表示只把最后一个时间步的隐藏状态传给 Dense 层,因为你要的是单点预测而不是序列到序列。dropout=0.2和recurrent_dropout=0.2是防过拟合的,电力负荷数据里周末和工作日模式差异大,不加 dropout 很容易在验证集上翻车。Adam(0.001)是默认学习率,论文里没提优化器细节,但这是当时 TensorFlow 1.3 时代最通用的选择。
注意:
recurrent_dropout在 TensorFlow 1.x 的某些版本里会拖慢训练速度,如果发现每个 epoch 时间异常长,先把它设成 0 跑通流程,再逐步加回来。
3.3 训练循环与早停:别让模型在训练集上自嗨
论文提到「多次数据迭代、参数更新」,落到代码就是epochs和batch_size。电力负荷数据量大,batch_size 一般设 128 或 256,epochs 设 100 以上,但必须配早停:
early_stop = tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=200, batch_size=128, callbacks=[early_stop], verbose=1 )patience=10意思是验证集损失连续 10 个 epoch 不下降就停,restore_best_weights=True保证回滚到最优权重而不是停在最后一步。这两个参数是防止过拟合的后悔药,没有它们,你很可能拿到一个训练集 MAPE 0.5%、测试集 MAPE 5% 的「假模型」。
4. 评价指标与对比实验:MAPE 1.81% 是怎么算出来的
4.1 MAPE 和 RMSE 的分工
论文用了两个指标:MAPE(平均绝对百分比误差)和 RMSE(均方根误差)。MAPE 看的是相对误差,适合跨数据量级比较,比如 500 万条和 4000 万条数据下的模型谁更准。RMSE 看的是绝对误差,单位跟负荷一样,适合判断「预测值偏离实际值多少兆瓦」。
def mape(y_true, y_pred): return np.mean(np.abs((y_true - y_pred) / y_true)) * 100 def rmse(y_true, y_pred): return np.sqrt(np.mean((y_true - y_pred) ** 2))MAPE 里除以y_true要求真实值不能为 0,电力负荷一般不会出现 0,但如果你做的是光伏出力预测,夜间出力为 0,MAPE 会炸掉,这时候要换 sMAPE 或者直接看 RMSE。这是论文没展开但实际落地必须知道的边界。
4.2 对比实验的公平性:同数据、同划分
论文表 1 给了随机森林 3.32%、SVM 4.24%、LSTM 1.81% 的对比。要复现这个结论,关键是三个「同」:同一份清洗后的数据、同一个训练/验证/测试划分、同一个预测目标。很多人对比时给 LSTM 喂了更多特征或者更长的历史窗口,然后说 LSTM 更好,这不叫对比,叫作弊。
from sklearn.ensemble import RandomForestRegressor from sklearn.svm import SVR # 把三维 LSTM 输入展平成二维给传统模型 X_train_flat = X_train.reshape(X_train.shape[0], -1) X_test_flat = X_test.reshape(X_test.shape[0], -1) rf = RandomForestRegressor(n_estimators=100, random_state=42) rf.fit(X_train_flat, y_train) rf_pred = rf.predict(X_test_flat) svm = SVR(kernel='rbf', C=1.0, epsilon=0.1) svm.fit(X_train_flat, y_train) svm_pred = svm.predict(X_test_flat)reshape把[样本, 96, 6]展成[样本, 576],传统模型才能吃。n_estimators=100是随机森林的树数量,C=1.0和epsilon=0.1是 SVM 的默认正则和容忍带。这样跑出来的对比才有说服力。
4.3 数据量增大时 LSTM 的鲁棒性从哪来
论文表 2 显示,数据从 500 万条涨到 4000 万条,MAPE 从 1.93% 降到 1.81%。这个趋势不是玄学,是深度学习的基本特性:参数量大,需要足够多的样本才能把权重训稳。传统机器学习模型在数据量超过某个点后,精度就平了,因为它们的假设空间有限。LSTM 的假设空间大得多,数据越多,越能逼近真实的负荷生成过程。
但这里有个前提:数据质量得跟得上。如果 4000 万条里有一半是坏数据,LSTM 会把这些噪声也学进去,精度反而可能掉。所以论文在预处理上花了那么大篇幅,不是凑字数,是给后面的鲁棒性结论打地基。
5. 避坑与排查:复现这篇论文时最容易翻车的五个地方
5.1 现象:模型 loss 一直不降,停在 0.5 附近
原因:最常见的是归一化没做,或者做了但训练集和测试集分别归一化,导致输入分布不一致。其次是学习率太大,Adam 默认 0.001 在电力负荷上一般没问题,但如果你手动改成 0.1,梯度会震荡。
解决:先检查X_train和X_test的数值范围,确认都在 [0,1]。然后打印前几个 batch 的 loss,如果第一轮就是 NaN,说明输入里有 NaN 或者 inf,回到预处理查缺失值填补。学习率先用 0.001 跑通,再考虑调。
5.2 现象:训练集 MAPE 很低,测试集 MAPE 高得离谱
原因:过拟合。电力负荷数据里,工作日和周末的模式差异大,如果训练集里周末样本少,模型会把工作日模式硬套到周末上。另外,dropout没加或者加得太小也会导致这个问题。
解决:先加dropout=0.2和recurrent_dropout=0.2,再检查训练集和测试集的时间划分是不是随机的——时序数据不能随机划分,必须按时间顺序切,否则未来信息会泄漏。正确做法是前 70% 训练,中间 15% 验证,后 15% 测试。
5.3 现象:预测曲线整体平移,峰谷对不上
原因:LSTM 对趋势敏感,如果训练数据的负荷均值在时间上有漂移(比如夏季整体高于春季),模型学到的可能是「平均负荷」而不是「相对变化」。另外,如果特征里没有时间戳的周期性编码(比如 sin/cos 变换),模型分不清早上 8 点和晚上 8 点。
解决:在特征里加小时和分钟的 sin/cos 编码,或者直接用pd.get_dummies把 96 个时间点做成 one-hot。论文里只说了「时间因素」,没展开编码方式,这是它没写透的地方,但实际做的时候必须补上。
5.4 现象:训练到一半 loss 突然变成 NaN
原因:梯度爆炸。LSTM 虽然比普通 RNN 稳,但在序列很长(比如 seq_length 超过 200)或者学习率偏大时,梯度还是会炸。论文里没提梯度裁剪,但这是 LSTM 训练的标配。
解决:在model.compile里加clipnorm=1.0或者clipvalue=0.5:
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001, clipnorm=1.0)clipnorm=1.0意思是把梯度向量的范数限制在 1.0 以内,超过就按比例缩放。这个参数不改变正常训练时的梯度方向,只在爆炸时兜底。
5.5 现象:换一台机器跑,结果对不上
原因:随机种子没固定。TensorFlow 的权重初始化、dropout、数据 shuffle 都带随机性,不固定种子,每次跑出来的 MAPE 可能差 0.1 到 0.3 个百分点。
解决:在代码开头加:
import random random.seed(42) np.random.seed(42) tf.random.set_seed(42)42是习惯用法,换成别的整数也行,关键是整个项目统一。注意,即使固定了种子,不同 TensorFlow 版本之间的数值精度差异还是可能导致微小偏差,这是框架层面的,不是你的问题。
6. 把这篇论文变成你自己的基线:三个进阶改法
第一个改法,把单步预测改成多步预测。论文做的是预测下一个 15 分钟点,但实际调度更关心未来 4 小时(16 个点)甚至 24 小时(96 个点)的负荷曲线。改法很简单,把Dense(1)换成Dense(16),y的pred_length改成 16,损失函数从 MSE 换成 MAE 或者 Huber,因为多步预测里个别点的偏差会被 MSE 放大。我一般会先用Dense(96)跑一版,看整体曲线形状对不对,再决定要不要做多模型滚动预测。
第二个改法,把 LSTM 换成 GRU 或者加 Attention。GRU 比 LSTM 少一个门,参数少三分之一,训练快,在数据量不够大的时候反而更稳。Attention 加在 LSTM 后面,让模型自己决定历史 96 个点里哪些对当前预测更重要——比如预测周一早上 8 点的负荷,上周一同一时刻的权重应该比上周六高。代码上就是在 LSTM 的return_sequences=True后面接一个tf.keras.layers.Attention(),然后接 GlobalAveragePooling1D。
第三个改法,把 MAPE 换成更鲁棒的指标。MAPE 在负荷接近 0 的时候会失真,电力系统里凌晨低谷负荷虽然不为 0,但可能只有峰值的 30%,这时候 MAPE 会偏大。我习惯同时看 MAPE、RMSE 和 R²,三个指标都达标才认为模型可用。R² 用sklearn.metrics.r2_score直接算,不用自己写。
from sklearn.metrics import r2_score def evaluate_all(y_true, y_pred): return { 'MAPE': mape(y_true, y_pred), 'RMSE': rmse(y_true, y_pred), 'R2': r2_score(y_true, y_pred) }这三个指标里,MAPE 看相对精度,RMSE 看绝对偏差,R² 看模型解释了多少方差。论文只给了前两个,但 R² 能告诉你模型是不是只学到了均值——如果 R² 低于 0.8,说明模型没捕捉到负荷的波动模式,得回去查特征或者加层。
从那以后我每次复现这类时序预测论文,都强制走一遍「先跑通单步、再扩多步、最后换指标」的流程,不跳过任何一步。希望帮到你。
本文还有配套的精品资源,点击获取