ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Informer:长序列预测的高效Transformer改造方案

Informer:长序列预测的高效Transformer改造方案 如果你把 Transformer 直接搬到长时间序列预测任务上大概率会撞上一堵墙模型还没收敛显存先爆了。我两年前第一次在电力负荷预测场景里做实验时把标准 Transformer 的 encoder-decoder 结构原封不动搬过来序列长度设到 1536结果一张 24GB 的卡直接 OOM。后来换到 Informer同样序列长度、同样的 batch size显存占用直接降了一个数量级预测效果反而更好。这篇文章就围绕 Informer 这套面向长时间序列预测的高效 Transformer 改造方案展开把它的核心思路、工程复现、实际效果和坑都讲透。无论你是刚接触时间序列预测还是已经在用 Transformer 系列模型做项目都应该能从里面找到可以直接拿走的东西。熟悉 Transformer 的人都知道自注意力机制强大但强是有代价的计算复杂度随序列长度平方增长序列一长显存和时间都不够看。Informer 做的不是小修小补而是从注意力计算、特征蒸馏、解码器设计三个层面一起改把长序列预测场景下的资源消耗降下来精度还比原来更稳。下面的内容我会先拆解长序列预测为什么难再讲 Informer 的具体设计最后用工程视角带你把模型跑起来并分享一些官方文档里不会写的排错经验。1. 长序列预测为什么成了 Transformer 的软肋1.1 时间序列预测到底难在哪时间序列预测的任务可以这样理解给你过去一大段观测值比如 96 个时刻的数据让你预测未来 24、48 甚至 96 个时刻的数值。这个“预测长度”和“输入长度”一旦同时变大问题性质就变了。短期预测时当前值和邻域值高度相关很多简单模型就能拿到不错的结果但长期预测要求模型抓住趋势、周期、突变和噪声之间的复杂耦合任何一个环节掉链子预测误差都会随步长累积。看起来 Transformer 很适合做这件事因为它能建模任意两个位置的长距离依赖卷积和 RNN 很难比。实际跑起来却是另一回事。标准 Transformer 的自注意力层需要对输入序列中的每个时刻都和所有其他时刻计算相关性序列长度从 96 涨到 192、384计算量和显存是平方级膨胀。我在实际项目中感受过这种差距输入长度从 512 提升到 1024训练一个 epoch 的时间翻了不止 4 倍这在调参阶段非常痛苦。还有一个常被忽略的点标准 Transformer 的解码器是自回归的一个时刻一个时刻往外吐预测值。预测 96 个点就要循环 96 次前一步的预测误差还会被当成输入进入下一步误差就像滚雪球一样越滚越大。这两个问题叠加起来标准 Transformer 在长序列面前既“算不动”又“稳不住”。1.2 O(L²) 复杂度L 一长一切优雅都扛不住先看自注意力层的计算过程。输入序列长度为 L每个 token 会被映射成 query、key、value 三个向量注意力分数矩阵就是所有 query 和所有 key 的点积结果形状是 L×L。序列长度 1000 时这个矩阵是 100 万个元素长度 10000 时就是 1 亿个元素。显存和计算量都在平方级飙升这是硬伤。很多人会说我可以把序列分块、可以稀疏化注意力但真正落地时难点在于分块会损失全局信息简单稀疏化又不知道把注意力放在哪里。Informer 的切入点很有意思它先从统计角度证明了一件事注意力分数矩阵并不是所有元素都重要真正起主导作用的只是少数几个 query剩下大部分 query 只要看局部信息就够了。这个观察是后来所有优化的基础。1.3 自回归解码的误差累积是另一个隐形杀手即使你解决了计算复杂度标准 Transformer 的解码方式在长序列预测里也不合适。自回归解码时模型每一步都基于前一步的预测值来预测下一步。短期一两步还好长期来看任何一步的偏差都会被放大。我做过一个对比实验用标准 Transformer 预测未来 168 个点前 24 个点还算正常越往后越平滑到接近序列末尾时几乎变成一条直线。这不是模型没训练好而是误差累积把高频信息抹掉了。Informer 的生成式解码器正是针对这个问题设计的。它不一步步预测而是用一次前向传播直接输出整段预测序列从根本上绕开了“一步错、步步错”的循环。这一点我在后面的章节会详细拆。2. 从稀疏性切入Informer 对注意力的改造逻辑2.1 注意力矩阵其实存在明显的“头尾效应”如果你把训练好的 Transformer 某层注意力矩阵可视化会发现一个规律少数位置的 query 会同时关注很多 key而大多数位置的 query 只会关注附近少数几个 key。换句话说注意力分布呈长尾状少数点贡献了大部分信息量。这有点像公司里真正拍板的人永远是少数其他人虽然整天开会但对最终决策的影响有限。Informer 的作者把这种性质称作“稀疏性”。如果能提前判断出哪些 query 是“拍板的人”只让它们做完整的注意力计算其余 query 用更便宜的方式处理那整个计算量就会大幅下降。难点在于怎么在计算完整注意力之前识别出这些重要 queryInformer 用了一个巧妙且高效的近似指标。2.2 ProbSparse Self-Attention 的稀疏度量方法要识别重要 queryInformer 定义了一个稀疏度度量 M(qi, K)。它的出发点是比较一个 query 的注意力分布和“均匀分布”的差距。如果这个 query 对应的注意力分数特别不均匀说明它的选择性很强携带的信息量更大如果所有 key 的得分都差不多说明这个 query 很“平庸”算不算它都行。严谨一点的表达是用 KL 散度衡量 query 的注意力分布与均匀分布的差异差异越大query 越重要。但这个 KL 散度直接算也要遍历所有 key代价还是高。Informer 做了一个近似用每个 query 与所有 key 点积的最大值减去所有点积的均值当作稀疏度打分。打分高的 query 进注意力计算打分低的直接跳过。实际操作时还有一步工程简化不是对全部 L 个 query 都打分而是随机采样一部分 query 来估计分数再从中选出 Top-u 个。这里的 u 通常取一个与 ln L 成正比的数比如factor * ln L。这样整体复杂度就从 O(L²) 降到了 O(L ln L)。我在第一次看到这个设计时觉得有点冒险但实验证明随机采样加 Top-u 已经很稳因为大量平庸 query 即使被采样到分数也不足以挤进 Top-u。2.3 为什么复杂度能压到 O(L ln L)一句话概括 ProbSparse Self-Attention只让少数重要 query 计算完整的注意力其他 query 不参与注意力加权。但要注意这些“其他 query”并不是直接丢弃而是通过残差连接保留原始特征。这一点很重要刚开始我实现时直接把未被选中的 query 对应输出置零模型训练直接崩了。原因很简单自注意力输出后面还有残差和层归一化未选中的 query 如果不产生有效输出信息就断层了。Informer 的代码实现里被选中的 query 正常算注意力得到新的上下文向量未被选中的 query 虽然没有新的上下文向量但残差连接会帮它们保留上一层特征后续层照样能继续处理。这相当于在不牺牲全局信息的前提下把一个完整的 L×L 注意力计算替换成只对 u 个重要 query 做 L 维注意力计算分子小了很多分母也从 O(L²) 降到了 O(L ln L)。如果你拿 96 个输入长度来跑这个优化看着不痛不痒可一旦输入长度到 960 甚至 1920区别就非常明显了。我在 1920 长度下对比过标准 Transformer 和 Informer前者 GPU 占用直接爆掉后者还能正常训练这就是稀疏化带来的实际价值。3. 高效架构的另外两块拼图蒸馏与生成式解码3.1 Self-Attention Distilling让特征长度逐层减半ProbSparse Self-Attention 把单层的计算量降下来了但如果你照搬 Transformer 的 encoder 结构层数一多显存和计算量依然会叠加起来。Informer 做了另一个很“像卷积网络”的设计在相邻两层之间加入蒸馏操作。蒸馏的思路是上一层经过注意力计算后的特征图先做一个一维卷积再通过 ELU 激活最后用最大值池化把序列长度缩减一半。也就是说越往高层走序列长度越短模型可以用更少的计算量去提炼更全局的特征。这个设计和图像分类里的金字塔结构有异曲同工之处。具体的蒸馏写法很简单大致是下面这个流程def distilling(x): x Conv1d(x, kernel_size3, stride1, padding1) x ELU(x) x MaxPool1d(kernel_size3, stride2, padding1) return x连续做几层之后本来长度为 L 的特征序列会变成 L/2、L/4、L/8。长度越短后续注意力计算就越便宜。这里有个经验蒸馏的层数不能过多否则会丢掉太细粒度的局部信息。Informer 原文里 encoder 通常用三层蒸馏后长度是原来的八分之一这样既保证效率又不至于损失太多细节。我在做工业传感器数据时试过四层蒸馏效果反而变差所以层数设置需要跟着数据复杂度走不能盲目加深。3.2 生成式 Decoder一次前向输出整段预测Informer 的另一个关键改动在 decoder。它没有沿用标准 Transformer 的自回归解码而是设计成一次前向就能输出整段预测。具体做法是解码器的输入由两部分拼接而成——一段从 encoder 输入尾部截取的“start token”序列以及一段长度等于预测长度的“占位符”。占位符在训练时可以理解为一组全零向量模型要在 decoder 内部通过 masked multi-head attention 逐步生成真实预测。由于 start token 是真实历史数据解码器在初期就有了一个稳定的“锚点”不需要从零开始生成这会显著降低预测的不确定性。同时masked multi-head attention 保持了自回归训练的特性但在推理时因为占位符长度是固定的模型可以在一次前向中输出全部预测结果不用再循环几百次。要注意的是start token 的截取长度通常是输入序列的一部分比如取输入末尾的一小段而不是把整个输入都塞进 decoder。因为 decoder 的任务是“续写”它只需要最近一段上下文就足够了。把太多历史信息塞进去反而会增加计算量也容易让 decoder 过度依赖旧信息。3.3 三个优化叠加后的实际效果ProbSparse Self-Attention 管计算效率蒸馏管特征浓缩生成式解码器管预测速度和精度。三个优化叠加起来Informer 在长序列预测场景下的优势就很明显了。我自己复现下来的体感是在电力和气象这类有明显周期性的数据集上Informer 的预测误差相比标准 Transformer 通常能下降 10% 到 25%推理速度上由于不需要自回归循环预测长度越长优势越大。比如预测未来 168 个点标准 Transformer 要循环 168 次Informer 一次前向就完了推理时间可能差一个数量级。这说明“比 Transformer 更有效”不是一个营销说法而是从计算复杂度和解码方式上都能得到解释的实实在在的改进。当然Informer 也不是在所有数据集上都碾压其他模型。如果序列非常短、预测长度只有个位数Informer 的很多优化手段体现不出优势甚至因为结构复杂而显得“杀鸡用牛刀”。它的适用场景一定得是“长序列”和“长预测长度”同时成立。4. 复现一个能跑的 Informer数据集、代码与关键超参4.1 从数据准备到滑动窗口先讲数据集。时间序列预测领域常用的公开数据集有 ETTh、ETTm、Electricity、Weather 这些。ETT 是电力变压器温度数据周期性强做长序列预测实验很合适。下载后你会看到这种格式每行是一个时间点的多个特征第一列是时间戳后面是油温、负载等数值。数据预处理的核心是滑动窗口。比如你输入长度设 96预测长度设 48那就从原始时间序列里切出长度为 9648144 的窗口前 96 个点做输入后 48 个点做标签。每滑动一步生成一个样本窗口之间重叠很正常。这里我要强调切完窗口后训练集、验证集、测试集必须按时间顺序切分不能随机打乱否则测试集里会出现训练阶段已经见过的未来信息指标会虚高。我一般按 7:1:2 或者 6:2:2 的比例按时间轴切成三段。切完之后再做归一化归一化参数只能在训练集上计算验证集和测试集复用训练集的均值和方差。很多人在这一步图省事对整个数据集一次性归一化这在时间序列预测里是大忌相当于把未来信息泄漏进去。4.2 模型搭建的骨架代码Informer 的完整代码结构包含 encoder、decoder、ProbSparse attention、蒸馏层等。我这里只演示最核心的 ProbSparse Attention 思路帮助你理解架构完整代码可以直接参考官方 PyTorch 仓库。import math import torch import numpy as np def prob_sparse_attention(q, k, v, factor5): B, Lq, Lk, d_k q.shape[0], q.shape[1], k.shape[1], q.shape[-1] u int(factor * np.ceil(np.log(Lk)).item()) u min(u, Lq) # 随机采样一部分 query用于评估稀疏度 q_sample_idx torch.randperm(Lq, deviceq.device)[:u] q_sample q[:, q_sample_idx, :] # 计算稀疏度max - mean scores_sample torch.matmul(q_sample, k.transpose(-2, -1)) / math.sqrt(d_k) m scores_sample.max(dim-1).values - scores_sample.mean(dim-1) # 选出 Top-u 个重要 query _, topk_idx torch.topk(m, u, dim-1) q_selected torch.gather( q, 1, topk_idx.unsqueeze(-1).expand(-1, -1, d_k) ) # 对被选中的 query 计算完整注意力 scores torch.matmul(q_selected, k.transpose(-2, -1)) / math.sqrt(d_k) attn torch.softmax(scores, dim-1) out torch.matmul(attn, v) return out, topk_idx再次提醒这里的实现只是为了展示原理真正的官方代码还有多头拆分、mask、残差连接等逻辑。如果你自己改代码千万别丢掉残差否则模型大概率不收敛。Decoder 部分的核心是把 start token 和占位符拼接起来然后进入 masked self-attention。训练时 start token 是真实历史片段推理时同样是从 encoder 输入序列尾部截取这一点必须保持一致否则训练和推理会出现严重的分布偏差。4.3 训练和评估时最容易忽视的设置下面是我在多个数据集上调试后觉得比较稳的一组基础超参适合作为起始点超参数建议值说明d_model512特征维度数据量小可以降到 256n_heads8多头注意力的头数e_layers3encoder 层数d_layers2decoder 层数factor5ProbSparse 采样系数控制重要 query 数量dropout0.05防止过拟合batch_size32显存不足降到 16learning_rate0.0001Adam 默认即可建议配合 warmupepochs6~10时间序列任务通常不需要训太多轮训练时有两个细节值得注意。一是学习率 warmup。Informer 结构复杂直接用大学习率容易在早期震荡我习惯前几个 epoch 做线性 warmup再衰减到比较小的值。二是早停机制。时间序列模型过拟合后验证集指标可能不升反降而且波动很大最好监控验证集 MSE连续 3 个 epoch 不下降就保存最优模型。评估时主流指标还是 MSE 和 MAE计算时要把归一化后的预测值还原到原始尺度否则看到的数字没有物理意义。如果你做的是多步预测建议把不同预测步长的误差分开看。我曾经遇到整体 MSE 还行、但最后 20 个预测点几乎全偏的情况分开看才定位到问题。5. 我用 Informer 踩过的几个坑5.1 数据划分不对指标再好看也是假的我最早跑实验时为了省事把整个数据集切成窗口后直接train_test_split随机划分结果测试集指标好得离谱。后来发现原因同一个时间序列切出来的相邻窗口高度重叠随机划分后训练集里包含了大量“未来窗口”的信息模型等于提前偷看了答案。正确做法一定是在切窗口之前先把原始时间序列按时间顺序切成 train / val / test 三段然后再分别做滑动窗口。窗口之间可以有重叠但绝对不能跨越数据集边界。这个问题尤其隐蔽因为整体误差看起来不大但如果把预测曲线和真实曲线画在一起你会发现测试集前段拟合得异常好这就是泄漏的信号。5.2 归一化参数必须锁死在训练集时间序列的分布会漂移不同时间段的均值方差差别很大。如果你用全量数据的均值和方差做归一化相当于让模型知道了未来的分布特征。我在一个工业数据集上测试过这种“全量归一化”的做法能把验证集误差降低不少但一旦把模型部署到未来新数据上误差立刻反弹因为真实场景里你根本拿不到未来的统计量。正确的流程是只对训练集计算 mean 和 std然后用这个 mean/std 去变换验证集和测试集。预测完成后用同一组 mean/std 做反归一化。这看起来只是一个顺序问题但对模型的真实泛化能力影响非常大。5.3 解码器输入和推理方式要保持一致Informer 的 decoder 训练时用的是“start token 来自真实历史片段 占位符”的输入结构。推理时也必须用同样的方式构造输入。如果推理时把 start token 换成了模型自己的预测值或者把占位符換成别的值整个 decoder 的输出分布都会偏移。另一个容易踩的坑和蒸馏层有关。如果输入序列长度不能被 2 的蒸馏次数整除经过多层 MaxPool 后特征长度会变得很尴尬可能出现维度不匹配。官方代码里通常要求序列长度满足一定条件比如是 2 的整数次幂的倍数。我在自定义数据集上设输入长度 100 时就遇到过这个问题后来改成 96 或 128 就顺了。建议输入长度选 96、192、384 这类能整除 64 的数字。5.4 未选中 query 的残差连接不能想当然我前面已经提过ProbSparse Attention 只计算少数重要 query但这绝不意味着未选中的 query 可以被置零。如果你在看官方源码时发现未选中 query 在注意力矩阵里是 0不要惊慌那是为了配合残差连接和后续层归一化。我自己动手实现时曾想“优化”一步把未选中 query 的行直接置零以减少卷积计算量结果模型输出曲线全变成锯齿状训练 loss 高得离谱。改回残差设计后一切正常。这一点想表达的是Informer 的每一个设计都是配套的改一个地方往往需要连带考虑后续模块的输入分布。尤其是从论文到代码不要只看公式要顺着数据流把每一层的输入输出 shape 和对齐关系理清楚。6. 什么场景该用 Informer什么场景可以再想想6.1 Informer 的适用范围和边界Informer 最适合的任务是输入历史和输出未来都比较长且数据有明显的周期性或趋势性比如电力负荷、交通流量、天气温度、云资源监控指标。这类场景下Informer 的长序列建模能力和高效解码优势能充分发挥。但在两类场景下我会谨慎使用。一类是输入序列很短比如只有 10 到 20 个点Informer 的稀疏注意力、蒸馏这些设计发挥不出价值传统方法或轻量模型往往更划算。另一类是数据极度非平稳且样本量很小Transformer 系列都需要大量数据喂样本太少时反而不如简单模型稳。这时候可以先用 ETS、Prophet 这类经典方法跑一版基线差距不大就尽量别上复杂模型。对比其他 Transformer 改进模型Informer 的主要优势集中在“时间和空间效率”。Reformer 用 LSH 稀疏注意力也能降复杂度但实现复杂实际加速效果因数据而异LogTrans 用 LogSparse 注意力在长依赖建模上不如 Informer 灵活。如果你需要在生产环境做一个参数可控、效果稳定的长序列预测基准Informer 是很合适的起点。6.2 和后续模型如何衔接一次升级/替换的思考从 Informer 往后时间序列预测领域又陆续出现了不少新模型比如 Autoformer、FEDformer、PatchTST。它们各有侧重Autoformer 用自相关机制强化周期依赖FEDformer 在频域做稀疏表示PatchTST 把多变量序列分割成 patch 再按 Transformer 方式处理。我的建议是不要因为新模型出来了就急着全部推翻。Informer 的代码结构清晰和标准 Transformer 的差异点很明确适合作为理解后续模型的基础。你可以在 Informer 的框架上做两件事一是把它的 ProbSparse Attention 替换成 Autoformer 的自相关模块做对比二是把数据预处理改成 patch 方式观察 PatchTST 里 patch 长度对效果的影响。这样不仅能加深理解也能帮你更理性地判断新模型在你的数据上是否真的值得迁移。最后分享一个实操技巧在跑任何长序列预测模型之前先算一次数据集的周期长度。电力数据往往有 24 小时周期交通数据有一周周期。Informer 的输入长度尽量覆盖 1 到 2 个完整周期这样模型才能看到足够的周期上下文。我见过太多人直接把输入长度设成 96但实际上数据的自然周期是 168模型连一个完整周期都没看全怎么可能预测得好这个小细节往往比换模型更影响最终效果。
返回列表