
简介这份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_colload, time_coltimestamp): # 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(methodlinear, limit_directionboth) return df.drop(columns[is_outlier, date])这段代码里3 * sigma是异常判定阈值电力负荷数据一般用 3σ太松会漏掉尖刺太紧会误杀正常峰谷。0.3是整天替换的触发比例超过三成点位异常就认为这天整体不可信。interpolate的limit_directionboth保证首尾缺失也能补上。跑完这一步数据里不该有 NaN也不该有肉眼可见的离群尖峰。2.3 归一化不是可选项是必选项论文里明确写了不同量纲直接喂进模型很容易不收敛。负荷是兆瓦级温度是摄氏度降水量是毫米节假日是 0/1量纲差好几个数量级。归一化公式就是最朴素的 min-maxdef 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_length96, pred_length1): 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_length96对应一天的历史窗口pred_length1是预测下一个 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( units64, # 隐藏层神经元数 input_shape(96, 6), # 时间步长 96特征数 6 return_sequencesFalse, # 只取最后一个时间步输出 dropout0.2, # 输入 dropout recurrent_dropout0.2 # 循环 dropout ), tf.keras.layers.Dense(1) # 输出单个负荷值 ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] )units64是隐藏状态维度论文没写具体值但 2018 年那批电力负荷 LSTM 实验里 64 和 128 最常见64 在 8GB 内存的机器上更稳。return_sequencesFalse表示只把最后一个时间步的隐藏状态传给 Dense 层因为你要的是单点预测而不是序列到序列。dropout0.2和recurrent_dropout0.2是防过拟合的电力负荷数据里周末和工作日模式差异大不加 dropout 很容易在验证集上翻车。Adam(0.001)是默认学习率论文里没提优化器细节但这是当时 TensorFlow 1.3 时代最通用的选择。注意recurrent_dropout在 TensorFlow 1.x 的某些版本里会拖慢训练速度如果发现每个 epoch 时间异常长先把它设成 0 跑通流程再逐步加回来。3.3 训练循环与早停别让模型在训练集上自嗨论文提到「多次数据迭代、参数更新」落到代码就是epochs和batch_size。电力负荷数据量大batch_size 一般设 128 或 256epochs 设 100 以上但必须配早停early_stop tf.keras.callbacks.EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs200, batch_size128, callbacks[early_stop], verbose1 )patience10意思是验证集损失连续 10 个 epoch 不下降就停restore_best_weightsTrue保证回滚到最优权重而不是停在最后一步。这两个参数是防止过拟合的后悔药没有它们你很可能拿到一个训练集 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但如果你做的是光伏出力预测夜间出力为 0MAPE 会炸掉这时候要换 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_estimators100, random_state42) rf.fit(X_train_flat, y_train) rf_pred rf.predict(X_test_flat) svm SVR(kernelrbf, C1.0, epsilon0.1) svm.fit(X_train_flat, y_train) svm_pred svm.predict(X_test_flat)reshape把[样本, 96, 6]展成[样本, 576]传统模型才能吃。n_estimators100是随机森林的树数量C1.0和epsilon0.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没加或者加得太小也会导致这个问题。解决先加dropout0.2和recurrent_dropout0.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里加clipnorm1.0或者clipvalue0.5optimizertf.keras.optimizers.Adam(learning_rate0.001, clipnorm1.0)clipnorm1.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_sequencesTrue后面接一个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说明模型没捕捉到负荷的波动模式得回去查特征或者加层。从那以后我每次复现这类时序预测论文都强制走一遍「先跑通单步、再扩多步、最后换指标」的流程不跳过任何一步。希望帮到你。本文还有配套的精品资源点击获取