PyTorch多变量时间序列预测:DeepAR、Informer、Transformer与RNN实战对比
简介:这份资源面向时间序列预测方向的研究者、算法工程师与高年级学生,聚焦用PyTorch复现并改进DeepAR、Informer、Transformer、RNN等经典模型,解决多变量时序分析与预测问题,覆盖金融、气象、工业等场景。压缩包共61个文件,约55.25MB,以49个Python实现文件为主,配套4个gz数据集、4张png结果图,以及txt说明、docx附赠文档、json与md说明,代码按data、models、exp、test等模块组织,便于按实验脚本逐项复现。已有95人学习下载。读者可拿到各模型的完整实现与训练脚本、概率预测与点预测的对比实验、多变量数据加载与特征工程工具,以及指标计算、概率预测可视化等辅助模块,适合在此基础上做二次开发与模型改进。
1. 从一份「模型全家桶」说起:多变量时间序列预测到底难在哪
电力负荷曲线、交通流量、工业设备振动、零售销量,这些数据有一个共同点:它们不是孤立的一列数,而是多个变量互相纠缠着随时间演化。你拿到的往往是一张[时间步, 变量数]的表,想预测未来一段时间的走势。单变量预测那套「一列历史推一列未来」的做法,在多变量场景下会立刻暴露问题——变量之间的相关性被丢掉了,而恰恰是这些相关性里藏着预测的线索。
这份标题里提到的方案,本质是把 DeepAR、Informer、Transformer、RNN 这几类经典模型放进同一个 PyTorch 工程里,用统一的数据接口和训练流程做多变量时间序列分析与预测。它解决的不是「哪个模型最强」这种问题,而是「我手上这批多变量数据,到底该用哪类模型、怎么快速横向对比」。适合已经会写 PyTorch 训练循环、但面对时间序列时不知道从哪下手的工程师,也适合想把 LSTM 时间序列预测 Python 那套老流程升级到注意力机制的人。下面按「先立住原理、再动手复现、最后讲坑」的顺序拆开讲。
2. 四类模型的分工:DeepAR、Informer、Transformer、RNN 各自解决什么
2.1 先搞清楚它们不是同一层的替代品
很多人一上来就问「Informer 和 Transformer 哪个准」,这个问题本身就问偏了。这几类模型的差异不在精度高低,而在它们对时间序列的建模假设不同。
RNN(含 LSTM、GRU)是自回归的逐点递推结构,隐状态沿时间轴传递,天然适合捕捉短期依赖和序列顺序。它的短板是长序列上梯度传播路径太长,且无法并行,训练慢。LSTM 时间序列预测 Python 生态里最成熟的方案就是它,代码简单、调参直观,作为基线非常合适。
Transformer 用自注意力一次性看到整个序列,任意两个时间步之间的依赖路径长度都是 1,长程依赖建模能力强,且可并行。但标准自注意力的计算复杂度是序列长度的平方,长序列上显存和耗时都会爆炸。
Informer 是针对长序列预测做的改进版,核心是 ProbSparse 稀疏注意力,把注意力复杂度从 O(L²) 降到 O(L log L),同时用蒸馏机制逐层压缩序列长度。它适合输入序列很长(比如上千步)的预测任务。
DeepAR 是另一条路线,它不直接输出点预测,而是输出概率分布的参数(比如高斯分布的均值和方差),用自回归方式逐点采样,天然给出预测区间。多变量场景下它把所有变量的历史一起喂进去,共享一个网络,适合需要不确定性量化的业务。
| 模型 | 建模方式 | 长序列能力 | 是否输出概率 | 并行训练 |
|---|---|---|---|---|
| RNN/LSTM | 逐点递推 | 弱 | 否 | 否 |
| Transformer | 全局自注意力 | 中 | 否 | 是 |
| Informer | 稀疏注意力+蒸馏 | 强 | 否 | 是 |
| DeepAR | 自回归+分布输出 | 中 | 是 | 否 |
2.2 统一数据接口是复现的前提
四类模型要放在一个工程里对比,第一件事不是写模型,而是把数据格式统一。多变量时间序列的常见组织方式是滑动窗口:给定历史长度context_length和预测长度pred_length,从原始序列上切出[context_length, num_vars]的输入和[pred_length, num_vars]的目标。
import torch from torch.utils.data import Dataset class MultiVarWindowDataset(Dataset): def __init__(self, data, context_length=168, pred_length=24): # data: [total_steps, num_vars] 的 float32 张量 self.data = torch.as_tensor(data, dtype=torch.float32) self.context_length = context_length self.pred_length = pred_length self.num_samples = len(self.data) - context_length - pred_length + 1 def __len__(self): return self.num_samples def __getitem__(self, idx): x = self.data[idx : idx + self.context_length] # 历史窗口 y = self.data[idx + self.context_length : idx + self.context_length + self.pred_length] # 预测目标 return x, y这段代码的关键在num_samples的计算:总长度减去窗口长度再减预测长度加一,少加这个一就会在最后一个样本上越界。context_length和pred_length是最需要根据业务调整的两个参数——电力负荷常用 168(一周小时数)预测 24,交通流量常用 96 预测 12。窗口切好后,RNN 类模型直接按时间维喂入,Transformer 和 Informer 需要额外做位置编码,DeepAR 则要在目标上做分布假设。
提示:多变量数据务必先做标准化,且标准化参数只能用训练集统计,验证和测试集复用训练集的均值和方差,否则会引入未来信息。
3. 用 PyTorch 把四类模型跑通:从环境到训练循环
3.1 环境搭建与依赖确认
PyTorch 环境搭建是绕不过去的第一步。CUDA 和 PyTorch 版本必须对齐,装错了要么用不上 GPU,要么直接报错。常见做法是用 conda 建独立环境,再按官方命令装对应 CUDA 版本的包。
conda create -n ts_pred python=3.10 -y conda activate ts_pred # 按本机 CUDA 版本选择,下面以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install numpy pandas scikit-learn matplotlib装完先验证 GPU 是否可用,这一步别省:
import torch print(torch.__version__) print(torch.cuda.is_available()) # 必须为 True 才走 GPU print(torch.cuda.get_device_name(0)) # 确认识别到正确显卡如果cuda.is_available()返回 False,八成是 CUDA 版本和 PyTorch 包不匹配,或者驱动太旧。Ubuntu 安装 PyTorch 和 WSL 环境下排查思路一样:先看nvidia-smi输出的 CUDA 版本,再对照 PyTorch 官网的版本矩阵重装。Anaconda 配置 PyTorch 环境时,别在 base 环境里装,污染了很难清理。
3.2 RNN 基线:最短路径先跑出一个能看的数
任何对比都要有基线。LSTM 结构简单、收敛稳定,先用它把整条链路跑通,确认数据管道、损失函数、评估指标都没问题,再去上复杂模型。
import torch.nn as nn class LSTMForecaster(nn.Module): def __init__(self, num_vars, hidden_size=64, num_layers=2, pred_length=24): super().__init__() self.lstm = nn.LSTM( input_size=num_vars, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, ) self.head = nn.Linear(hidden_size, num_vars * pred_length) self.pred_length = pred_length self.num_vars = num_vars def forward(self, x): # x: [batch, context_length, num_vars] out, _ = self.lstm(x) last = out[:, -1, :] # 取最后时间步的隐状态 y = self.head(last) # [batch, num_vars * pred_length] return y.view(-1, self.pred_length, self.num_vars)hidden_size从 64 起步,num_layers两层通常够用,再深容易过拟合小数据集。head一次性输出整个预测窗口,比逐点递推快很多,代价是丢掉了预测步之间的自回归依赖。如果业务对多步一致性要求高,可以改成逐点输出再滚动,但训练会慢一个量级。
3.3 Transformer 与 Informer:位置编码和注意力掩码是重点
Transformer 用在时间序列上,位置编码不能照搬 NLP 那套固定正弦编码就完事,因为时间序列的周期性和绝对位置都重要。常见做法是把位置编码和可学习的时间特征(小时、星期、月份)拼接。
class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000): super().__init__() pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len).unsqueeze(1).float() div = torch.exp(torch.arange(0, d_model, 2).float() * (-torch.log(torch.tensor(10000.0)) / d_model)) pe[:, 0::2] = torch.sin(position * div) pe[:, 1::2] = torch.cos(position * div) self.register_buffer('pe', pe.unsqueeze(0)) # [1, max_len, d_model] def forward(self, x): return x + self.pe[:, :x.size(1)]register_buffer让pe跟着模型走 GPU 但不参与梯度更新,这是标准写法。Informer 的 ProbSparse 注意力实现要复杂得多,核心是只对「查询稀疏性」高的那部分 query 做注意力计算,其余用均值填充。自己手写容易出错,建议先用标准 Transformer 跑通,再替换注意力模块做对比。
Transformer 预测正弦数据是验证实现正确性的经典手段:如果模型连正弦都拟合不了,说明位置编码或掩码写错了。训练时注意解码器输入要右移一位,且用因果掩码防止看到未来。
3.4 DeepAR:把点预测换成分布预测
DeepAR 的输出不是一条线,而是一个分布的参数。以高斯为例,网络输出均值和标准差,损失用负对数似然。
class DeepARHead(nn.Module): def __init__(self, hidden_size, num_vars): super().__init__() self.mu = nn.Linear(hidden_size, num_vars) self.sigma = nn.Linear(hidden_size, num_vars) def forward(self, h): mu = self.mu(h) sigma = torch.nn.functional.softplus(self.sigma(h)) + 1e-6 # 保证正数 return mu, sigma def gaussian_nll(y, mu, sigma): # y, mu, sigma: [batch, pred_length, num_vars] return (torch.log(sigma) + 0.5 * torch.log(2 * torch.tensor(3.14159)) + 0.5 * ((y - mu) / sigma) ** 2).mean()softplus加一个小常数是为了防止标准差趋近 0 导致数值爆炸,这个1e-6是血泪经验,去掉之后训练到中期很容易出 NaN。DeepAR 训练时每个时间步都算一次损失,推理时从分布里采样再喂回下一步,所以推理速度比一次性输出的模型慢。
4. 多变量预测的避坑清单:五个真实翻车现场
4.1 现象:验证损失比训练损失低很多
原因:数据泄漏。最常见的是标准化用了全量数据的均值方差,或者滑动窗口切分时训练集和验证集有重叠。时间序列不能随机划分,必须按时间先后切。
解决:先切分再标准化,训练集算出的均值方差存下来给验证测试用。窗口切分时在训练集和验证集之间留出至少一个context_length的间隔。
4.2 现象:模型预测出来是一条直线
原因:多变量里某些变量量纲差异太大,比如一个变量范围是 0 到 1,另一个是 0 到 10000,梯度被大量纲变量主导,小量纲变量学不动。
解决:每个变量单独标准化,或者用 RobustScaler 抗异常值。检查输入张量的每个通道统计量,确认没有某个通道方差异常大。
4.3 现象:Informer 训练显存爆掉
原因:序列长度设得太长,或者 batch size 没跟着调。稀疏注意力虽然降了复杂度,但常数项不小,序列上千步时显存占用依然可观。
解决:先把context_length降到 336 或 512 试跑,确认能跑通再往上加。开启混合精度训练,torch.cuda.amp能省不少显存。
4.4 现象:Transformer 训练不收敛,损失震荡
原因:学习率太大,或者没用 warmup。Transformer 对学习率敏感,直接上 1e-3 很容易发散。
解决:用 AdamW,学习率从 1e-4 起步,加线性 warmup 前 10% 步数。梯度裁剪设 1.0,防止梯度爆炸。
4.5 现象:DeepAR 推理结果每次都不一样
原因:这是设计如此,DeepAR 是概率模型,每次从分布采样结果都不同。
解决:需要确定性输出时,取多次采样的均值或中位数。评估时用分位数损失或 CRPS,不要用 MSE 直接比,否则对概率模型不公平。
5. 进阶技巧:怎么判断一个模型值不值得上生产
跑通不等于能用。我一般会做三件事来判断一个时间序列模型是否值得投入生产。
第一,做滚动预测验证。不要只在一个固定测试集上算一次指标,而是模拟真实场景:用前 N 步预测后 M 步,然后窗口往后滑,重复几十次,看指标的均值和方差。方差大的模型上线后表现会很不稳定。
第二,和朴素基线比。季节性朴素法(拿去年同期的值当预测)在很多业务上强得离谱。如果你的 Transformer 打不过它,说明模型没学到真东西,先回去查数据和特征。
第三,看预测区间的覆盖率。如果业务需要不确定性量化,DeepAR 这类概率模型要检查 80% 和 95% 预测区间的实际覆盖率是否接近名义值。覆盖率严重偏低说明分布假设不对,可能要换负二项或 Student-t。
| 验证项 | 合格标准 | 不达标先查 |
|---|---|---|
| 滚动预测指标方差 | 相对均值小于 20% | 数据平稳性、窗口长度 |
| 对比朴素基线 | 至少提升 10% | 特征工程、模型容量 |
| 预测区间覆盖率 | 与名义值偏差小于 5% | 分布假设、损失函数 |
最后说个习惯:我每次换模型前都会把基线结果和随机种子固定下来存成文件,不然调了两周回头发现还不如第一版,连后悔药都没得吃。多变量时间序列预测没有银弹,DeepAR、Informer、Transformer、RNN 各有各的适用面,把数据管道和评估流程做扎实,比追新模型重要得多。希望帮到你。
本文还有配套的精品资源,点击获取