资讯动态

PyTorch多变量时序预测:DeepAR/Informer/Transformer/RNN统一框架与避坑指南

发布时间:2026/9/27 23:10:46 来源:尧图企业网站定制
简介这是一份基于PyTorch框架的时间序列预测算法研究与实现资源面向深度学习方向的研究者、算法工程师和数据科学从业者可用于金融、气象、工业等场景下的多变量时间序列分析与预测。资源聚焦DeepAR、Informer、Transformer、RNN等经典模型的复现与改进同时覆盖LSTNet、TCN、MLP、DeepFactor等对比模型从模型结构、损失函数到训练流程均有可运行代码支撑。压缩包共61个文件体积约55.25MB主体为49个Python脚本覆盖数据加载、特征构造、分布输出、评估指标、结果可视化和各模型实验入口另含4张效果图、4个gz数据文件及txt/docx/json/md等辅助材料便于对照论文理解与二次开发。目前已有95人学习下载。资源内置清晰的目录结构除模型实现外还提供实验脚本、测试数据与结果分析说明可帮助使用者完成从数据预处理、模型训练到概率预测绘图的全流程工作尤其适合需要复现和优化时间序列深度学习模型的读者参考。1. 时间序列预测模型别只盯着一张 loss 曲线做时间序列预测的人大概率经历过这种场景模型在测试集上 RMSE 漂亮得感人一上真实业务数据就原形毕露。我拆这份基于 PyTorch 的时序预测资源包时最直观的感受是它不打算让你跑通一个模型就收工而是把 DeepAR、Informer、Transformer、RNN 这四类主流模型塞进同一套代码框架里用统一的数据接口和训练管线做多变量时间序列分析与预测。这对两类人特别有用一是刚接触时序预测、想在多个模型间快速做基线对比的研究生二是已经在用 LSTM 或 Transformer 做单变量预测、想扩展到多变量场景的算法工程师。它解决的核心问题不是哪个模型精度最高而是在同样数据、同样预处理、同样评估口径下不同模型的差距到底来自架构还是来自工程细节。这个话题我在实际项目中踩过太多次坑下面按我自己拆解这套资源的顺序把模型选型、数据构造、训练评估和排错经验完整写出来。2. 模型选型与统一接口四个模型如何放进同一套训练管线2.1 先搞清四个模型的定位差别很多人一上来就纠结哪个模型最好实际上这四个模型根本不在同一个赛道上。RNN 是时序预测的老牌 baseline适合中等长度序列、特征维度不高的场景优点是训练快、显存占用低缺点是长程依赖能力弱LSTM 作为 RNN 的改进版解决了部分梯度消失问题但本质上还是按时间步顺序计算无法并行。Transformer 靠自注意力机制把序列内任意两个位置直接关联起来理论上能捕捉更长依赖但标准 Transformer 的注意力计算复杂度是 O(n²)序列一长显存就吃紧。Informer 是专门针对长序列设计的改进版核心是 ProbSparse 自注意力机制把复杂度降到 O(n log n)同时用蒸馏操作减少网络层数它在长序列预测场景下比标准 Transformer 有明显速度优势。DeepAR 则完全是另一种思路——它不直接预测未来值的点估计而是输出未来每个时间步的概率分布本质上是把时序预测建模成自回归的极大似然估计问题。这四个模型放在一起对比真正要看的不是谁更先进而是你的业务场景需要点预测还是概率预测、序列长度是几百还是几千、特征维度是几个还是几十个。2.2 统一接口的价值换模型不改数据管线这套资源包做得最聪明的地方是设计了一层统一模型接口。我在实际工程中发现很多时序预测项目之所以对比不公平就是因为不同模型的输入格式、损失函数、预测输出都不一样最后对比出来的差异其实是工程差异。这个包的做法是用一个模型工厂函数来统一入口核心代码如下# model_factory.py import torch.nn as nn from models.rnn_model import RNNModel from models.deepar_model import DeepARModel from models.transformer_model import TransformerModel from models.informer_model import InformerModel MODEL_REGISTRY { rnn: RNNModel, deepar: DeepARModel, transformer: TransformerModel, informer: InformerModel, } def build_model(model_name: str, config: dict) - nn.Module: 根据模型名称和配置字典构建模型实例。 config 里必须包含 input_size、hidden_size、output_size 等公共参数 各模型特有的参数如 DeepAR 的 num_mixture_components也放在里面。 if model_name not in MODEL_REGISTRY: raise ValueError(fUnsupported model: {model_name}, available: {list(MODEL_REGISTRY.keys())}) model_cls MODEL_REGISTRY[model_name] return model_cls(configconfig)这段代码的逻辑很直接用字典做模型名到类的映射build_model接收统一的 config 字典由工厂函数负责任何模型特有参数的透传。这样做的直接好处是你在训练脚本里只需要改一行model_name transformer或model_name deepar数据处理、训练循环、评估逻辑全部复用模型之间的对比公平性就有了保障。参数方面有一个关键约定config字典里的公共键名必须保持一致比如input_size在所有模型里都表示输入特征维度hidden_size都表示隐藏层单元数seq_len都表示输入序列长度。我在自己的项目里也是这么设计模型层的省掉大量重复代码而且换模型做对比实验时不容易出错。2.3 各模型核心组件拆解RNN 模型的文件结构是最简单的那种一个nn.Module子类里面通常有两层第一层是nn.LSTM或nn.GRU看配置第二层是回归头nn.Linear把最后一步的隐状态映射到预测值。它的 forward 输入形状是(batch, seq_len, input_size)输出是(batch, output_size)这里的output_size在多变量预测里等于预测的步长乘目标变量数。DeepAR 的模型结构相对复杂一些它的核心是自回归解码器。训练阶段的做法是在时间步 t把上一步的目标值y_{t-1}和当前协变量x_t拼在一起作为输入输出当前步目标值的分布参数——通常是高斯分布的均值和方差或者混合高斯分布的权重、均值和方差。推理阶段没有真实目标值可用就需要用采样方式逐步生成从预测分布里采一个样本作为下一步的输入这样一步步滚到预测终点。这个包的实现里有一个num_samples参数控制推理时采样多少条轨迹最后用这些轨迹的均值作为点预测分位数作为区间预测。这也是我把 DeepAR 单独拎出来的原因——它在模型结构上和 RNN、Transformer 有本质差异。Informer 的代码里最值得看的部分是 ProbSparse 注意力实现。它并不是对所有 attention 对都计算打分而是先根据稀疏性度量选出最关键的几个 query只计算这些 query 和 key 之间的点积再通过采样和修正补偿来近似完整注意力输出。在代码层面它维护一个sparse_threshold参数控制每个 query 块里保留多少比例的 top-k 值参与计算。这个参数调得太小会退化成一堆随机噪声调得太大又失去加速意义我一般在长序列超过 500 步场景下才会动它短序列保持默认值即可。Transformer 模型的代码是标准实现重点是位置编码和掩码。位置编码用的是正弦余弦函数生成固定编码不参与训练掩码的核心是保证未来信息不泄露——注意力矩阵右上角全部置为-inf经过 softmax 后权重归零。这个细节在时序预测里非常关键我在后面避坑章节会专门展开讲。3. 多变量数据预处理滑窗、归一化与时间戳切分的正确顺序3.1 数据集的统一构造方式数据预处理是整个时序预测工程里最容易被低估的环节。很多人的翻车经历不是模型没选好而是数据进模型之前就已经错了。这个包的做法是先构造一个统一的滑动窗口数据集类把所有数据转换逻辑收敛到一个地方# dataset.py import torch from torch.utils.data import Dataset class TimeSeriesDataset(Dataset): 多变量时序数据集用固定窗口滑动裁剪样本。 features: shape (total_timesteps, num_features) targets: shape (total_timesteps, num_target_vars) seq_len: 输入窗口长度 pred_len: 预测窗口长度 def __init__(self, features, targets, seq_len48, pred_len12): self.features torch.FloatTensor(features) self.targets torch.FloatTensor(targets) self.seq_len seq_len self.pred_len pred_len def __len__(self): return len(self.features) - self.seq_len - self.pred_len 1 def __getitem__(self, idx): x self.features[idx : idx self.seq_len] # (seq_len, num_features) y self.targets[idx self.seq_len : idx self.seq_len self.pred_len] # (pred_len, num_target_vars) return x, y这个类的逻辑很朴素给定一段连续的多变量特征矩阵用两个固定长度的窗口在时间轴上滑动前seq_len步作为输入紧接着的pred_len步作为要预测的目标。需要注意idx的取值范围推导——最后一个可用样本的索引必须是len - seq_len - pred_len因为索引后面还必须能截出完整的预测窗口。features和targets可以来自同一个原始矩阵的切片也可以不同——比如用天气特征做输入、用电负荷做目标这个灵活度在实际项目中非常重要。3.2 归一化的正确时机与逆变换归一化几乎是所有时序模型训练的必要前置步骤但这个包的处理方式有一个值得学习的细节它把归一化做在滑动窗口切分之前并且在训练集上拟合 scaler然后用同一个 scaler 去 transform 验证集和测试集。这一点我必须强调因为我在实际项目中见过太多人犯一个错——对全量数据用同一个 scaler 统一做归一化再切分数据集。这样做相当于让测试集的信息在训练阶段泄露进 scaler 的均值和方差里测试指标会虚高模型上线后真实表现会打折扣。正确的做法是下面这样# preprocessing.py from sklearn.preprocessing import StandardScaler scaler StandardScaler() # 只用在训练集上拟合 train_scaled scaler.fit_transform(train_raw.reshape(-1, num_features)) # 验证集和测试集用同一个 scaler 转换 val_scaled scaler.transform(val_raw.reshape(-1, num_features)) test_scaled scaler.transform(test_raw.reshape(-1, num_features))这里的逻辑是fit_transform只在训练集上调用训练集的均值和标准差就固定下来之后验证集和测试集调用transform时用的是训练集的统计量不再重新拟合。参数方面值得注意reshape(-1, num_features)这一步——StandardScaler要求输入形状是二维的(样本数, 特征数)原始时序数据是(时间步, 特征数)形状恰好匹配但如果你的数据是三维的(batch, time, feature)需要先展平再归一化否则每个样本的每个时间步会被当作独立样本归一化结果完全错误。至于逆变换预测输出还原到原始量纲时也要注意对齐形状。如果模型输出是(batch, pred_len, num_target_vars)你需要把它reshape(-1, num_target_vars)之后再调用scaler.inverse_transform()最后再弄回原来的形状。这个顺序反了的话数值对不上画出来的曲线和真实值之间会有一个奇怪的偏移。3.3 时间序列切分的两个坑切分数据集的时候很多做图像分类养成的习惯会在这里栽跟头。图像分类里随机打乱数据是标配但时序数据绝对不能随机打乱——时间顺序本身就是数据的核心结构。这个包的代码里切分方式是按时间比例切比如前 70% 训练、中间 15% 验证、最后 15% 测试。这里有一个细节如果你的数据有明显的周期性趋势比如电力负荷的日周期和周末效应时间比例切分比按样本数量切分更合理因为前者能保持连续时间段的完整性。另一个坑是目标变量的构造。多变量预测有两种常见设定一种是预测未来所有变量的值另一种是用多个历史变量预测其中一部分目标变量的未来值。这个包支持的是第二种targets参数单独传了一个矩阵维度可以和features不同。我在实际业务里经常遇到的情况是输入特征有温度、湿度、风速、历史负荷等 8 个变量但真正要预测的只有未来 1 小时的负荷这时候num_features8、num_target_vars1目标矩阵单独从负荷列里切出来。很多初学者会把features和targets当成同一个矩阵用在部分变量要预测、部分变量只做特征时就会出问题。4. 训练与评估指标损失函数、概率预测和基准怎么对齐4.1 不同模型应该用不同损失函数训练脚本里最值得研究的部分是损失函数的选择。这个包对不同模型配置了不同的默认损失函数这一点设计得非常专业。RNN、Transformer、Informer 这三个模型做的是点预测默认用 MSE均方误差或 Huber LossDeepAR 做的是概率预测用的是负对数似然损失NLL Loss。为什么不能统一用 MSE因为 DeepAR 的预测头输出的是分布参数均值、方差MSE 只约束均值完全没有约束方差训练出来的方差预测没有任何意义。DeepAR 的损失函数在代码里大概是这样的# deepar_loss.py import torch import torch.nn.functional as F def negative_gaussian_log_likelihood(mu: torch.Tensor, sigma: torch.Tensor, target: torch.Tensor) - torch.Tensor: 高斯分布假设下的负对数似然损失。 mu: 预测均值, shape (batch, pred_len, num_targets) sigma: 预测标准差, shape (batch, pred_len, num_targets) target: 真实值, shape (batch, pred_len, num_targets) sigma F.softplus(sigma) 1e-6 # 保证标准差为正且数值稳定 nll 0.5 * ((target - mu) ** 2) / (sigma ** 2) torch.log(sigma) return nll.mean()这里两个细节值得注意第一sigma经过softplus激活后再加一个极小值因为网络输出的原始值可以是任意实数而标准差必须是正数第二损失返回的是均值而不是求和这样在不同批次大小下损失值可比。我在实际项目里还会额外关注 PICP预测区间覆盖概率和 PINW预测区间平均宽度前者衡量 90% 预测区间是否真的覆盖了大约 90% 的真实值后者衡量区间是否足够窄、有没有实际使用价值。只用 NLL 一个指标没法判断预测分布是否校准。4.2 统一评估脚本怎么设计为了让四个模型公平对比评估脚本需要做三件事加载测试集、逐批次推理、计算统一指标。这个包的 evaluate 函数最关键的一点是对齐所有的输出格式代码示意如下# evaluate.py import numpy as np from sklearn.metrics import mean_squared_error, mean_absolute_error def evaluate_model(model, test_loader, scaler, pred_len12, num_samples100): model.eval() all_preds, all_trues [], [] with torch.no_grad(): for x, y in test_loader: pred model.predict(x, num_samplesnum_samples) # (batch, pred_len, num_targets) all_preds.append(pred.numpy()) all_trues.append(y.numpy()) # 合并所有批次后逆归一化 preds np.concatenate(all_preds, axis0) trues np.concatenate(all_trues, axis0) preds_inv scaler.inverse_transform(preds.reshape(-1, preds.shape[-1])).reshape(preds.shape) trues_inv scaler.inverse_transform(trues.reshape(-1, trues.shape[-1])).reshape(trues.shape) rmse np.sqrt(mean_squared_error(trues_inv.reshape(-1), preds_inv.reshape(-1))) mae mean_absolute_error(trues_inv.reshape(-1), preds_inv.reshape(-1)) return {RMSE: rmse, MAE: mae}需要解释的是model.predict(x, num_samplesnum_samples)这行。RNN、Transformer、Informer 的predict方法其实只是关闭 dropout 后的 forward 推理num_samples参数会被忽略但 DeepAR 的predict会执行自回归采样num_samples次生成多条轨迹最后返回这些轨迹的均值作为点预测值。统一接口在这里的好处体现得淋漓尽致——评估脚本完全不用感知模型差异。在预测步长pred_len的选择上要注意如果序列数据有明显的周期特征应该让pred_len至少覆盖一个完整周期比如日粒度数据 24 小时否则看指标会得出错误的结论。4.3 指标口径的统一评估脚本里还有一处我特别认可的地方逆归一化之后才计算 RMSE 和 MAE。这在点预测对比中问题不大因为 MSE 和 MAE 对线性变换的敏感性不同RMSE 是在原始量纲下算的更直观。但如果你在归一化后的数据上计算指标展示给业务方时还得再把误差还原回去中间容易出错。我一般只会在归一化域里监控训练损失最终评估一定在原始量纲下做。这里也要提到点预测和概率预测之间还缺少一个重要的评估维度概率预测的校准质量。这个包的评估脚本里没有直接算 PICP但我在实际使用中会自己加上因为对业务方来说预测 0.5MW 但区间是 ±3MW 和 区间是 ±0.3MW 的决策价值完全不同。5. 避坑PyTorch 时序模型最常见的五个翻车点5.1 数据泄露归一化放到了切分之前现象模型在测试集上的指标奇好无比RMSE 逼近零但一上真实数据就完全偏离。原因在切分数据集之前用全量数据fit了归一化 scaler训练集、验证集、测试集的均值和方差全部用了全量数据的统计量相当于把未来信息泄露给了模型。解决严格按本文 3.2 节的做法只在训练集上fit_transform验证集和测试集只调用transform。5.2 Future Leaking序列预测里把未来步的输入喂给了模型现象训练收敛很快loss 降到很低但推理阶段预测结果非常差。原因模型编码阶段或解码阶段的输入里混入了未来时间步的数据——比如用t1的实际值作为t1步的输入特征来预测t1的目标值这在训练时有真实数据可用所以看似合理但推理时未来的输入根本不存在。解决检查模型 forward 里的第二个输入序列是否都来自历史窗口加掩码时重点看右上角——把未来位置的注意力分数手动置为-inf再进 softmax。在这里还伴随一个架构陷阱很多人在训练时因为teacher forcing在解码阶段用真实值作为下一步输入测试时把它替换成上一步预测值训练/测试分布不一致误差一路累积导致预测结果发散。我的习惯是先在teacher_forcing_ratio0.5附近做实验模型健壮性有明显改善再降到 0.1。5.3 DeepAR 训练出现 NaN现象训练到某个 epoch 后 loss 突然变成 NaN之后无法恢复。原因负对数似然里做了log(sigma)sigma 经过 softplus 后还是可能趋近于 0注意这里不是等于 0而是过小导致除法溢出另外当输入数据有极端异常值时中间层的梯度容易爆炸。解决第一给 sigma 加下限softplus(sigma) 1e-6第二在归一化时对极端离群点用分位数裁剪例如把 0.1% 和 99.9% 分位数之外的数据直接拉回边界第三设置梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)除此之外可以把初始学习率降到 1e-4 做前两个 epoch 的 warmup。5.4 Informer 在短序列上比 Transformer 还慢现象序列长度在 200 步以内Informer 不仅没加速反而比标准 Transformer 慢。原因ProbSparse 注意力在长序列上才有加速优势短序列的稀疏采样和修正补偿计算反而成了额外开销数据结构太小、注意力矩阵无法充分展示稀疏性。解决序列长度小于 300 时直接用标准 Transformer 即可只有序列长度到 500 甚至上千时Informer 才值得切换。这是一个典型的模型选型看数据规模问题千万别只看某个模型被宣传得多么高效。5.5 WSL 环境下 PyTorch 训练速度异常现象同样的模型在 Linux 服务器上训练 10 分钟在 WSL 里跑要 40 分钟。原因WSL 里 PyTorch 的 CPU 版本在某些机器上被系统调度器打了折扣尤其在 AMD 7000 系列显卡上驱动、CUDA 版本、PyTorch 版本之间匹配不当就会拖慢训练很多人其实是在用 CPU 训练还不自知。解决先python -c import torch; print(torch.cuda.is_available())输出False就说明根本没启用 CUDA然后检查nvidia-smi里的驱动版本和 CUDA 版本是否匹配 PyTorch 的预编译 wheel 要求——不要用系统 Python 自带的 pip 装包用 conda 管理独立环境更稳。我常用的做法是conda create -n ts python3.10然后pip install torch --index-url https://download.pytorch.org/whl/cu118这类指定 CUDA 版本的安装命令。6. 进阶用滚动预测检验模型真实泛化能力固定切分的测试集评估只能说明模型在这段连续时间上表现好无法体现模型在真实上线场景下的表现。真实业务里模型是不断滚动预测的用当前时刻往前seq_len步的历史数据预测未来pred_len步时间窗口继续往前走再预测下一段。我拿到这套资源之后做的一件重要事情就是写了一个滚动预测验证脚本专门检验模型的累积误差和长期稳定性# rolling_evaluate.py import numpy as np def rolling_evaluate(model, full_data, scaler, seq_len48, pred_len12, step6): 滚动预测每次预测后窗口向前移动 step 步。 full_data: 原始多变量数据, shape (total_timesteps, num_features) model.eval() preds, trues [], [] start 0 while start seq_len pred_len len(full_data): x full_data[start : start seq_len] y_true full_data[start seq_len : start seq_len pred_len] # 归一化、转 tensor、加 batch 维度 x_scaled scaler.transform(x.reshape(-1, x.shape[-1])).reshape(1, seq_len, -1) x_tensor torch.FloatTensor(x_scaled) with torch.no_grad(): y_pred_scaled model.predict(x_tensor, num_samples100) # (1, pred_len, num_targets) y_pred scaler.inverse_transform(y_pred_scaled.numpy().reshape(-1, y_pred_scaled.shape[-1])).reshape(pred_len, -1) # 这里只保留第一个 step 步作为有效预测避免重叠部分重复计入 preds.append(y_pred[:step]) trues.append(y_true[:step]) start step preds np.concatenate(preds, axis0) trues np.concatenate(trues, axis0) # 按预测提前量分层计算误差 errors {} for h in range(step): rmse_h np.sqrt(np.mean((preds[h::step] - trues[h::step]) ** 2)) errors[fhorizon_{h1}] rmse_h return errors这段代码的逻辑是用一个循环让窗口在整段完整数据上滚动step控制每次滑动的步长。如果step pred_len相邻两次预测的区间会有重叠我只取每次预测的前step步计入误差避免重叠部分被反复计算导致误差被稀释。返回值是按预测提前量分层的 RMSE——horizon_1是第一步预测误差horizon_2是第二步预测误差以此类推。这个分层信息非常有用它能直观看出模型在预测越远的未来时误差恶化到什么程度如果horizon_1误差和horizon_12误差差距特别大说明模型对长期依赖的建模能力偏弱应该考虑调整seq_len或换用注意力机制更强的模型。我在实际项目里通过这个滚动验证发现过一个令人惊讶的现象某个 Transformer 模型固定测试集 RMSE 比 LSTM 低 20%但滚动预测 12 步之后误差反超 LSTM——原因就是它的长期预测会逐渐漂移累积误差比 LSTM 更严重。如果不做滚动验证这个隐患在项目上线前根本无法发现。滚动预测脚本定下来之后我每次拿到新数据集都会跑一遍完整的流程先看数据形状和缺失值再滑动切分、拟合 scaler、建模型、训练、固定评估、滚动评估——一条龙走完。这个习惯让我躲过不少坑尤其是数据泄露那个问题。如果你打算把这套资源应用到自己的项目里我建议你也把这个流程固化下来先跑几个小模型看趋势再上大模型精调。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑