FEATURED · 精选文章

CNN-LSTM电网负荷预测:PyTorch工程化实战

发布时间 / 2026/9/19 10:53:20
来源 / 创域科博编辑部
栏目 / 资讯中心
CNN-LSTM电网负荷预测:PyTorch工程化实战 简介这份 46 页的 PDF 围绕电网大数据场景下的能源负荷预测给出 CNN-LSTM 混合模型的完整工程化实现路线面向具备一定深度学习基础、希望将 PyTorch 用于电力负荷预测的研究者与算法工程师。文档既梳理 CNN 卷积、池化与 LSTM 门控机制等理论基础也逐章展开混合模型搭建、智能电表与气象等多源数据清洗归一化、时间序列划分、训练优化及误差评估并附带从数据收集到系统部署的工程化案例。压缩包内仅 1 个 PDF 文件大小约 2.03MB支持目录章节跳转与阅读器左侧大纲快速定位已有 57 人学习下载。读者可据此掌握损失函数选择、学习率调整、正则化与早停策略理解 MSE、RMSE、MAE、MAPE 等指标的计算与结果可视化方法并借鉴项目部署与效果分析流程适合希望快速复现 CNN-LSTM 负荷预测方案的中高级读者。1. 电网负荷预测为什么单靠 LSTM 会翻车先看一个现场场景某地区负荷曲线在夏季午后会出现一个尖锐的爬坡从谷值到峰值只用了 40 分钟幅度接近全天峰谷差的 30%。这种曲线用纯 LSTM 去拟合往往在爬坡段的误差最大因为 LSTM 的门控结构擅长捕捉长程的缓慢趋势却对局部突变的时间点不够敏感。CNN-LSTM 混合模型出现的直接理由就在这里用卷积层先在时间维上做局部特征提取把爬坡、跌落这种短时形态先「提出来」再交给 LSTM 处理跨小时、跨天的依赖关系。能源负荷预测在电网大数据语境下有几个绕不开的特点采样间隔通常为 15 分钟或 1 小时单条序列一年就有 3.5 万到 8.7 万个点影响因素多且异质气温、湿度、日照、节假日类型、星期几、前一日的同期负荷都要作为外生变量进入不同台区、不同变电站的负荷形态差异极大模型必须能批量训练和复用。这套工程化实践要解决的问题就是把 CNN-LSTM 从一个能跑通的 demo变成能按台区批量训练、能固定随机种子复现、能在 PyTorch 环境下稳定加载数据的生产脚本。适合读这篇的人有三类刚学完 PyTorch 张量基础和nn.Module写法想拿一个真实场景练手的已经在用 LSTM 做负荷预测但发现峰值段误差压不下去的以及需要把模型塞进现有数据管道、关心显存占用和训练耗时的工程同学。2. CNN-LSTM 混合模型的结构选型与 PyTorch 建模2.1 为什么是「CNN 提特征 LSTM 建时序」而不是反过来把 CNN 放在前面的核心原因是感受野与时序建模的分工。一维卷积在时间轴上滑动每个输出点只看窗口内的若干个时间步这天然对应「局部形态」比如连续四个 15 分钟点的上升斜率。如果先做 LSTM 再卷积LSTM 输出的隐状态已经是被门控压缩过的全局表示局部形态信息在压缩中会被抹平后面的卷积就没有可提的东西了。从参数量角度看一个Conv1d(in1, out64, kernel3)只有 192 个权重却能把原始单变量负荷序列扩成 64 个特征通道后续 LSTM 的输入维度变成 64。这相当于用极低成本做了一次非线性特征增广。我在实际配置里通常把卷积核尺寸设成 3 和 5 两组并行对应 45 分钟和 75 分钟两个尺度的局部模式。需要提醒的是不要把卷积层堆太深。负荷序列不像图像有空间层级结构两层Conv1d加ReLU已经够用再深会带来过拟合且感受野重叠严重反而削弱 LSTM 的长程建模价值。2.2 用 PyTorch 定义 CNN-LSTM 模块的完整代码下面这段是可以直接抄的模型定义输入形状约定为(batch, seq_len, n_features)其中n_features包含负荷本身和外生变量。import torch import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, n_features8, cnn_channels64, kernel_sizes(3, 5), hidden_size128, num_layers2, dropout0.2, horizon1): super().__init__() # 多尺度一维卷积不同 kernel 捕捉不同时长的局部形态 self.convs nn.ModuleList([ nn.Conv1d(in_channelsn_features, out_channelscnn_channels, kernel_sizek, paddingk // 2) # same padding保证长度不变 for k in kernel_sizes ]) self.relu nn.ReLU() self.pool nn.MaxPool1d(kernel_size2) # 时间维降采样一半 cnn_out_dim cnn_channels * len(kernel_sizes) self.lstm nn.LSTM(input_sizecnn_out_dim, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout) self.head nn.Linear(hidden_size, horizon) def forward(self, x): # x: (B, T, F) - Conv1d 需要 (B, F, T) x x.transpose(1, 2) outs [self.relu(conv(x)) for conv in self.convs] x torch.cat(outs, dim1) # (B, C*k, T) x self.pool(x).transpose(1, 2) # (B, T/2, C*k) h, _ self.lstm(x) # h: (B, T/2, H) last h[:, -1, :] # 只取最后时间步 return self.head(last)逻辑说明与参数说明paddingk//2保证卷积后时间长度不变这样多尺度卷积的输出能直接按通道拼接不用做对齐。MaxPool1d(2)把时间维砍半是一次激进但有收益的降采样显存和时间都会明显下降代价是丢失相邻步的细节若发现峰值段误差反而变大可以把它去掉再跑一轮对比。batch_firstTrue让张量维度顺序是(B, T, F)和 pandas 切出来的数据一致不用额外 permute。horizon是预测步数预测未来 1 个点就设 1做多步就设成 4 或 96。num_layers2配dropout0.2是我在负荷数据上比较稳的组合层数再往上加验证集误差基本不降训练时间却线性增长。2.3 输入特征设计与滑动窗口构造模型再对特征没喂对也白搭。电网负荷预测的常用特征可以按这张表组织特征类别具体字段处理方式说明历史负荷当前点、前 1 点、前 1 日同期直接入窗滞后特征是主力时间编码小时、星期、月份sin/cos 周期编码避免 23 点与 0 点距离失真气象温度、湿度、体感温度标准化温度对负荷影响最强日历是否节假日、调休标记0/1 编码节假日形态差异大统计近 7 日同期均值、方差滚动计算提供基线参照周期编码的写法要注意直接填hour23和hour0在数值上相差 23但实际只差 1 小时所以用sin(2πh/24)和cos(2πh/24)两列代替。滑动窗口构造的逻辑是给定序列长度T9624 小时 × 15 分钟预测下一个点那么每次从序列里取连续 96 个点作为输入紧跟其后的点作为标签。这一步的常见做法是用 numpy 的sliding_window_view一次性切出所有样本比 Python 循环快一个数量级。import numpy as np def make_windows(features, target, seq_len96, horizon1): # features: (N, F), target: (N,) x np.lib.stride_tricks.sliding_window_view(features, seq_len, axis0) # x: (N-seq_len1, F, seq_len) - 调整成 (S, seq_len, F) x np.transpose(x, (0, 2, 1)) idx np.arange(seq_len, len(target) - horizon 1) y np.stack([target[i:i horizon] for i in idx], axis0) x x[:len(y)] return x.astype(np.float32), y.astype(np.float32)sliding_window_view返回的是视图不是拷贝内存占用低但要注意后续做标准化时别在原视图上原地修改否则会污染原始数据。astype(np.float32)是必须的PyTorch 默认float32如果喂float64会报 dtype 不匹配。3. 电网大数据下的 PyTorch 训练管道与批量工程化3.1 Dataset 与 DataLoader 的正确写法台区数量多、单条序列长一次性把全部数据读进内存不可行。合理的做法是把每个台区或每块时间片当作一个样本桩Dataset 里只存索引和归一化参数__getitem__时再切窗取数。from torch.utils.data import Dataset, DataLoader class LoadDataset(Dataset): def __init__(self, feats, targets, seq_len96, horizon1): self.feats feats # (N, F) float32 self.targets targets # (N,) float32 self.seq_len seq_len self.horizon horizon self.n len(targets) - seq_len - horizon 1 def __len__(self): return max(self.n, 0) def __getitem__(self, i): x self.feats[i:i self.seq_len] # (T, F) y self.targets[i self.seq_len: i self.seq_len self.horizon] return torch.from_numpy(x), torch.from_numpy(y) loader DataLoader(LoadDataset(X, y), batch_size256, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue)num_workers4在 Linux 下能明显提升吞吐Windows 上因为进程启动方式不同建议先设 0 试通再往上加。pin_memoryTrue只在用 GPU 时有意义它把数据锁在页锁定内存加速主机到显存的拷贝。drop_lastTrue是为了避免最后一个不满 batch 的样本在 BatchNorm 之类的层上产生统计偏差如果模型里没有 BatchNorm这个参数可以留默认。3.2 训练循环、早停与显存控制训练循环本身不复杂关键是早停和显存。负荷数据的验证集误差通常在前 20 个 epoch 内快速下降之后波动早停能省掉大量无效训练。import torch from torch.optim import AdamW from torch.optim.lr_scheduler import ReduceLROnPlateau device torch.device(cuda if torch.cuda.is_available() else cpu) model CNNLSTM(n_featuresX.shape[1]).to(device) opt AdamW(model.parameters(), lr1e-3, weight_decay1e-4) sched ReduceLROnPlateau(opt, modemin, factor0.5, patience3) loss_fn torch.nn.MSELoss() best, patience, wait float(inf), 8, 0 for epoch in range(100): model.train() for xb, yb in loader: xb, yb xb.to(device), yb.to(device) opt.zero_grad() loss loss_fn(model(xb), yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) opt.step() # 验证略此处只演示早停骨架 val_loss 0.0 sched.step(val_loss) if val_loss best: best, wait val_loss, 0 torch.save(model.state_dict(), best.pt) else: wait 1 if wait patience: breakclip_grad_norm_(..., 1.0)是 LSTM 类模型必备的梯度爆炸在序列较长时不罕见。AdamW的weight_decay1e-4做的是解耦权重衰减比在Adam上用 L2 更规范。ReduceLROnPlateau的patience3表示验证损失 3 个 epoch 不降就把学习率打五折配合上面的patience8早停一般 30 到 50 个 epoch 就收敛。显存不够时的处理顺序是先把batch_size减半、再关掉MaxPool之外的多尺度卷积只留一个 kernel、最后才考虑减hidden_size。调hidden_size对模型表达能力影响最大放最后动。3.3 标准化与数据泄漏的边界负荷预测里最常见的隐性 bug 是标准化用了全集统计量。正确做法是只用训练集算mean和std然后套用到验证和测试集。如果用全集统计量验证误差会虚低上线后直接崩。train_end int(len(feats) * 0.7) mu, sigma feats[:train_end].mean(0), feats[:train_end].std(0) sigma[sigma 1e-6] 1.0 # 防止常数列除零 feats_norm (feats - mu) / sigma np.save(norm_stats.npy, np.vstack([mu, sigma]))把mu和sigma存盘是关键工程细节推理服务的预处理必须复用同一组参数不能重新算。sigma里做除零保护是因为像「是否节假日」这种列在某些切片里可能全为 0。4. 负荷预测精度评估与典型误差排错4.1 用 MAPE、RMSE 和峰值段误差三个指标看模型单看 MSE 会掩盖问题因为负荷量级大MSE 主要被高峰时段主导。工程上一般同时报三个指标指标公式含义关注点参考阈值MAPE平均绝对百分比误差整体相对精度 3% 算好RMSE均方根误差大偏差惩罚与负荷均值比 5%Peak-MAPE只取前 10% 高峰点算 MAPE峰值段精度 5%纯 LSTM 的典型表现是 MAPE 能到 2.5%但 Peak-MAPE 可能到 8%CNN-LSTM 的价值就体现在把 Peak-MAPE 压到 4% 到 5%。如果 CNN-LSTM 的 Peak-MAPE 和纯 LSTM 差不多说明卷积没起作用先检查卷积输出有没有被pool削得太狠。4.2 训练不收敛、损失震荡的排查顺序遇到 loss 从第一个 epoch 就飙到nan按这个顺序查先看输入里有没有nan或inf用np.isnan(feats).any()一行就能确认再确认标准化是不是有问题sigma为 0 会把整列变成inf然后看学习率LSTM 上1e-2经常炸1e-3起步更稳最后才是梯度裁剪有没有开。如果是 loss 下降但震荡幅度大多半是batch_size太小或者shuffle让样本分布不均。负荷数据有明显日周期shuffleTrue是必要的但如果你按天顺序切分又打乱会让相邻样本跨越不同天反而增大方差。这种情况下可以把batch_size提到 512。还有一个高频坑验证集损失一直不降但训练集降得很快通常是dropout不够或者窗口重叠太多导致训练样本和验证样本高度相似。检查切分方式确保验证集在时间上是训练集之后的一段而不是随机抽。5. 多台区批量预测与推理加速的工程技巧5.1 按台区分组训练与共享模型的取舍台区数量从几十到上千时有两种做法。一种是每个台区单独训一个模型精度高但维护成本随台区数线性增长且小台区样本不足容易过拟合。另一种是训一个共享模型把所有台区的特征如台区容量、用户类型编码拼进输入让模型自己学差异。我一般先用共享模型打底看误差分布如果某些台区的 MAPE 明显高于群体中位数两倍以上再给这些台区单独微调。共享模型的输入里加一列台区容量和用户类型编码很关键否则模型面对形态差异大的台区会取一个折中解谁都拟合不好。5.2 冻结卷积层做小样本台区微调新接入的台区往往只有几周数据这时候全量重训不现实。做法是加载共享模型的权重冻结 CNN 部分只训 LSTM 和输出层。PyTorch 里冻结很简单model.load_state_dict(torch.load(shared.pt, map_locationdevice)) for p in model.convs.parameters(): p.requires_grad False # LSTM 和 head 保持可训练 opt AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr5e-4)filter(lambda p: p.requires_grad, ...)这一步不能省否则优化器里还带着被冻结的参数虽然梯度是 0但weight_decay仍会改它们的值等于冻结没生效。lr用共享模型的一半甚至更低微调阶段的目标是偏移而不是重建。5.3 推理阶段显存与延迟的两个具体手段推理时不需要梯度先包一层torch.no_grad()再把模型切到eval()让 dropout 和 BatchNorm 走推理行为。显存不够时用torch.inference_mode()代替no_grad()它省掉版本计数显存和速度都更好。model.eval() with torch.inference_mode(): preds model(xb.to(device)).cpu().numpy() # 反标准化必须用训练时保存的统计量 mu_t, sigma_t stats[-1], stats[-2] preds preds * sigma_t mu_t最后一步反标准化是负荷预测里最容易被漏掉的收尾。存盘时把mu和sigma一起序列化推理服务的预处理和后处理必须成对出现任何一边改了标准化方式结果都会整体偏移。验证方式是拿一个已知的历史片段跑推理看还原后的负荷量级是否落在合理区间比如居民台区单点不超过线路额定容量的 1.2 倍。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻