ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

PyTorch实现时空注意力机制:电力负荷预测实战

2026/9/19 21:38:42 拓冰建站 浏览量
PyTorch实现时空注意力机制:电力负荷预测实战 简介这是一份聚焦能源消耗预测的 PDF 技术文档面向深度学习入门者、电力系统研究人员及相关专业学生以电力负荷预测为主线讲解使用 PyTorch 构建时空注意力机制模型的完整流程。文档共计 46 页支持目录章节跳转和阅读器大纲定位内容涵盖电力负荷预测概述、PyTorch 基础与优势、时空注意力机制原理、基于 PyTorch 的数据处理与特征工程、模型训练与评估、实验结果对比及实际应用案例从理论到实战层层递进。资源包为单个 PDF 文件压缩包大小约 2.27MB目前已有 78 人学习。除框架核心特性外文档还提供了数据清洗、特征构造、模型调优、部署应用等环节的实现思路适合用于课程设计、课题研究或工程预研帮助读者快速搭建和验证时空注意力负荷预测方案。1. 为什么电力负荷预测需要时空注意力机制只把电力负荷当成一根“时间线”来预测是很多能源消耗预测模型跑分不错、上线就跑偏的根源。城市电网的负荷是多个站点共同作用的结果中心城区进入晚高峰时周边产业园区可能刚结束生产相邻区域的负荷曲线存在明显的错峰与传导关系单站点自己的历史序列根本解释不了这种联动。时空注意力机制就是冲着这个问题来的它在计算预测时沿时间轴回顾相似历史窗口沿空间轴挑选关联区域再结合气温、湿度、工作日等外部特征把这些信息同时压进同一套注意力映射。下文用 PyTorch 从头搭一个可运行的时空注意力模型在电力负荷预测数据上走通数据组织、网络构建、训练评估与参数调优的完整闭环。2. 时空注意力机制如何同时吃进时间与空间两个维度2.1 把负荷数据组织成时空张量站点 × 历史步数 × 特征先做一件与普通时间序列项目不同的事把每个时间切片上的单值展开成一张二维表。电力负荷预测里一个常见设定是同时预测 25 个区域站点未来 24 小时的负荷历史窗口取 168 小时刚好覆盖一周。来源数据通常是一个按时间排序的宽表每行一个时点列是各站点负荷、气温、湿度等外生变量。进入模型之前要重排成四维张量批量大小、站点数、历史步数、特征数。seq_len, pred_len, n_sites 168, 24, 25 # 取第 i 个样本原始矩阵形状为 (总时刻, 总列数) x_sample data[i:iseq_len].reshape(seq_len, n_sites, -1) x_sample x_sample.transpose(1, 0, 2) # (n_sites, seq_len, features) y_sample data[iseq_len:iseq_lenpred_len, :n_sites] # (pred_len, n_sites)reshape 时把最后剩余的列按站点平分默认就是“每个站点一组特征”的顺序。transpose(1, 0, 2) 把站点维度从第 1 维挪到第 0 维这样时间注意力可以按站点逐个批量计算空间注意力则把站点轴当作序列轴。后面所有张量形状都以这条对齐为准建议写成独立的数据预处理函数避免每个实验脚本各写一份导致形状对不上。2.2 Q、K、V 在时间轴与空间轴上的计算差异标准注意力计算式写作 A softmax(QKᵀ/√dₖ)V区别只在于 Q、K、V 的来源。时间注意力中Q 和 K 来自同一个站点不同时刻的向量矩阵里每个元素回答“当前时刻应该参考哪个历史时刻”空间注意力中Q 和 K 来自同一时刻不同站点的向量矩阵里每个元素回答“当前站点应该参考哪些关联区域”。两者共用缩放系数 √dₖ都是为了防止点积数值过大使 softmax 输出落入饱和区反向传播时梯度被压到几乎没有。实际建模时时间注意力的输入形状是 (batch×sites, seq_len, d_model)空间注意力的输入形状是 (batch×time, sites, d_model)。这种把 batch 与其中一个维度合并的做法是为了让同一个多头注意力模块能够复用到两条轴上不需要写两套实现。理解到这一层剩下的工作就是在 PyTorch 里做张量 reshape 和维度交换。2.3 时间→空间、空间→时间与并行双分支的结构选型组合顺序决定了信息传导路径不是随便选的。先时间后空间意味着先清洗单站点自身的时序信号再引入跨站点关联适合站点自身随机波动大、空间耦合较弱的场景先空间后时间先用相邻站点的同刻信息修正当前值再做时序建模适合区域性传导强的场景并行双分支把两条路径分开计算最后拼接或加权求和参数最多适合数据量大且对精度要求高的生产环境。结构计算顺序参数规模适用场景时间先空间后时间注意力 → 空间注意力中等单站点噪声大、空间耦合偏弱空间先时间后空间注意力 → 时间注意力中等区域强耦合、错峰传导明显并行双分支两个注意力并行 → 拼接大数据量大、追求最优精度下面这段代码展示先时间后空间的骨架逻辑真正的多头注意力实现放在下一章class SequentialST(nn.Module): def __init__(self, time_firstTrue): super().__init__() self.time_first time_first self.time_attn MultiHeadAttention(64, 4) self.spatial_attn MultiHeadAttention(64, 4) def forward(self, x): # x: (batch, n_sites, seq_len, d_model) b, n, t, d x.shape if self.time_first: xt self.time_attn(x.reshape(b * n, t, d))[0].reshape(b, n, t, d) x x xt xs self.spatial_attn( x.permute(0, 2, 1, 3).reshape(b * t, n, d))[0] x x xs.reshape(b, t, n, d).permute(0, 2, 1, 3) else: # 先空间后时间把两段注意力调换顺序代码结构不变 xs self.spatial_attn( x.permute(0, 2, 1, 3).reshape(b * t, n, d))[0] x x xs.reshape(b, t, n, d).permute(0, 2, 1, 3) xt self.time_attn(x.reshape(b * n, t, d))[0].reshape(b, n, t, d) x x xt return x这里先默认 MultiHeadAttention 已经定义传参是 d_model 和 n_heads。区分的意义在于后续做消融实验时可以一行调用 SequentialST(time_firstTrue/False)不用改前面的数据管线。3. 用 PyTorch 实现可运行的时空注意力负荷预测模型3.1 PyTorch环境搭建与最小依赖手写注意力模型的依赖很少torch、numpy、pandas再加一个 sklearn 做数据标准化。用 conda 单独建环境可以避免污染系统 Pythonconda create -n loadforecast python3.10 -y conda activate loadforecast pip install torch numpy pandas scikit-learn装完跑一下确认环境可用python -c import torch; print(torch.__version__, torch.cuda.is_available())没有 GPU 的机器直接用 CPU 版也没问题本文这套模型在 25 站点、168 步历史窗口、64 维嵌入的条件下单轮训练在 CPU 上也能跑完。有 N 卡时注意 PyTorch 版本与 CUDA 版本对齐装错版本最常见的症状是 import torch 正常但 torch.cuda.is_available() 返回 False。3.2 多头注意力层形状变换是核心多头注意力的实现关键是“先拆后合”把 d_model 拆成 n_heads 份每个头在低维子空间上独立计算注意力最后拼回原维度。多个头天然关注不同的相关性模式比如一个头盯日周期另一个头盯温度突变。import torch import torch.nn as nn import math class MultiHeadAttention(nn.Module): def __init__(self, d_model, n_heads, dropout0.1): super().__init__() assert d_model % n_heads 0, d_model 必须能被 n_heads 整除 self.d_k d_model // n_heads self.n_heads n_heads self.w_q nn.Linear(d_model, d_model) self.w_k nn.Linear(d_model, d_model) self.w_v nn.Linear(d_model, d_model) self.out_proj nn.Linear(d_model, d_model) self.dropout nn.Dropout(dropout) def forward(self, x): batch, seq_len, _ x.shape q self.w_q(x).view(batch, seq_len, self.n_heads, self.d_k) k self.w_k(x).view(batch, seq_len, self.n_heads, self.d_k) v self.w_v(x).view(batch, seq_len, self.n_heads, self.d_k) q, k, v q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2) scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k) attn torch.softmax(scores, dim-1) attn self.dropout(attn) out torch.matmul(attn, v) out out.transpose(1, 2).contiguous().view(batch, seq_len, -1) return self.out_proj(out), attnview 把最后一个维度同时切成头数和每个头的维度transpose 把头的维度换到第 2 维注意力矩阵的形状是 (batch, heads, seq_len, seq_len)。把头的维度放在 batch 之后可以让 matmul 一次并行处理所有头。contiguous() 是因为 transpose 返回的是视图内存布局不连续直接 view 会报错必须复制后再展开。注意如果做的是“预测未来多步”的序列到序列任务需要在 scores 上叠加因果掩码。本文用固定窗口直接映射未来负荷不生成序列因此不需要。3.3 时空注意力块与预测头把两个 MultiHeadAttention 按第 2 章的“时间先空间后”顺序焊进一个块里再叠加 LayerNorm、残差与前馈网络。class SpatioTemporalBlock(nn.Module): def __init__(self, d_model, n_heads, dropout0.1): super().__init__() self.time_attn MultiHeadAttention(d_model, n_heads, dropout) self.spatial_attn MultiHeadAttention(d_model, n_heads, dropout) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.ffn nn.Sequential( nn.Linear(d_model, 4 * d_model), nn.GELU(), nn.Dropout(dropout), nn.Linear(4 * d_model, d_model)) self.dropout nn.Dropout(dropout) def forward(self, x): b, n, t, d x.shape # 时间注意力按站点逐个序列计算 xt_out, attn_t self.time_attn(x.reshape(b * n, t, d)) x self.norm1(x self.dropout(xt_out.reshape(b, n, t, d))) # 空间注意力按时刻逐个截面计算 xs_out, attn_s self.spatial_attn( x.permute(0, 2, 1, 3).reshape(b * t, n, d)) x self.norm2(x self.dropout( xs_out.reshape(b, t, n, d).permute(0, 2, 1, 3))) return x, attn_t, attn_s接着是完整预测网络class LoadForecaster(nn.Module): def __init__(self, n_sites, in_features, pred_len, d_model64, n_heads4, dropout0.1): super().__init__() self.n_sites n_sites self.pred_len pred_len self.embed nn.Linear(in_features, d_model) self.block SpatioTemporalBlock(d_model, n_heads, dropout) self.norm nn.LayerNorm(d_model) self.head nn.Sequential( nn.Linear(n_sites * d_model, 256), nn.GELU(), nn.Dropout(dropout), nn.Linear(256, n_sites * pred_len)) def forward(self, x): b x.size(0) x self.embed(x) x, attn_t, attn_s self.block(x) x self.norm(x)[:, :, -1, :] # 取最后时刻的全局表征 x x.reshape(b, -1) # (batch, n_sites * d_model) y self.head(x).view(b, self.n_sites, self.pred_len) return y, attn_t, attn_sembedding 层把负荷和气象特征统一映射到 d_model 维时空块负责信息交互预测头从最后一个时间步的特征直接映射到未来 24 步。取最后一个时间步而不是把所有步拉平能显著减少参数量而且经过注意力交互后的末尾状态已经包含了前面所有时刻的信息拉平全部时刻属于重复计算。4. 训练循环与评估让模型在电力负荷数据上真正收敛4.1 滑动窗口构造训练样本数据准备的第一步是把连续负荷表切成有监督样本。以 15 分钟粒度为例168 步历史就是一天半的数据24 步预测对应未来 6 小时读者可以根据自己数据粒度等比调整。def make_windows(data, n_sites, seq_len, pred_len): X, Y [], [] for i in range(len(data) - seq_len - pred_len): X.append(data[i:iseq_len].reshape(seq_len, n_sites, -1).transpose(1, 0, 2)) Y.append(data[iseq_len:iseq_lenpred_len, :n_sites]) return np.stack(X), np.stack(Y).transpose(0, 2, 1) X_train, Y_train make_windows(train_data, n_sites25, seq_len168, pred_len24) X_val, Y_val make_windows(val_data, n_sites25, seq_len168, pred_len24)X.shape 对应 (batch, n_sites, seq_len, features)Y.shape 对应 (batch, n_sites, pred_len)和上一章模型 forward 的输入输出完全对齐。数据切分时按时间顺序分成 train/valid/test 三段窗口滑动步长取 1 或几小时根据数据量调整数据量大时 stride 可以取 4减少样本重叠度。标准化处理放在窗口生成之前先用训练段的统计量 fit 一个 StandardScaler再 transform 验证段和测试段。预测的输出是归一化负荷评估前要用同一 scaler 的 inverse_transform 转回原始量纲再计算误差指标。from torch.utils.data import TensorDataset, DataLoader train_ds TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(Y_train)) train_loader DataLoader(train_ds, batch_size32, shuffleTrue)4.2 训练循环损失函数、优化器与梯度裁剪训练部分用 PyTorch 标准流程即可import torch.optim as optim model LoadForecaster(n_sites25, in_features5, pred_len24, d_model64, n_heads4) loss_fn nn.HuberLoss(delta1.0) optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-5) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) for epoch in range(30): model.train() epoch_loss 0.0 for Xb, Yb in train_loader: optimizer.zero_grad() pred model(Xb)[0] loss loss_fn(pred, Yb) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() epoch_loss loss.item() * Xb.size(0) scheduler.step() print(fepoch {epoch} loss {epoch_loss / len(train_ds):.4f})注意 model(Xb) 返回的是 (预测值, 时间注意力, 空间注意力) 三元组训练时只取第 0 项。HuberLoss 是负荷预测里常用的损失函数误差小于 delta 时按平方损失大于 delta 时按线性损失比 MSE 更能容忍尖峰负荷带来的大误差又比纯 MAE 收敛更稳定。梯度裁剪上限设 1.0防止注意力模块内部大矩阵乘法产生梯度爆炸这个参数在序列模型里几乎必设。4.3 评估指标MAPE、RMSE 与峰值时段误差训练完成后用三个指标评估每个指标的侧重点不一样def mape(y_true, y_pred): mask y_true ! 0 return torch.mean(torch.abs((y_true - y_pred)[mask] / y_true[mask])) * 100 def rmse(y_true, y_pred): return torch.sqrt(torch.mean((y_true - y_pred) ** 2)) def peak_error(y_true, y_pred, ratio0.05): k max(1, int(y_true.numel() * ratio)) idx torch.topk(y_true.flatten(), k).indices return torch.mean(torch.abs(y_true.flatten()[idx] - y_pred.flatten()[idx]))MAPE 直接回答“平均偏了几个百分点”汇报时最直观RMSE 放大了大误差的代价适合调度关注的大幅偏差场景峰值时段误差只统计真实负荷最高的 5% 时刻专门评价对尖峰负荷的捕捉能力因为尖峰时刻的预测误差直接影响备用机组决策。以下是一组参考分布具体数值随数据波动指标负荷平稳时段有强波动时段使用建议MAPE (%)2 ~ 55 ~ 10汇报用主指标RMSE5 ~ 1010 ~ 20关注大幅偏差PeakErr5 ~ 1515 ~ 30高峰、极端天气评估时特别注意峰值误差如果低谷时段 MAPE 很好看但 PeakErr 超过 25%说明模型在极端时段基本在猜需要回查训练集是否包含了足够多的极端样本。5. 时空注意力模型的参数区间与排错5.1 核心超参数经验范围超参数经验范围调节要点d_model32 ~ 128数据量小于 1 万样本请用 32 或 64n_heads2 ~ 8必须整除 d_model头数过多小数据集会收敛慢seq_len96 ~ 336至少覆盖一个完整日周期168 对小时粒度最常用dropout0.1 ~ 0.3多站点数据小于 50 个时建议 0.2 以上lr1e-4 ~ 1e-3与 CosineAnnealingLR 搭配使用时序模型调参有个重要差异序列长度不是越长越好。当站点数量大、注意力矩阵边长等于站点数时空间注意力的参数与计算开销增长明显窗口过长反而引入噪声而且每个站点序列的周期性在长序列中会被拉平。先跑小窗口基线再逐步加长观测 MAPE 的变化是更稳的做法。5.2 数据泄漏的三个高发点第一个高发点是标准化。必须先按时间切分再对训练部分的特征矩阵 fit 一个 StandardScaler验证集和测试集只用这个 scaler 做 transform。常见错误是把全部数据一起 fit再切分这样验证集分布信息已经进入了模型输入的缩放参数验证指标会系统性偏高。第二个高发点是洗牌。相邻时间窗口高度相关如果随机打散验证集里会出现训练集时间邻域内的样本模型相当于看到了答案。正确做法是按时间顺序切分三段批次内部的顺序可以打乱但要保证时间窗口在验证阶段不出现在训练集里。第三个高发点是气象特征。预测未来 24 小时时输入的气温、湿度必须是预报值不能用当天实况。训练时用实况温湿度模型把实况信息当作已知上线后预报误差进来预测立刻变差。处理方式是在特征工程阶段就把温度列换成数值天气预报并保留一列是否节假日的标志位。注意数据标准化必须遵守“先切分、再 fit、后 transform”的顺序。先 fit 全量数据再做时间切分是负荷预测项目里最常见的隐性数据泄漏。5.3 训练不收敛的排查步骤# 打开 NaN 追踪定位第一个出问题的样本 torch.autograd.set_detect_anomaly(True) for step, (Xb, Yb) in enumerate(train_loader): if torch.isnan(Xb).any(): print(输入含 NaN批序号, step) break排查顺序建议固定下来。先检查输入确认标准化没有除零、时间窗口没有越界拉出空行再检查标签确认 Y 里没有 NaN 或整段为 0 的区域然后把学习率从 1e-3 降到 1e-4排除优化器起步过猛的问题最后看注意力矩阵如果所有头在训练中几乎一样说明 dropout 太小或数据量不足以支撑多头结构把 n_heads 降到 2 或 4 通常能缓解。6. 注意力权重可视化与消融验证6.1 取出时空注意力权重并绘制热力图模型训练完把 forward 返回的注意力矩阵拿出来画热力图是检查模型有没有学到合理模式最直接的方法。import matplotlib.pyplot as plt model.eval() with torch.no_grad(): _, attn_t, attn_s model(xb[:1]) # batch 保持 1形状更直观 # attn_s: (1*T, heads, n_sites, n_sites) mat attn_s[:48, 0].mean(axis0) # 取第一个验证窗口的前两天第 0 个头 fig, ax plt.subplots(figsize(8, 6)) im ax.imshow(mat, cmapYlOrRd) ax.set_title(Spatial attention average (48 steps)) plt.colorbar(im) plt.show()时间注意力矩阵的对角线一定亮因为模型天然更信任邻近时刻。更有价值的是观察距离对角线较远的亮带如果每周一早上和上周一早上对应位置出现亮斑说明模型学到了周周期。空间注意力矩阵看行方向第 i 行第 j 列亮表示站点 i 在预测时重点参考了站点 j。拿这份矩阵跟实际变电站接线图做对照如果注意力集中在电气距离近的站点说明学习路径合理如果注意力权重和接线图完全对不上数据对齐或特征构造很可能有问题。6.2 用消融验证空间注意力是否真的有用时空注意力不是无成本收益。如果把空间注意力替换成恒等映射其他超参数保持不变对比两个模型的验证 MAPE能判断空间分支在当前数据上是否值得保留。替换方式很简单把 SpatioTemporalBlock 里 self.spatial_attn 相关代码注释掉forward 直接跳过空间注意力部分其余流程不动。如果替换后 MAPE 掉了 0.5 个百分点以上说明空间关联对预测有实质贡献如果几乎没变化就说明站点间耦合在当前数据里不明显继续保留只会增加显存占用和训练时间。这个消融结果既是模型选型的依据也是汇报与论文里最有说服力的一张表。拿热力图与消融指标一起放进模型评审文档时空注意力在电力负荷预测里是否值得保留就有了一组可复验的数字依据。本文还有配套的精品资源点击获取