ARTICLE DETAIL

资讯详情

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

CNN-GRU-Attention时间序列预测实战:电力负荷预测代码解析与避坑指南

CNN-GRU-Attention时间序列预测实战:电力负荷预测代码解析与避坑指南 简介针对电气领域中的时间序列预测任务这一深度学习代码包以CNN-GRU-Attention混合模型为核心面向电力负荷预测、设备故障预警等典型场景适合有一定深度学习基础并希望将注意力机制运用于实际时序问题的研究者和工程师。压缩包共8个文件主要包含Python模型脚本.py、样例数据集.csv、环境依赖说明.txt及配套说明文档整体仅1.24MB结构清晰便于快速下载与本地部署。目前已有109人学习下载。资源内提供了可复现的训练流程涵盖数据读取、归一化、模型搭建与结果评估并标注了依赖包版本便于环境配置读者可通过修改网络层参数或注意力权重直观理解CNN提取局部特征、GRU捕捉长程依赖、Attention突出关键信息的三级架构在时序预测中的优势。1. 这份CNN-GRU-Attention预测代码从压缩包到出数的完整闭环做电力负荷预测的人应该都对这类场景不陌生手头只有一份历史负荷数据领导要求预测未来几小时的用电趋势传统ARIMA在非平稳序列上表现飘忽而LSTM又往往在长序列上丢关键信号。我拿到这份051cnn-gru-attention预测 Python程序.zip时第一反应是“终于有人把数据、模型、训练脚本打包在一起了”。它实际上是一个完整的深度学习预测工程用一维CNN提取局部波动特征GRU承接时间依赖Attention层给每个时间步分配权重数据是load1.csv电气负荷记录编程语言是Python主程序集中在一个CNN-GRU-Attention.py文件里对刚入门时序预测的从业者和学生来说这是能直接跑通并对照学习的好素材。2. 拆包与数据预处理load1.csv格式、依赖清单、滑动窗口参数2.1 压缩包结构分析先把压缩包解开看内容。它包含主程序CNN-GRU-Attention.py、数据集load1.csv、依赖包版本文件依赖包版本_2.txt另有一份说明.txt。注意有个__MACOSX目录这是macOS压缩产物解压出来的隐藏目录里面的说明文件是重复副本可以忽略或直接删掉——别让它混进你的工程目录。依赖清单文件依赖包版本_2.txt一定要先看。这个项目基于PyTorch编写涉及numpy、pandas、matplotlib、scikit-learn。装环境时先建干净的虚拟环境再用这个文件对照版本。重点PyTorch的版本和CUDA必须匹配否则模型训练时直接报错。我一般的做法是装PyTorch CPU版先跑通再决定要不要上GPU避免一上来就被驱动版本卡住。2.2 load1.csv的数据结构判断load1.csv是这个项目的核心素材。电气负荷采集数据最常见的格式是第一列时间戳、第二列总负荷值。用pandas简单读一下就知道具体结构import pandas as pd # 先读前5行看清格式不要急着训练 df pd.read_csv(load1.csv, encodingutf-8) print(df.head()) print(f数据量: {df.shape[0]} 行, 特征数: {df.shape[1]}) print(f缺失值数量: {df.isnull().sum().sum()})这段代码解决的是“数据黑匣子”问题。很多项目拿到csv就急着建模结果第一列时间戳被当成特征或者缺失值没有处理训练出的模型完全不能看。读前几行和统计缺失值是我拿到任何数据集的第一件事。如果第一列是时间字符串建模时需要单独处理如果第一列直接是数值负荷则可以直接作为序列特征。实际处理中电流、电压、功率数据很可能存在个别缺失行。常见做法是线性插值import pandas as pd import numpy as np df pd.read_csv(load1.csv) df df.interpolate(methodlinear, limit_directionboth) # 只保留负荷值列转成float类型 data df.iloc[:, 0].values.astype(np.float32)interpolate的limit_directionboth表示序列两端的缺失也用最近邻方向的数据补上只填中间空档容易在边界产生NaN。这段代码之后数据应该是一维数组长度就是原始行数。2.3 归一化与滑动窗口切分负荷数值一般在几十到几千的范围直接喂给神经网络会导致梯度震荡。标准做法是用MinMaxScaler归一化到[0,1]区间。归一化之后做滑动窗口切分用过去N个时刻预测未来M个时刻这是时序预测的通用格式from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(df.iloc[:, 0].values.reshape(-1, 1)) def create_sequences(data, window_size, predict_len1): X, y [], [] for i in range(len(data) - window_size - predict_len 1): X.append(data[i:i window_size, 0]) y.append(data[i window_size:i window_size predict_len, 0]) return np.array(X), np.array(y) WINDOW_SIZE 24 # 用过去24个时间点 PREDICT_LEN 1 # 预测未来1个时间点 X, y create_sequences(scaled_data, WINDOW_SIZE, PREDICT_LEN)WINDOW_SIZE的选择直接决定预测效果窗口太短模型看不到完整的波动周期窗口太长训练数据量缩减计算开销变大。对小时级的负荷数据24小时窗口是常用起点。数据划分必须按时间顺序切前80%训练、后20%测试绝不能用train_test_split默认的随机打乱后续在避坑章节展开。3. 模型结构拆解CNN提局部特征、GRU记时序依赖、Attention加权聚焦3.1 为什么是CNN-GRU-Attention这个组合单独用GRU做预测模型能够学到时间依赖但原始序列进入GRU之前缺少一次特征增强单独用CNN能提取局部模式但缺乏长期记忆能力。这份代码把三层串起来CNN作为特征提取的前置模块等效于在时间维度上滑动卷积核自动发现相邻几个时间步的局部波动模式GRU承接CNN输出学得比较长的依赖关系Attention层为GRU每个时间步的输出计算权重把注意力集中在最像转折点的位置。这个组合的价值在于电力负荷受工作日、气温、生产计划多重因素影响有些时段的变化对预测结果影响剧烈普通RNN对所有时间步一视同仁Attention让模型学会自己“挑重点”。从工程角度看这个组合不需要额外的人工特征工程原始序列进去、预测结果出来省去了大量手调特征的时间。3.2 核心模型定义还原与参数说明原项目CNN-GRU-Attention.py的模型结构我按PyTorch最常见的写法还原如下打开主程序时对照着看import torch import torch.nn as nn class CNNGRUAttention(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, dropout0.2): super(CNNGRUAttention, self).__init__() # 一维CNN提取局部特征 self.conv1 nn.Conv1d(input_size, 32, kernel_size3, padding1) self.conv2 nn.Conv1d(32, 64, kernel_size3, padding1) self.relu nn.ReLU() # GRU学习时间依赖batch_firstTrue时输入为(batch, seq_len, features) self.gru nn.GRU(64, hidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout) # Attention对GRU每个时间步的输出计算一个重要性分数 self.attention nn.Linear(hidden_size, 1) # 输出层 self.fc nn.Linear(hidden_size, 1) def forward(self, x): # 输入x形状: (batch, seq_len, features) x x.permute(0, 2, 1) # 转为(batch, features, seq_len)适配Conv1d x self.relu(self.conv1(x)) x self.relu(self.conv2(x)) x x.permute(0, 2, 1) # 转回(batch, seq_len, channels) gru_out, _ self.gru(x) # gru_out: (batch, seq_len, hidden_size) # 注意力权重计算 attn_scores self.attention(gru_out) # (batch, seq_len, 1) attn_weights torch.softmax(attn_scores, dim1) context torch.sum(attn_weights * gru_out, dim1) # 加权求和 return self.fc(context) # 输出(batch, 1)kernel_size3的含义是每个卷积核一次看相邻3个时间步padding1保证卷积不改变序列长度。hidden_size64是GRU内部状态维度维度越高拟合能力越强但小数据集上容易过拟合。num_layers2代表堆叠两层GRU初学者可以从1层起步逐层加。最后一层输出维度是1对应单步预测。如果你想预测未来多个时刻把nn.Linear(hidden_size, 1)的1改成预测步数即可同时标签也要相应调整。3.3 训练循环与评估指标模型的训练流程就是标准PyTorch套路定义损失函数、优化器、迭代训练。这份代码用的应该是均方误差MSE因为负荷预测是回归问题MAE虽然更直观但对离群点不够敏感MSE能放大较大误差的惩罚让模型优先拟合那些偏差大的点。import torch.optim as optim from torch.utils.data import TensorDataset, DataLoader X_tensor torch.tensor(X, dtypetorch.float32).unsqueeze(-1) y_tensor torch.tensor(y, dtypetorch.float32) # 按时间顺序切分前80%训练后20%测试 train_X, train_y X_tensor[:int(len(X)*0.8)], y_tensor[:int(len(y)*0.8)] test_X, test_y X_tensor[int(len(X)*0.8):], y_tensor[int(len(y)*0.8):] train_dataset TensorDataset(train_X, train_y) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) model CNNGRUAttention() optimizer optim.Adam(model.parameters(), lr1e-3) loss_fn nn.MSELoss() for epoch in range(100): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() pred model(x_batch) loss loss_fn(pred, y_batch) loss.backward() optimizer.step() if epoch % 20 0: print(fEpoch {epoch}, Loss: {loss.item():.6f})batch_size64是通用设置数据量小时改成16或32反而更稳。shuffleTrue在训练时打乱样本顺序有助于收敛但测试阶段不能打乱必须是时间顺序。训练完成后的评估重点看两个指标RMSE和R²前者衡量绝对误差的平均水平后者衡量预测曲线对真实曲线的解释程度。from sklearn.metrics import mean_squared_error, r2_score import numpy as np model.eval() with torch.no_grad(): test_pred model(test_X).numpy().flatten() test_true test_y.numpy().flatten() # 反归一化还原真实负荷值 test_pred_real scaler.inverse_transform(test_pred.reshape(-1, 1)).flatten() test_true_real scaler.inverse_transform(test_true.reshape(-1, 1)).flatten() rmse np.sqrt(mean_squared_error(test_true_real, test_pred_real)) r2 r2_score(test_true_real, test_pred_real) print(fRMSE: {rmse:.4f}, R²: {r2:.4f})反归一化是评估中最容易漏的一环。模型输出是[0,1]区间直接跟真实负荷比大小没有意义必须用训练阶段fit好的scaler做inverse_transform还原。4. 运行避坑清单解压、归一化、切分、评估的五个经典翻车点4.1 __MACOSX隐藏目录干扰工程结构现象解压后多了一个__MACOSX目录里面还有一份说明txt的副本直接复制整个目录进项目时出现文件冲突。原因macOS系统压缩zip时自动生成元数据文件夹Windows或Linux解压后保留为普通目录不是病毒也不是项目自带逻辑。解决解压后直接删除__MACOSX目录把CNN-GRU-Attention.py、load1.csv按自己习惯的工程结构重新存放。主程序路径不要出现中文和空格避免某些库读取csv时编码错乱。4.2 MinMaxScaler在测试集上重复fit现象训练损失正常下降测试集预测值整体偏移真实值且偏移方向一致。原因有人在切分数据后分别对训练集和测试集做了fit_transform测试集的归一化基准跟训练集不一致反归一化后自然对不上。这是个非常隐蔽的错误因为训练过程本身没有问题。解决正确的顺序是先对全部数据fit再切分、再transform。用训练阶段保存的scaler直接transform测试集不重新fit。# 正确做法 scaler.fit(train_data) scaled_train scaler.transform(train_data) scaled_test scaler.transform(test_data)4.3 读取csv时索引列被误当成特征现象模型训练时维度报错或者特征数比预期多1。原因pd.read_csv(load1.csv)会把第一列自动识别为索引列但如果第一列本来就是负荷数值再被当成索引会导致下面的数据错位。同理有时候第一列是时间戳却被当成数值特征参与训练。解决读取后第一时间打印df.dtypes和df.shape确认列数。如果第一列是辅助索引或时间列用df.iloc[:, 1:]选择后面的特征列如果第一列就是负荷值且没有时间戳保持iloc[:, 0]不变。4.4 随机切分破坏了时间序列顺序现象训练曲线很正常测试集R²也很高但画图发现预测值在时间轴上频繁跳变完全没有趋势感。原因train_test_split默认shuffleTrue把时间序列随机打乱后训练集中混入未来数据模型学到的是“穿越信息”测试集上尽管指标好看实际部署时会完全失效。解决时间序列数据切分必须按顺序切。本项目用train_X[:n]和train_X[n:]的写法直接按位置比例切分天然保证顺序。如果一定要用train_test_split必须加上shuffleFalse。4.5 单步预测的滞后效应现象测试集上RMSE很低但画预测曲线时预测值总是比真实值滞后一个时间步看起来像平移过的真实曲线。原因这是单步预测最常见的问题模型本质上学会的是“上一时刻的值约等于下一时刻的值”尤其在负荷平稳段很好用但在转折点完全失效。RMSE指标无法暴露这个问题因为平移曲线的误差并不大。解决把R²和滞后相关性结合判断——计算真实值和预测值在不同滞后阶数下的相关系数。正常情况滞后0阶相关系数最高如果滞后1阶更高说明模型在“复读”而不是“预测”。更彻底的方案是改用多步预测即用24个历史点预测未来3个点而不是只预测下一个点。这样模型被迫学到变化趋势而不只是做短时复制。5. 小数据下的训练策略dropout、早停、滚动验证如何落地5.1 数据量不足时的过拟合判断load1.csv的数据量如果是几千行级别切分后训练样本大约只有几千个滑动窗口样本。这个量级下hidden_size64、num_layers2的模型很容易把训练集背下来表现为训练损失一路降到很有吸引力测试集R²却低于训练集一大截。判断是否过拟合最直接的方法是打印训练集和测试集的loss差异model.eval() with torch.no_grad(): train_loss loss_fn(model(train_X), train_y).item() test_loss loss_fn(model(test_X), test_y).item() print(fTrain Loss: {train_loss:.6f}, Test Loss: {test_loss:.6f})如果test_loss是train_loss的2倍以上基本可以断定过拟合。这时候先别急着加数据——很多场景下数据就这么多——先从模型瘦身开始。5.2 调低容量、加dropout和L2正则过拟合的应对不是盲目减少层数而是对症下药。第一优先把hidden_size从64降到32num_layers从2降到1。模型越小拟合上限越低但泛化能力往往更好。第二优先适当加大dropout概率原代码默认dropout0.2项目数据量小时调到0.3甚至0.4把一部分神经元随机置零强制模型学冗余特征。第三优先是L2正则PyTorch在优化器里直接支持optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5)weight_decay就是L2惩罚系数1e-5到1e-4是常见区间。它的作用是约束权重不能太大防止某些特征被过度放大。很多人在小数据上跳过这一步实际效果差别很明显。5.3 早停机制的实现早停是最实用的防过拟合手段本质是“在测试损失不再下降时停止训练”。训练过程中记录每一轮的验证损失如果连续N轮没有下降就回滚到最好的那一轮模型参数。best_loss float(inf) patience 15 counter 0 for epoch in range(200): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() loss loss_fn(model(x_batch), y_batch) loss.backward() optimizer.step() # 每个epoch结束用验证集评估 model.eval() with torch.no_grad(): val_loss loss_fn(model(val_X), val_y).item() if val_loss best_loss: best_loss val_loss counter 0 torch.save(model.state_dict(), best_model.pt) else: counter 1 if counter patience: print(fEarly stopping at epoch {epoch}) break model.load_state_dict(torch.load(best_model.pt))patience15表示连续15轮验证损失没有新低就停。这个数值不要设太小因为MSE的下降本来就有波动设5以内很容易过早停止。训练结束后用load_state_dict把最好的参数恢复回来而不是用最后一轮的参数。5.4 时序数据的滚动验证小数据集上常用的另一个验证思路是滚动验证也叫时间序列交叉验证。做法是固定窗口训练集不断向后推进验证集位置from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits4) for train_idx, val_idx in tscv.split(X): train_X_CV X_tensor[train_idx] val_X_CV X_tensor[val_idx] # 每组分别训练和评估最终取平均TimeSeriesSplit(n_splits4)会把数据分成5段每次用前4段训练、最后1段验证然后逐轮推进。这套机制的好处是多用了几组不同的验证区间评估结论比单次20%切分更可靠代价是训练时间变为原来的几倍。数据量小、训练轮数不多时值得做一轮完整滚动验证来确定最终的参数组合再在全量训练集上重新训练。6. 进阶注意力权重可视化与多步预测的验证技巧注意力机制最大的优势在于可解释性。模型学完之后我们可以直接把Attention权重提取出来看看模型到底把注意力放在哪些时间步上。这个信息对电力负荷预测尤其有价值——如果模型持续关注最近几个时间步说明它主要通过短期趋势判断未来如果它把权重分配给更早的周期节点说明它学到了周期性规律。验证方法是修改forward函数把权重一并返回def forward_with_attention(self, x): x x.permute(0, 2, 1) x self.relu(self.conv1(x)) x self.relu(self.conv2(x)) x x.permute(0, 2, 1) gru_out, _ self.gru(x) attn_scores self.attention(gru_out) attn_weights torch.softmax(attn_scores, dim1) context torch.sum(attn_weights * gru_out, dim1) return self.fc(context).squeeze(-1), attn_weights.squeeze(-1) model.eval() with torch.no_grad(): _, attn_w model.forward_with_attention(test_X[:1]) print(attn_w.numpy()) # 每个时间步的注意力权重画成热力图就能直观看到在负荷陡升或陡降的样本中Attention权重往往集中在几个关键时间步。如果权重分布过于均匀说明Attention层没有学到有效区分这时可以增大hidden_size或者给注意力层增加一层非线性变换。多步预测的验证技巧也有一个习惯可以分享不要只用单步预测评估试着连续滚动预测未来K个时刻每步预测完把预测值拼回输入继续预测下一步。这种“吃自己输出”的验证方式会放大误差累积能更真实地反映模型的鲁棒性。我拿到新数据都会把真实值和预测值的散点图画出来看R²是否有虚高再做一次滞后相关检查。这套动作帮我避开了好几次“指标漂亮但实际不能部署”的尴尬。希望这份从拆包到调参再到验证的思路能帮你少走弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表