ARTICLE DETAIL

资讯详情

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

PyTorch实现LSTM、GRU、RNN的时间序列预测:附温度与风电数据调参实战

PyTorch实现LSTM、GRU、RNN的时间序列预测:附温度与风电数据调参实战 简介面向时间序列预测任务这份Python项目包集成了LSTM、GRU、RNN三种经典循环神经网络的完整实现适合计算机、人工智能、数据科学等相关专业学生用于课程设计、毕设入门或算法对比实验。压缩包共15个文件大小5.83MB核心包括3个Python源码文件、2个已训练好的PyTorch模型权重.pt、3个Excel数据集和1个CSV温度数据覆盖从数据加载、模型训练到预测评估的完整流程便于直接运行并观察不同网络的效果差异。包内还附带项目配置文件环境搭建后即可上手模型与数据分离存储方便替换自有数据做二次开发。已有238人学习下载对于希望快速搭建时间序列Baseline、理解RNN系列模型差异的开发者而言是一份实用且轻量的参考资料。1. 从 RNN 到 LSTM为什么温度预测都在用这三个模型做时间序列预测的人最先接触的往往不是 ARIMA 或 Prophet而是 RNN、LSTM 和 GRU 这三兄弟。原因是它们把「序列」这个概念直接做进了网络结构——你输入的不再是一行独立样本而是一段有先后顺序的历史数据。无论是月平均气温、风电功率还是河流径流凡是带有时间依赖的回归任务这套结构几乎成了标配。本文这套资源里恰好把三类模型放在同一个训练脚本里数据集也覆盖了 CSV 和 Excel 多种格式适合拿来对比「同样数据下谁收敛更快、谁精度更好、谁更省显存」。我之前用这套代码做过一次风电数据的单步预测印象最深的是 GRU 在训练时间上比 LSTM 少约 30% 而精度几乎持平RNN 虽然最轻量却非常容易梯度消失输入序列一长就直接躺平。这类结论不是看论文看出来的是必须换数据集、调学习率、改 hidden_size 之后自己跑出来的。所以这篇不仅讲源码怎么用还会把每个超参数在气温和风电两类数据上应该怎么设、踩坑之后从哪里排查一并写清楚。适合课程设计、毕设初期验证也适合想快速对比三种循环网络效果的工程师。2. 数据与预处理运行源码前必须搞懂的格式和归一化问题2.1 资源里的数据集到底长什么样压缩包解压后数据落在00Data目录下核心有三个文件各地区每月平均温度.csv、月平均气温(landocean)1850-2022.10.xlsx、风电数据集.xlsx另外还有一个1月.xlsx作为补充。从文件名就能看出温度类数据适合做中长周期趋势预测风电数据则是典型的非平稳、强波动序列。两类的统计特性差异非常大直接套同一个模型参数往往温度能到 0.5 的 R²风电却可能完全发散。import pandas as pd df pd.read_csv(../00Data/各地区每月平均温度.csv, encodinggbk) print(df.head()) print(df.columns.tolist())上面的代码用gbk编码读取是因为多数中文 CSV 是 Excel 直接导出的默认编码是 GBK 而不是 UTF-8。如果遇到乱码可以把gbk换成gb2312或utf-8-sig后者的好处是能自动去掉 BOM 头。这个 CSV 的结构通常是「年份、月份、地区、温度」这种长表格式。如果要按地区拆分可以df[df[地区] 北京]单独提取如果要预测全球平均温度则直接用月平均气温(landocean)1850-2022.10.xlsx读取方式如下df_global pd.read_excel(../00Data/月平均气温(landocean)1850-2022.10.xlsx) df_global df_global.dropna() df_global df_global.set_index(Date) # 把日期列设为索引Excel 读取时最常见的坑是Date列被解析成字符串而不是日期类型导致后续seasonal_decompose报错。解决方法是读进来后显式转换pd.to_datetime(df_global.index, format%Y-%m-%d)。2.2 为什么要做滑动窗口构造样本循环神经网络不像传统回归那样直接把特征丢进去它要求每个样本是一段长度为seq_len的连续序列和一个对应的目标值。比如用过去 12 个月预测下一个月那么seq_len12。构造函数我习惯写成import numpy as np def create_sequences(data, seq_len12): xs, ys [], [] for i in range(len(data) - seq_len): xs.append(data[i:i seq_len]) ys.append(data[i seq_len]) return np.array(xs), np.array(ys)参数说明seq_len决定每个样本用多长的历史窗口。对月温度数据12 或 24覆盖一年或两年的周期效果最好对风电数据小时级数据用 2472 更合理因为风电的日周期性很强窗口太短捕捉不到完整的日内波动。滑动窗口构造完后样本数约等于总长度 - seq_len。如果数据集只有两百多条记录那窗口设为 24 之后可用样本就只剩一百多训练集、验证集、测试集的划分要保证时间顺序不能随机打乱否则未来信息会被泄漏到训练集里去。2.3 归一化LSTM 与 RNN 对尺度极其敏感RNN 系模型内部是tanh和sigmoid激活输入如果不在同一量级梯度很容易被某个大数值主导。温度数据范围大概是 -3035风电功率可能到 2000直接把原始值喂进去损失函数会剧烈震荡。常见的做法是 MinMaxScaler 或 StandardScaler我在这套资源里更推荐 MinMaxScaler因为温度序列的长期趋势会持续改变上下界用标准化的均值方差法会跟随时序漂移产生偏置。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(df_temp.values.reshape(-1, 1))注意这里必须对df_temp.values做reshape(-1, 1)因为 sklearn 的 scaler 要求输入是二维数组一维序列直接扔进去会报错。训练时把 scaler 保存在内存里预测完成后必须用同一个 scaler 做 inverse_transform否则预测结果画出来是 01 的数值没法跟真实温度对比pred_real scaler.inverse_transform(pred_scaled.reshape(-1, 1))很多新手把缩放和反缩放写成了两个 scaler导致预测曲线整体偏移这个错误在调试时非常隐蔽。建议在构造模型脚本时就把同一个scaler作为全局变量传递而不是复制一份。2.4 风速与温度两种数据在预处理上的差异风电数据集通常包含风速、风向、功率等多列如果只预测功率最直接的做法是只取功率列做单变量预测。但如果你想用风速辅助预测功率数据维度就变成二维需要单独对每列做归一化然后拼接成[样本数, seq_len, 特征数]的形状。注意LSTM 的输入张量最里层维度就是特征数不是 1。处理多特征时一个常见坑是不同列有各自的缺失值。温度数据基本是连续的是风电数据却常常因为停机维护产生大段空值。我一般先用df.interpolate(methodlinear)做线性插值如果空值超过 30%则该列直接丢弃因为循环网络对异常填充非常敏感插值造出来的假模式比缺失本身更危险。3. 核心源码拆解LSTM、GRU、RNN 共用一套 Trainer 的设计思路3.1 主脚本的模块结构04Code目录下有三个核心文件1.py、Time series prediction.py、TrainSave_model(Time series based on LSTM_RNN_GRU).py。后面两个文件名长得几乎一样但实际分工不同。Time series prediction.py是推理脚本加载已经保存的.pt模型文件做预测TrainSave_model...py是训练脚本负责构建数据、定义模型、训练并保存权重。1.py通常是一个快速验证脚本可能是直接用固定随机种子跑一遍也可能是把三模型封装成一个类的入口。我建议你优先读TrainSave_model那个文件因为它包含的完整训练循环才最能看出作者对超参数的选择。3.2 LSTM、GRU、RNN 三种网络的核心差异三种网络都属于循环结构但内部门的数量不同模型门控结构参数量训练速度适用场景RNN无门控仅循环权重最少最快短序列、趋势平缓、对精度要求不高LSTM遗忘门、输入门、输出门最多最慢长序列、存在长期依赖或周期性规律GRU重置门、更新门介于两者之间较快数据量中等需要平衡精度和训练时间LSTM 之所以能缓解梯度消失是因为它的细胞状态c_t以加法方式传递而 RNN 的隐藏状态h_t是直接连乘连乘一多梯度就指数衰减。GRU 在 LSTM 基础上合并了输入门和遗忘门为更新门所以参数减少约四分之一训练更快但短期记忆建模能力强于 LSTM 的结论并不成立实际效果要看你数据中依赖的长度。import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, output_size1): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): out, _ self.lstm(x) out out[:, -1, :] # 只取最后一个时间步的隐藏状态 out self.fc(out) return out代码里的out[:, -1, :]是序列预测和逐点预测的分水岭。如果你做的是单步预测用最后一步的输出接全连接层足以如果是多步预测比如预测未来 24 小时就要把每个时间步的输出都接线性层或再喂给另一个 LSTM 解码器。batch_firstTrue这个参数一定别丢它决定了输入形状是[batch, seq_len, feature]而不是[seq_len, batch, feature]新手最容易在这上面维度报错。GRU 和 RNN 的模型定义只是把nn.LSTM换成nn.GRU或nn.RNN。RNN 的默认激活函数是tanh遇到很长的序列建议在nn.RNN里显式传nonlinearityrelu有助于缓解梯度问题。3.3 训练循环里的关键参数说明核心训练代码通常长这样optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.MSELoss() for epoch in range(200): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() out model(x_batch) loss criterion(out, y_batch) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step()这里最容易被忽略的是clip_grad_norm_。LSTM 偶尔会发生梯度爆炸尤其在序列较长且学习率较大的时候。设置为 1.0 是最保守的做法如果你发现损失在训练到某个 epoch 后突然变成 NaN就检查这个值是否被注释掉了。lr0.001是 Adam 的默认值但对不同数据集表现出入巨大温度数据可以沿用 0.001风电数据通常要降到 0.0005 或 0.0003否则会在最优值附近震荡。训练完成后保存模型torch.save(model.state_dict(), ../03Save_Model/Model_LSTM时间序列预测.pt)注意资源中已经有两个.pt文件Model_LSTM时间序列预测.pt和Model_LSTM时间序列预测1.pt。前者可能是训练了 200 轮的权重后者可能是不同随机种子或不同数据划分下的结果。加载模型时需要重新定义与你之前完全一致的类结构然后再 loadmodel LSTMModel(input_size1, hidden_size64, num_layers2) model.load_state_dict(torch.load(../03Save_Model/Model_LSTM时间序列预测.pt)) model.eval()4. 实操跑通风电数据集训练并用验证集检查拟合4.1 适配两个 Excel 数据的 DataLoader 写法把Wind Power.xlsx读入后要放进 PyTorch 的 DataLoader否则手写循环喂数据麻烦又不规范。常见做法是把 numpy 数组转成 TensorDataset:from torch.utils.data import TensorDataset, DataLoader x_train_t torch.tensor(x_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32).view(-1, 1) train_dataset TensorDataset(x_train_t, y_train_t) train_loader DataLoader(train_dataset, batch_size64, shuffleFalse)shuffleFalse对时间序列是强制要求。如果打乱顺序模型会学到完全不存在的随机映射看起来训练损失降得不错但验证集上一塌糊涂。对严格时间序列推荐用torch.utils.data.SequentialSampler或直接不启用 shuffle保证每个 batch 里都是连续的真实数据。4.2 训练曲线怎么看训练脚本里通常会打印每个 epoch 的损失但只看 loss 数值很难判断模型是否过拟合。建议在训练时同时记录验证集损失EpochTrain LossVal Loss判断500.0120.011正常1200.0040.009开始过拟合1800.0030.012过拟合严重应早停一旦验证集损失连续 20 轮不降反而上升就保存当前权重并停止训练这就是 early stopping。资源里的训练脚本没有集成早停逻辑你可以在循环外定义一个best_val_loss float(inf)来判断。4.3 用可视化检查序列预测的形态预测完之后把真实值和预测值画在同一张图里千万不要只看 MSE。时间序列回归的 MSE 很低也可能是因为预测值是真实值的滞后版本这种滞后在气象数据里尤其常见。滞后现象表现为真实曲线峰尖靠前预测曲线整体右移一格说明模型本质上是在学「上一时刻的值等于下一时刻」的恒等映射。import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(y_test, labelreal, colorblack) plt.plot(pred_real, labelpred, colorred) plt.legend() plt.show()如果发现滞后优先增大seq_len窗口越长模型越能看到前期上升趋势滞后越弱。另一个技巧是把预测目标从原始数值改为相邻差值差分利用差分后的序列做预测再还原成真实值。这个技巧在处理风电功率这类非平稳数据时非常有效。5. 模型保存与推理从 .pt 加载权重并输出未来 N 步资源里的03Save_Model目录下放好了训练好的模型但光有.pt文件不够推理脚本中必须包含模型类的完整定义。很多人把.pt文件拷给别人后直接torch.load会报错AttributeError: Cant get attribute LSTMModel就是因为没有 import 定义该类的模块。正确的推理流程model LSTMModel(input_size1, hidden_size64, num_layers2, output_size1) model.load_state_dict(torch.load(../03Save_Model/Model_LSTM时间序列预测.pt, map_locationcpu)) model.eval() last_seq torch.tensor(x_test[-1], dtypetorch.float32).unsqueeze(0) with torch.no_grad(): pred model(last_seq).item()map_locationcpu是关键如果模型在 GPU 上训练而你的机器没有 CUDA不指定这个参数会直接报 CUDA 不可用。unsqueeze(0)的作用是把形状从[seq_len, 1]变成[1, seq_len, 1]即补上 batch 维。做多步预测时不能把未来预测值直接拼接进原始序列那么草率要维护一个滑动窗口。每预测出一个新值就把它追加到窗口末尾并丢掉窗口最前面的值def predict_future(model, last_seq, steps12): model.eval() preds [] with torch.no_grad(): for _ in range(steps): pred model(last_seq).item() preds.append(pred) new_seq torch.cat([last_seq[:, 1:, :], torch.tensor([[[pred]]], dtypetorch.float32)], dim1) last_seq new_seq return preds这段代码里的last_seq[:, 1:, :]表示去掉第一个时间步保留剩下 11 个然后拼接新预测值形成下一步需要的新窗口。这种滑动预测方式虽然简单但会累积误差——前期的预测误差会一步步传导到后面所以步数越多结果越平缓通常会退化成接近均值。如果要对未来 24 小时做高精度预测建议改成 seq2seq 结构用编码器 LSTM 把历史窗口编码成上下文向量再用解码器 LSTM 逐步生成未来值。不过那要求改写模型类资源自带的是单步预测结构滑动拼接已经能完成基本的多步预测需求。6. 进阶调参对气温与风电数据分别找出最优超参数6.1 用同一份数据做三模型横向对比既然资源里同时提供了 LSTM、RNN、GRU 的源码最直观的用法是固定seq_len、hidden_size、batch_size和epochs然后只切换模型类记录三者的训练时间和验证集 RMSEmodels { LSTM: LSTMModel, GRU: GRUModel, RNN: RNNModel } for name, model_cls in models.items(): model model_cls(hidden_size64, num_layers2) # 复用同一套训练循环这个对比最好在温度数据集上做因为它的波动相对温和训练稳定能清楚体现 LSTM 训练最慢但收敛更好。风电数据集是典型的高波动、强非平稳你会发现 RNN 的验证集损失到最后还在大幅震荡这是因为风电序列梯度不稳定RNN 的简单结构很难学出来。对比时有一个参数不要随便动就是随机种子torch.manual_seed(42) np.random.seed(42)不固定种子的话每次初始化权重不同模型的表现差异可能比更换模型本身的差异还大横向对比就失去意义了。6.2 seq_len 和 hidden_size 的调参策略对月温度数据推荐从seq_len12、hidden_size32、num_layers1起步然后按网格搜索组合。不要一上来就两层hidden_size256模型复杂度远超数据量后温度这种长周期数据特别容易过拟合。气温序列的周期性很强seq_len12时模型能看到一个完整年度循环效果通常比seq_len6好很多。但seq_len超过 36 后信息增益就不再明显反而序列太长会让隐含层难以记住更早的依赖。风电数据建议hidden_size从 64 起跳因为功率波动幅度大需要更多神经元去拟合非线性。风电数据存在明显日周期seq_len24小时级数据是起步配置如果看分钟级风速数据seq_len可以到 144这时候梯度消失风险增加必须在训练中用clip_grad_norm_配合 LSTM 或 GRU。6.3 损失函数选择与评价指标补充资源里默认使用MSELoss。对温度预测来说没问题但风电功率是零到几千的大跨度数据直接只看 MSE 会被高峰值片段主导。建议同时输出 MAE 和 R²弥补 MSE 在大幅误差面前被放大的问题from sklearn.metrics import mean_absolute_error, r2_score mae mean_absolute_error(y_test, pred_real) r2 r2_score(y_test, pred_real) print(fMAE: {mae:.3f}, R2: {r2:.3f})如果 R² 接近 0说明模型跟直接用历史均值预测没什么差别。温度数据 R² 通常能到 0.9 以上风电数据能到 0.7 已算不错。如果 R² 是负数优先检查预测是否做了完整反归一化以及测试集是否在训练集之前的时间段时间泄漏的相反情况。6.4 一个实用技巧用早停保存最佳权重再重新加载训练脚本保存模型的位置是固定的可以用一个技巧让训练结束自动保留最佳验证模型best_val float(inf) best_epoch 0 for epoch in range(epochs): train_loss train_one_epoch(...) val_loss validate(model, val_loader) if val_loss best_val: best_val val_loss best_epoch epoch torch.save(model.state_dict(), best_model.pt)每次验证损失刷新时就覆盖保存一次训练结束后加载best_model.pt做测试比直接使用最后一轮权重稳定得多。这算是我每次跑这类时间序列预测最常用的小技巧。注意不要用.pt和.pth混用加载时保持后缀一致即可03Save_Model路径里的中文文件名在 Windows 下没问题但如果把项目部署到 Linux 服务器上路径中含中文可能导致张量反序列化失败建议统一改成英文文件名。本文还有配套的精品资源点击获取
返回列表