PyTorch实战:LSTM-GRU混合模型电力负荷预测从原理到部署 简介这份PDF文档面向电力负荷预测方向的研究者、算法工程师与深度学习学习者围绕LSTM-GRU混合模型的PyTorch实现展开帮助读者理解单一循环网络模型的局限并掌握混合架构在时序预测中的搭建与调优思路。文档共48页以1个PDF文件打包压缩包约2.22MB支持目录章节跳转、阅读器左侧大纲显示与章节快速定位查阅体验完整流畅。内容从电力负荷预测概述、LSTM与GRU模型基础讲起逐步深入到混合模型的结构设计、层间连接与工作流程并完整覆盖PyTorch环境搭建、数据清洗归一化、自定义数据集类、模型定义、训练循环、梯度裁剪、评估指标计算、结果可视化与模型部署等环节还配有实际应用案例与效益分析。目前已有64人学习适合希望系统掌握时序预测建模流程、对照代码实践并查漏补缺的读者参考。1. 为什么电力负荷预测开始流行 LSTM-GRU 混合模型电力负荷预测的难点不在于预测两个字而在于负荷曲线本身同时包含多种时间尺度的模式日周期、周周期、节假日突变、温度骤变带来的尖峰。单一 LSTM 能记住长程依赖但在短时突变上反应偏慢单一 GRU 参数少、收敛快对局部波动敏感却容易在长序列上丢掉早期信息。把两者并行堆叠让 LSTM 抓趋势、GRU 抓局部残差再拼接输出是近几年在省级电网和园区级负荷预测里比较常见的一种折中方案。这篇文章面向已经会写 PyTorch 训练循环、但没系统做过负荷预测的工程师。我会从张量形状、模型结构、损失函数、滑窗构造一直讲到训练排错和推理部署代码全部用 PyTorch 原生 API不依赖第三方时序库。读完你应该能自己搭出一套可复现的 LSTM-GRU 混合预测流程并知道每个参数改动会带来什么后果。2. LSTM-GRU 混合模型的原理与 PyTorch 张量约定2.1 LSTM 与 GRU 在负荷序列上的分工LSTM 靠输入门、遗忘门、输出门三个门控和一条细胞状态通道把长距离信息以近似恒等映射的方式传下去。负荷序列里上周同一时刻的负荷这种跨 168 步的依赖正是它擅长的。GRU 把遗忘门和输入门合并成更新门只保留重置门和更新门参数量约为 LSTM 的 3/4在几百步以内的局部模式上收敛更快。混合的常见做法不是串行堆叠而是并行双分支同一段输入窗口同时喂给 LSTM 和 GRU各自输出最后一层隐状态拼接后接全连接层回归。这样两条分支的梯度互不干扰LSTM 分支负责慢变量GRU 分支负责快变量。也有串行做法LSTM 输出再进 GRU但串行会让梯度路径变长在负荷这种噪声偏大的数据上更容易过拟合我一般优先并行。2.2 输入张量的形状约定与滑窗构造PyTorch 的nn.LSTM和nn.GRU默认接受(seq_len, batch, input_size)如果设了batch_firstTrue则变成(batch, seq_len, input_size)。负荷预测里我强烈建议开batch_firstTrue因为构造数据集时按 batch 组织更直观调试时打印形状也不容易搞混。滑窗构造的核心是把一维负荷序列切成(样本数, 窗口长度, 特征数)。单变量负荷时特征数为 1加入温度、星期几、是否节假日后就变成多变量。import torch from torch.utils.data import Dataset class LoadWindowDataset(Dataset): def __init__(self, series, window168, horizon24): # series: (T, F) 的 float32 张量F 为特征数 self.series torch.as_tensor(series, dtypetorch.float32) self.window window # 回看窗口168 步约等于一周小时级数据 self.horizon horizon # 预测未来多少步 def __len__(self): return len(self.series) - self.window - self.horizon 1 def __getitem__(self, idx): x self.series[idx: idx self.window] # (window, F) y self.series[idx self.window: idx self.window self.horizon, 0] # 只预测负荷列 return x, y逻辑说明__len__保证最后一个样本的标签不越界horizon决定多步预测的长度。参数说明window取 168 对应小时级数据的一周若数据是 15 分钟粒度则应取 672horizon取 24 是日前预测的典型值做日内滚动预测可改成 4 或 1。注意标签只取第 0 列意味着温度等外生变量只作为输入特征不参与预测目标。2.3 归一化与数据集划分的坑负荷数据必须做归一化否则 LSTM 的 tanh 饱和会非常严重。常见做法是按训练集统计的均值和标准差做 z-score验证集和测试集复用训练集的统计量绝不能各自归一化否则会造成信息泄漏。处理项推荐做法常见错误归一化方式训练集 mean/std 的 z-score全局 min-max测试集参与统计划分比例7:1:2 按时间顺序切随机打乱切分缺失值线性插值后标记掩码直接填 0异常值3σ 截断或分位数缩尾直接删除导致序列断裂提示时间序列绝不能随机划分。负荷序列有强自相关随机切分会让相邻样本同时出现在训练和测试集指标虚高得离谱。3. 用 PyTorch 搭出 LSTM-GRU 并行混合网络3.1 双分支模块的定义与 forward 流程下面是一个可直接用的并行混合模型。LSTM 和 GRU 各自独立编码取最后时间步的隐状态拼接再经过两层全连接输出多步预测。import torch.nn as nn class LSTMGRUHybrid(nn.Module): def __init__(self, input_size, hidden64, horizon24, dropout0.2): super().__init__() self.lstm nn.LSTM(input_size, hidden, num_layers2, batch_firstTrue, dropoutdropout) self.gru nn.GRU(input_size, hidden, num_layers2, batch_firstTrue, dropoutdropout) self.head nn.Sequential( nn.Linear(hidden * 2, hidden), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden, horizon), ) def forward(self, x): # x: (batch, window, input_size) lstm_out, _ self.lstm(x) # (batch, window, hidden) gru_out, _ self.gru(x) # (batch, window, hidden) h torch.cat([lstm_out[:, -1], gru_out[:, -1]], dim-1) # (batch, 2*hidden) return self.head(h) # (batch, horizon)逻辑说明两条分支共享同一输入但参数完全独立lstm_out[:, -1]取最后一个时间步的隐状态作为整段窗口的摘要。参数说明hidden取 64 是负荷预测的常用起点数据量大可加到 128num_layers2兼顾表达力和训练速度超过 3 层在几千条样本上几乎必然过拟合dropout只在多层 RNN 的层间生效单层时该参数无效。注意torch.cat的维度是-1拼的是特征维而不是时间维。3.2 损失函数选择MSE、MAE 还是分位数损失负荷预测的评估指标通常是 MAPE 或 RMSE但训练损失不一定直接用它们。MSE 对大误差惩罚重适合关注尖峰MAE 对异常值稳健适合整体曲线平滑如果业务要给出预测区间就得分位数损失。def quantile_loss(pred, target, q0.5): # pred, target: (batch, horizon) e target - pred return torch.maximum(q * e, (q - 1) * e).mean() # 训练时组合使用兼顾点预测和区间 loss nn.functional.mse_loss(pred, y) 0.3 * quantile_loss(pred, y, 0.9)逻辑说明分位数损失在q0.9时对低估的惩罚更重能推动模型输出偏保守的上界。参数说明0.3是权重系数需要根据验证集上的区间覆盖率调整覆盖率低于 90% 就调大。注意分位数损失不能单独用于点预测必须和 MSE 或 MAE 组合。3.3 训练循环与学习率调度训练循环本身不复杂关键是加梯度裁剪和学习率衰减。RNN 类模型在负荷数据上很容易出现梯度爆炸clip_grad_norm_基本是必选项。from torch.optim import Adam from torch.optim.lr_scheduler import ReduceLROnPlateau model LSTMGRUHybrid(input_size4, hidden64, horizon24) opt Adam(model.parameters(), lr1e-3, weight_decay1e-5) sched ReduceLROnPlateau(opt, modemin, factor0.5, patience5) for epoch in range(80): model.train() for x, y in train_loader: opt.zero_grad() pred model(x) loss nn.functional.mse_loss(pred, y) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) # 梯度裁剪 opt.step() # 验证 model.eval() with torch.no_grad(): val_loss sum(nn.functional.mse_loss(model(x), y).item() for x, y in val_loader) / len(val_loader) sched.step(val_loss)逻辑说明clip_grad_norm_把所有参数的梯度范数限制在 1.0 以内防止单步更新过大。参数说明weight_decay1e-5是轻量 L2 正则负荷数据噪声大时可以加到 1e-4patience5表示验证损失连续 5 轮不降就砍半学习率。注意sched.step必须传验证损失用训练损失会导致调度滞后。4. 真实负荷数据上的训练排错与调参4.1 数据加载与特征工程实操真实数据通常来自 SCADA 或电表采样间隔不固定先重采样到整点。外生特征里温度用滑动平均比瞬时值更有效星期几和小时用 sin/cos 编码避免周期性断裂。import pandas as pd import numpy as np df pd.read_csv(load.csv, parse_dates[ts]).set_index(ts) df df.resample(1h).mean().interpolate() # 重采样到小时并插值 df[hour_sin] np.sin(2 * np.pi * df.index.hour / 24) df[hour_cos] np.cos(2 * np.pi * df.index.hour / 24) df[dow_sin] np.sin(2 * np.pi * df.index.dayofweek / 7) df[dow_cos] np.cos(2 * np.pi * df.index.dayofweek / 7) df[temp_ma] df[temp].rolling(6, min_periods1).mean() # 6 小时滑动平均 feat df[[load, temp_ma, hour_sin, hour_cos, dow_sin, dow_cos]].values逻辑说明sin/cos 编码让 23 点和 0 点在特征空间里相邻避免树模型和神经网络把周期性当成线性跳变。参数说明rolling(6)的窗口按数据粒度调整小时级取 6 表示 6 小时平均。注意interpolate只能处理短缺口连续缺失超过 3 小时应标记为异常段并剔除。4.2 过拟合与欠拟合的判别信号训练集损失降、验证集损失升是过拟合两者都高且下降缓慢是欠拟合。负荷预测里过拟合更常见因为样本量通常只有几千条。现象可能原因处理训练 loss 0.01验证 loss 0.2过拟合加 dropout、减 hidden、加 weight_decay两者都在 0.15 附近不降欠拟合或归一化错误检查归一化、加层、调大 lr验证 loss 震荡剧烈batch 太小或 lr 太大batch 加到 64、lr 降到 3e-4预测曲线整体平移标签泄漏或统计量错检查是否用了未来信息注意如果验证损失比训练损失低很多先怀疑数据泄漏而不是模型泛化好。时间序列里这几乎总是划分或归一化出了问题。4.3 多步预测的误差累积与 teacher forcing直接输出 24 步直接多步和逐步递归预测是两条路线。直接多步一次输出全部 horizon误差不累积但模型要学整段映射递归预测每步用上一步输出当输入短期准但长程漂移严重。负荷预测里我一般用直接多步配合 horizon 上的加权损失让近端权重更高。# 近端加权前 6 步权重 1.0后面线性衰减到 0.5 w torch.linspace(1.0, 0.5, 24) loss (w * (pred - y) ** 2).mean()逻辑说明加权让模型优先保证近端精度符合调度业务对最近几小时更敏感的需求。参数说明linspace的起止值按业务调整做日前计划可以设成均匀权重。注意权重张量要放在和 pred 相同的设备上否则会报设备不匹配。5. 混合模型的推理、验证与一个提精度的技巧训练完之后推理阶段最容易踩的坑是归一化统计量没保存。把训练集的 mean/std 一起存进 checkpoint推理时复用否则线上预测会整体偏移。ckpt { state_dict: model.state_dict(), mean: train_mean, # 训练集负荷均值 std: train_std, # 训练集负荷标准差 window: 168, horizon: 24, } torch.save(ckpt, hybrid.pt) # 推理 model.load_state_dict(ckpt[state_dict]) model.eval() with torch.no_grad(): x_norm (x_raw - ckpt[mean]) / ckpt[std] pred_norm model(x_norm) pred pred_norm * ckpt[std] ckpt[mean] # 反归一化验证时不要只看 MAPE还要看峰谷时段的误差分布。负荷预测的业务价值集中在尖峰整体 MAPE 低但尖峰误差大实际调度根本不敢用。建议按负荷分位数分段统计误差前 10% 高负荷段的 MAPE 单独看。一个提精度的实用技巧是残差修正先用混合模型预测再用一个轻量 GRU 对残差序列建模把残差预测加回主预测。残差序列通常更平稳小模型就能拟合整体 MAPE 往往能再降 0.5 到 1 个百分点。实现上就是把pred - y当作新标签用同样的滑窗构造再训一个小 GRU推理时两级串联。注意残差模型要和主模型分开训练联合训练容易让主模型偷懒。本文还有配套的精品资源点击获取