ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LSTM+Transformer电力负荷预测实战:从数据清洗到模型训练全流程

LSTM+Transformer电力负荷预测实战:从数据清洗到模型训练全流程 简介面向能源需求预测与电力负荷预测场景这是一份以PyTorch为框架讲解时间序列模型应用的实战PDF适合有一定深度学习基础、希望通过动手项目掌握LSTM与Transformer建模流程的学习者。文档共62页目录结构完整支持章节跳转和阅读器大纲快速定位。内容从传统时间序列模型AR、MA、ARIMA讲起逐步深入到LSTM的门控机制与Transformer的多头注意力原理并给出基于PyTorch从零实现、数据预处理、特征工程、模型训练与调优、评估可视化的完整路径。资源包为1个PDF文件压缩包大小2.34MB携带方便阅读体验良好。目前已有126人学习下载可作为电力负荷预测入门和进阶的实操参考。1. 先搞清楚这份资源解决什么问题做电力负荷预测的人经常卡在最尴尬的一步数据攒了一堆模型却不知道从哪搭起。ARIMA 这类传统模型处理不动多周期叠加和非线性波动而换个深度学习模型光是搞懂数据组织方式、门控结构和注意力机制就能耗掉一周。这份 PDF 把整条链路都摊开了——时间序列基础、LSTM 和 Transformer 原理、融合模型结构、训练调优、评估可视化一页页搭过来。它的定位很实在有 Python 基础、想快速落地时序预测的工程师可以直接照着代码逻辑复现做毕业设计或课程项目的学生也能从里面拿到一套完整的模型栈和调参思路。不是纯原理讲义是能跟着动手跑的实战文档。适合谁两类人一类是做电力系统调度、园区用能分析、售电公司负荷管理这类工作的技术人员需要从业务数据里做出可用预测另一类是刚接触深度学习不久、想知道 LSTM 和 Transformer 怎么组合而不是只在 MNIST 上调参的学习者。前者拿它当前置参考后者拿它当脚手架。2. 模型选型为什么是 LSTMTransformer纯 LSTM 差在哪2.1 电力负荷数据的结构先认清数据再选模型电力负荷序列跟普通销量序列最大的差别在于它叠着好几个周期的波动。日尺度上有早高峰晚高峰周尺度上有工作日和周末的差异年尺度上有夏季空调和冬季采暖带来的用电拉升。负荷还跟温度、湿度强相关再加上节假日、大型事件、极端天气这些突发扰动一个序列里既有明显的周期成分又有大量非线性毛刺。传统 ARIMA 和 SARIMA 能处理的基本是平稳或差分后平稳的序列。它们把负荷当作线性组合来理解遇到早晚高峰那种突变式的切换预测曲线会显得“钝”容易出现滞后和高峰低估。这份 PDF 里先把 ARIMA 为什么不够用的推导放前面再引出 LSTM 和 Transformer——这个编排方式是对的。做预测之前先搞清楚数据长什么样比急着调参重要得多。判断自己的场景是否需要深度学习模型我一般看两件事一是有没有至少两三年的历史数据能让模型见到足够的周季节模式二是负荷曲线是否有明显非线性特征比如峰谷差大、特殊日效应显著。如果数据只有一两周或者负荷规律非常平稳直接用 ARIMA 或简单的回归方法更省事。2.2 LSTM 的侧重点门控机制解决长期依赖但本质仍是顺序建模LSTM 在时间序列预测里之所以比普通 RNN 稳是因为三个门——遗忘门、输入门、输出门——给细胞状态提供了一条“传送带”路径。在每个时间步信息可以选择性通过或丢弃从而缓解传统 RNN 反向传播时的梯度消失问题。PDF 里对遗忘门和输入门的展开推导到工程落地时不用死磕公式知道门控作用是什么、重点改哪些参数就够了。但 LSTM 有个绕不开的限制它是按时间步逐个顺序计算的。序列越长训练速度越慢而且只要前面步骤有预测误差误差会沿着时间步一路传导影响后面步骤的特征表达。换句话说LSTM 擅长捕捉近距离的时序依赖但要捕捉“上个月同一天的用电模式”这种跨长距离关联效果会打折扣。2.3 Transformer 的侧重点自注意力适合长序列位置信息必须显式编码很多人把 Transformer 用到负荷预测时会直接套完整的 Encoder-Decoder 结构。但在电力负荷预测场景Decoder 那套自回归逐次输出没有明显优势——负荷一步预测并不需要像机器翻译那样先完整编码再逐个解码。这份 PDF 偏向使用 Encoder 部分我实际做也是这样用 Transformer Encoder 把序列整体编码再从最后一个时间步的输出接全连接层输出预测值。这样既利用注意力机制抓长程依赖又避开了 Decoder 昂贵的时间步开销。Transformer 能抓长程关系核心是多头注意力把序列中任意两个位置的依赖直接拉通不经过中间循环步。但它本身对位置不敏感输入顺序打乱后注意力计算结果完全一样所以位置编码必须跟输入特征一起喂进去。PDF 里给的正弦余弦位置编码是经典做法PyTorch 里可以直接用nn.TransformerEncoderLayer自带的参数但显式位置编码我仍会保留。2.4 融合的常见结构与选型理由LSTMTransformer 融合没有唯一的标准公式。就负荷预测这种单变量或多变量回归任务最常见的组合是底层 LSTM 先对输入序列做初步的时序特征提取把每个位置的表示转成高维向量然后接线性投影拉齐到 Transformer 需要的d_model维度再加位置编码送入 Transformer Encoder 做全局关系建模。最后取最后一个时间步的 Encoder 输出接全连接层出预测。为什么不让 Transformer 直接处理原始输入因为注意力机制对局部连续变化模式的归纳能力不如 LSTM 直接负荷数据里大部分是短时突变和缓慢趋势混合的状态先用 LSTM 把局部时序模式提取出来再交给 Transformer 做全局整合通常比单独用某一方更稳。实测下来这种组合在早晚高峰切换、气温突变后的负荷拐点处理上明显平滑。3. 数据准备从原始负荷数据到模型输入序列3.1 缺失值与异常值处理先清洗再建模电力负荷数据最常见的来源是 SCADA 系统和电表采集质量参差不齐。缺失值通常分成两种单个时间点缺失和连续一段时间缺失。单点缺失用线性插值基本能对付连续缺失超过一天插值出来的曲线会失真我一般直接剔除这一段或者用前后同类型日比如都是工作日的均值去补。异常值处理上我习惯先用可视化和简单统计两条腿走路。可视化看的是有没有突兀的尖峰或凹坑比如凌晨三点负荷突然翻倍多半是采集设备故障或通信丢包导致的数据错乱。统计方法用 3σ 准则检测离群点把超出均值加减三倍标准差的值标出来再逐个人工确认是真实用电还是脏数据import numpy as np import pandas as pd def detect_outliers_3sigma(series, k3): mean, std series.mean(), series.std() lower, upper mean - k * std, mean k * std return series[(series lower) | (series upper)].index.tolist() # 用法示例对2019-2023年的负荷序列检测离群点索引 outlier_idx detect_outliers_3sigma(df[load], k3) print(f检测到 {len(outlier_idx)} 个疑似离群点)k的取值决定检测灵敏度负荷数据里我一般取 3太小的k会把夏季正常高峰误杀。检测出来之后别急着删先看日期是不是落在节假日或极端天气日很多“异常值”其实是真实需求只是模型还没学会。3.2 归一化与数据划分先看顺序再谈比例归一化是 LSTM 和 Transformer 训练前的标准动作。Min-Max 把数据压到 [0,1]适合激活函数使用 sigmoid/tanh 的情况Z-Score 让数据变成零均值单位方差对离群值相对更宽容。两者在负荷预测里都有大量使用但有一个雷是共通的必须先把训练集划分出来再在训练集上计算归一化参数。from sklearn.preprocessing import MinMaxScaler # 先划分再fit scaler train_size int(len(df) * 0.7) val_size int(len(df) * 0.15) train_data df[load].iloc[:train_size] val_data df[load].iloc[train_size:train_sizeval_size] test_data df[load].iloc[train_sizeval_size:] scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train_data.values.reshape(-1, 1)) val_scaled scaler.transform(val_data.values.reshape(-1, 1)) test_scaled scaler.transform(test_data.values.reshape(-1, 1))fit_transform用在训练集验证集和测试集只用transform。这样做是为了防止测试集的信息在归一化阶段泄露到模型里这一点在后面的避坑章节会再展开一次。数据量够的话7:1.5:1.5 是常见划分比例数据量紧张时可以把验证集并入训练集改用 K 折交叉验证但时间序列的 K 折不能随机打乱得按时间顺序切块。3.3 滑动窗口构造输入输出序列模型要的不是一列负荷数值而是一段“窗口”和“窗口之后的目标值”。窗口长度设多少直接决定模型能看到多远的历史。日粒度数据我一般设 7 天或 30 天小时粒度数据常用 24 的整数倍比如 1687×24小时让模型能完整看到一个星期的周期。import numpy as np def create_sequences(data, seq_len168, pred_len1, step24): X, y [], [] for i in range(0, len(data) - seq_len - pred_len 1, step): X.append(data[i:i seq_len]) y.append(data[i seq_len:i seq_len pred_len]) return np.array(X), np.array(y) # 单变量负荷数据 → 3D张量(样本数, 序列长度, 特征数) X_train, y_train create_sequences(train_scaled.flatten(), seq_len168, pred_len1) X_train X_train.reshape(-1, 168, 1) print(f训练集形状: {X_train.shape}, 标签形状: {y_train.shape})step是窗口滑动的步长。设置成 1 会得到大量重叠窗口样本数量大但训练慢且容易过拟合设置成 24日粒度数据的 24 小时会让样本之间相关性降低训练更快。负荷预测里我会优先用step24如果数据量不够再调小。4. 核心代码LSTMTransformer 模型的 PyTorch 实现4.1 整体架构输入层、LSTM 层、Transformer 层、输出层模型把整条链路的各环节串起来。输入层接收形状为(batch, seq_len, input_size)的张量LSTM 层逐时间步提取局部时序模式输出形状(batch, seq_len, hidden_size)线性投影把特征维度从hidden_size对齐到d_model位置编码加在投影结果上Transformer Encoder 做全局注意力建模最后取最后一个时间步的输出接全连接层得到预测值。这个结构里有一个关键设计到底取 Transformer 最后一个时间步的输出还是把所有时间步的输出平均池化一步预测时取最后一个时间步更直接因为目标值紧跟在输入序列末尾多步预测时我会把所有时间步输出都接出来再过一个Linear层直接输出多步预测避免用逐时间步递归的方式放大多步误差。4.2 位置编码与模型类完整实现import math import torch import torch.nn as nn class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len5000): super().__init__() pe torch.zeros(max_len, d_model) position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1) div_term torch.exp( torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model) ) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) # shape: (max_len, d_model)直接注册为buffer不参与梯度更新 self.register_buffer(pe, pe) def forward(self, x): # x: (batch, seq_len, d_model) return x self.pe[:x.size(1)] class LSTMTransformer(nn.Module): def __init__(self, input_size1, hidden_size64, lstm_layers2, d_model64, nhead8, transformer_layers2, dropout0.1): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, lstm_layers, batch_firstTrue, dropoutdropout) self.proj nn.Linear(hidden_size, d_model) # 对齐特征维度 self.pos_enc PositionalEncoding(d_model) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dropoutdropout, batch_firstTrue ) self.transformer_encoder nn.TransformerEncoder( encoder_layer, num_layerstransformer_layers ) self.fc nn.Linear(d_model, 1) # 输出单步预测值 def forward(self, x): lstm_out, _ self.lstm(x) # (batch, seq_len, hidden_size) x self.proj(lstm_out) # (batch, seq_len, d_model) x self.pos_enc(x) # 加入位置编码 x self.transformer_encoder(x) # (batch, seq_len, d_model) out self.fc(x[:, -1, :]) # 取最后时间步形状 (batch, 1) return out.squeeze(-1)batch_firstTrue让所有张量按(batch, seq_len, features)排列省去维度转置的麻烦。lstm_layers和transformer_layers是两个独立的层数参数层数加深会提升模型容量但调过头就是过拟合。nhead是注意力头数必须能被d_model整除否则 PyTorch 会直接报错。注意nn.LSTM的dropout参数只在lstm_layers 1时生效单层 LSTM 传 dropout 会收到警告但不报错这点容易让人误以为已经加了正则化。4.3 模型初始化与关键参数设置模型实例化时参数没有一个放之四海而皆准的组合但可以从下面这样一组基线出发再按数据量调整参数基线取值说明input_size1只用负荷单变量加温度就改成2或3hidden_size64LSTM 隐藏维度数据量大可到128lstm_layers2一般不超过3多了容易过拟合d_model64Transformer 特征维度与 hidden_size 一致即可nhead8注意力头数8 头在64维下是常用配置transformer_layers2负荷预测2层足够4层以上边际收益很小dropout0.1数据量小可以降到0.05model LSTMTransformer( input_size1, hidden_size64, lstm_layers2, d_model64, nhead8, transformer_layers2, dropout0.1 ) total_params sum(p.numel() for p in model.parameters()) print(f模型参数量: {total_params:,})参数量直接关系到训练速度。这个基线配置参数量在 6 万左右对 CPU 训练都比较友好。如果数据量有几万条以上把 hidden_size 和 d_model 提到 128模型容量会更充裕。5. 训练与调参学习率调度、早停与三条翻车排查5.1 训练循环损失函数、优化器、早停与模型保存回归任务损失函数首选 MSE它对大误差的惩罚更狠会逼着模型把负荷峰值尽量拟合准。如果业务更关心整体偏差而不是个别尖峰MAE 也可以试但收敛曲线不如 MSE 平滑。优化器我基本固定用 Adam初始学习率设在 1e-3 上下配合学习率调度器做衰减。早停是防止过拟合最实用的手段。把训练集和验证集的损失曲线放在一起看验证损失连续多个 epoch 不降就停止训练并保存最优权重。下面这段是完整的训练循环包含早停和梯度裁剪import torch from torch.utils.data import TensorDataset, DataLoader optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, patience5, factor0.5 ) criterion nn.MSELoss() train_dataset TensorDataset( torch.FloatTensor(X_train), torch.FloatTensor(y_train) ) val_dataset TensorDataset( torch.FloatTensor(X_val), torch.FloatTensor(y_val) ) train_loader DataLoader(train_dataset, batch_size128, shuffleFalse) val_loader DataLoader(val_dataset, batch_size128, shuffleFalse) best_val_loss float(inf) patience_counter 0 max_epochs 200 for epoch in range(max_epochs): model.train() train_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() pred model(X_batch) loss criterion(pred, y_batch.squeeze(-1)) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step() train_loss loss.item() * X_batch.size(0) model.eval() val_loss 0.0 with torch.no_grad(): for X_batch, y_batch in val_loader: pred model(X_batch) loss criterion(pred, y_batch.squeeze(-1)) val_loss loss.item() * X_batch.size(0) scheduler.step(val_loss) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model.pt) patience_counter 0 else: patience_counter 1 if patience_counter 15: print(f早停于 epoch {epoch}) breakshuffleFalse是时间序列训练和普通图像训练的一个关键差别——打乱数据会破坏时序依赖关系即便 LSTM 内部有记忆机制样本之间的先后顺序本身就带着信息。梯度裁剪的max_norm5.0是把反向传播得到的梯度范数限制在 5 以内防止偶尔一个坏批次把参数更新顶飞。5.2 翻车一Min-Max 归一化直接在全量数据上 fit现象验证集和测试集效果都很好RMSE 数字漂亮但模型上线到新数据上预测就崩误差翻了几倍。原因归一化时用了全量数据的 min 和 max测试集的信息已经提前“暗示”给了模型。典型错误写法是拿到数据先scaler.fit_transform(df[load].values)再来划分数据集这样测试集的统计特征在训练时就已经暴露了。解决严格先划分训练集、验证集、测试集再在训练集上fit验证集和测试集只做transform。这个流程我每次都会写成固定模板不做任何例外。上线后如果来了新数据也是沿用旧的scaler参数做变换。5.3 翻车二序列窗口重叠过多导致过拟合现象训练损失一路降到很低验证损失却迟迟不下去两条曲线分开得越来越远。原因窗口构造时step1相邻两个样本之间只有 1 个时间点的差异数据高度冗余模型把训练样本的细微噪声都背了下来而 val 窗口和 train 窗口的时间段不同模式对不上。解决把step调到seq_len // 2或者pred_len的整数倍降低样本重叠度。另外可以检查训练集和验证集的时间范围是否有交叉时间序列划分严禁随机拆分必须按时间点切段。5.4 翻车三Transformer 在长序列输入时显存失控现象seq_len从 168 改成 336 后训练还没跑几步就 OOM或者训练速度陡然下降一个量级。原因Transformer 的注意力矩阵是(seq_len, seq_len)量级的序列长度翻倍显存占用就是平方级增长。LSTM 部分虽然对长序列友好但 Transformer 编码器会把这个平方开销吃掉。解决先明确业务需求不是所有任务都要用 336 小时的历史窗口很多场景 168 小时已经够用。如果确实需要长窗口可以把 Transformer 的nhead降低、减小d_model或者只在 Transformer 后面接一层而不是多层。更激进的做法是把输入序列先通过一个卷积层降采样再进 Transformer。6. 评估指标解读与预测效果自检技巧6.1 评估指标怎么配MSE、RMSE、MAE、MAPE 该看哪个训练结束之后评估指标才是业务方真正能理解的数字。MSE 在训练时好用因为平方项放大误差梯度但业务方听到“MSE 4900”通常没概念。RMSE 把单位拉了回来适合汇报“平均误差约 70 kW”。MAE 不放大离群点如果数据里偶尔有极端尖峰MAE 更能反映日常水平的误差。指标计算方式适用场景RMSE预测误差平方均值的开方最常用量纲一致MAE预测误差绝对值的平均关注典型偏差MAPE误差绝对值除以真实值再取平均看相对误差百分比P50/P90 误差误差分位数调度侧评估极端情况6.2 可视化验证别只看 RMSE 就收工评估指标数值之外我每次都会画两张图。第一张是真实值和预测值对比把预测时段放大到一周的粒度看第二张是误差分布直方图。指标好但误差直方图明显偏态说明模型在特定时段有系统性偏移。比如误差集中在下午两点到五点通常是温度变量没有进模型模型纯靠历史负荷“猜”高温天的峰值。import matplotlib.pyplot as plt plt.figure(figsize(12, 4)) plt.plot(test_index, test_real, label真实负荷) plt.plot(test_index, test_pred, label预测负荷) plt.xlabel(时间) plt.ylabel(负荷 (MW)) plt.legend() plt.show()局部放大图比整段大图更有用。只看整段曲线两条线几乎重合视觉上很容易高估模型质量放大到单日尺度才能看出峰谷时刻到底差了多少分钟、峰值低估了几个百分点。做过一类电力负荷项目后我养成了个习惯每次预测结果出来先做数据泄露自检再画误差直方图和局部对比图最后才看指标数字。有一回 RMSE 降了不少但直方图显示负误差集中在高温日后来把温度补进输入特征业务侧反馈才算真正可用。从那以后我每次做负荷预测都强制走一遍“先排查归一化和划分再看可视化最后管指标”的流程指标反映的是平均可视化暴露的是结构。这个顺序帮我在不少项目里少踩了看不见的坑希望帮到你。本文还有配套的精品资源点击获取
返回列表