Informer实战:ProbSparse自注意力机制与长序列预测调参指南
发布时间:2026/10/1 12:44:40来源:尧图网络
简介Informer模型实战案例资源包面向时间序列预测与长序列建模的学习者及开发者基于Informer2020开源实现完整演示ProbSparse自注意力机制与自注意力蒸馏两大核心技术并配有参数讲解与可运行代码。压缩包共64个文件以17个Python源码、17个numpy数据结果、16个pyc缓存、2个pth模型权重及csv数据集等为主整体约115.95MB目录涵盖models、exp、utils等模块结构清晰便于对照学习。目前已有2862人参与学习下载。资源内置ETTh1电力数据集、训练好的checkpoint与多种预测长度如pl4、pl24的预测及真实值npy文件可直接运行main_informer.py复现实验并通过metrics.npy查看MSE等指标适合想深入理解Informer原理并上手实践的读者。1. Informer 实战到底在解决什么长序列预测与 ProbSparse 自注意力机制的出场理由做长时间序列预测的人大多经历过这种尴尬用标准 Transformer 预测电力负荷或气温序列长度一拉到 1000 以上显存先撑不住接着训练慢到让人怀疑人生。Informer 模型实战案例里最核心的改动就是用 ProbSparse 自注意力机制替代标准自注意力把时间复杂度和显存占用从 O(L²) 压到 O(L log L)。下面我会从 ProbSparse 的原理出发用公开数据集和可以直接运行的代码把数据怎么切、参数怎么调、坑在哪讲清楚。这套方案适合已经用过 Transformer 做时序预测、但被长序列计算量卡住的从业者也适合想深入理解稀疏注意力的同学。2. ProbSparse 自注意力机制拆解从稀疏性假设到 KL 散度筛选2.1 标准自注意力为什么在长序列上扛不住标准自注意力先让每个位置的 query 与所有 key 做点积得到一个 L×L 的注意力矩阵再与 value 加权求和。这一步的时间复杂度是 O(L²)显存也要存下 L² 个分数。L 等于 96 的时候没感觉L 到 1000 的时候矩阵就是 100 万个数L 到 10000 就是 1 亿个。在长时序预测里L 往往不是原始序列长度本身而是多个时间窗口拼接后的长度比如把 720 步的历史和 720 步的未来拼在一起这个平方复杂度很快就变成瓶颈。另一个问题是效率。很多实验证明在时序数据里标准注意力的分布其实是稀疏的一小部分 query 的注意力集中在少数几个 key 上其余大部分 query 的注意力分布接近均匀计算它们完整点积几乎没有信息增益。Informer 的 ProbSparse 自注意力机制就是从这里下刀只保留那些“注意力分布足够尖锐”的 query 参与完整计算其余 query 用 value 的均值向量填充从而在保证精度的前提下大幅剪枝。2.2 查询稀疏度度量怎么选出“活跃”的 Q论文里用来判断 query 是否活跃的指标是 KL 散度。对于第 i 个 query q_i它与所有 key 的注意力分布 p_i 可以写成 softmax(q_i·K^T/√d)。同时假设注意力分布近似均匀分布KL 散度可以衡量 p_i 和均匀分布之间的距离。距离越大说明这个 query 越有可能只关注少数关键 key也就是越“活跃”。实际计算时不需要遍历所有 key而是先从 K 中随机采样一部分 key 来估算每个 query 的稀疏度得分。下面是一个简化版的计算逻辑帮助你理解内部流程import torch import torch.nn.functional as F def prob_sparse_scores(q, k_sample): # q: [batch, heads, L, d] # k_sample: [batch, heads, L_sample, d] scale q.shape[-1] ** -0.5 # 每个 query 与采样 key 做点积得到 L x L_sample 的分数 scores torch.matmul(q, k_sample.transpose(-2, -1)) * scale # 用 logsumexp 计算 ln(sum(exp(scores))) log_sum_exp scores.logsumexp(dim-1) # 用 mean 近似均匀分布下的期望 mean_scores scores.mean(dim-1) # 稀疏度得分两者之差 m log_sum_exp - mean_scores return m这段代码里scores是每个 query 与采样 key 的点积结果log_sum_exp对应 softmax 分母的对数mean_scores是点积的均值。两者相减得到的m越大说明该 query 的注意力越集中。筛选时通常按m排序只保留前 u 个 queryu factor * ceil(sqrt(L))。这里的factor就是 Informer 参数里最重要的稀疏因子后面参数章会展开讲。实际仓库实现里还会对 K 和 V 做采样避免为了估值再引入 O(L²) 的额外开销。2.3 稀疏采样后的注意力计算与多头融合选出活跃 query 之后ProbSparse 注意力并不会对整行都稀里糊涂地跳过。具体做法是对选出的 query 计算完整的 softmax 注意力权重并与所有 key 对应的 value 加权对于没被选中的 query不再计算点积而是把整条序列 value 的均值向量直接作为它们的输出。这样输出矩阵的维度依然是 [batch, heads, L, d]后续残差和层归一化可以原样使用。多头注意力里每个头独立做一次采样和筛选。这带来一个额外好处不同头可以关注不同的时间尺度和不同的关键点。比如电力负荷预测里一个头可能关注最近 12 小时的趋势另一个头关注每天同一时刻的周期点。由于每个头的采样是独立随机的这种多样性不需要额外设计就能出现。用代码表达 ProbSparse 注意力核心流程大概是下面这样def prob_sparse_attention(q, k, v, factor5, sampling_times5): batch, heads, L, d q.shape L_sample int(factor * (L ** 0.5)) # 1. 从 K 中随机采样 L_sample 个 key 用于估计稀疏度 k_sampled k[:, :, torch.randint(0, L, (sampling_times, L_sample)), :] # 2. 估计每个 query 的稀疏度得分 scores prob_sparse_scores(q, k_sampled) # 3. 选出前 u 个 query 的索引 top_u min(int(factor * (L ** 0.5)), L) top_indices scores.topk(top_u, dim-1).indices # 4. 只对选中的 query 计算完整注意力 q_selected torch.gather(q, -2, top_indices.unsqueeze(-1).expand(-1, -1, -1, d)) attn_scores torch.matmul(q_selected, k.transpose(-2, -1)) * (d ** -0.5) attn_weights F.softmax(attn_scores, dim-1) out_selected torch.matmul(attn_weights, v) # 5. 未选中的 query 用 V 的均值填充 v_mean v.mean(dim-2, keepdimTrue) # [batch, heads, 1, d] out_full v_mean.expand(batch, heads, L, d).clone() out_full.scatter_(-2, top_indices.unsqueeze(-1).expand(-1, -1, -1, d), out_selected) return out_full这段示例把筛选和计算分成了 5 步。注意第 1 步里sampling_times是采样批次数实际仓库实现会循环采样多次并取最大得分来提升估计鲁棒性第 4 步top_indices是每头独立的索引所以 gather 时需要展开到 embedding 维第 5 步用v_mean填充未选中位置这是保持序列长度不变的关键。真正生产环境里会合并采样和 topk 操作避免重复计算。2.4 与其他稀疏注意力方案的对比与选型理由除了 ProbSparse稀疏注意力还有几条常见技术路线。LogSparse 只让每个位置关注它以 2 为底的对数间隔的历史位置适合有明确局部依赖的序列但对周期跨度很大的长序列覆盖不足。Longformer 采用滑动窗口加全局 token适合文本但在时序上要让模型自己学会去哪找关键信息收敛偏慢。Reformer 用局部敏感哈希把相近的 key 分桶计算量低但分桶边界对周期性敏感容易把强相关的时间点拆进不同桶。ProbSparse 的优势在于不预设位置结构纯粹靠数据驱动选出关键 query。对突发传感器读数、价格跳变这类非周期事件它比基于固定模式的稀疏方案更稳。代价是筛选过程需要采样估计有一定随机性所以必须固定随机种子才能稳定复现。选择用不用 ProbSparse我的经验是序列长度超过 512且明显存在“少量关键时刻决定全局”的场景收益最大如果序列本身就短比如 96 以内直接用标准注意力更省心否则稀疏估计反而引入噪声。3. 数据集准备与代码复现跑通 Informer 的最小闭环3.1 数据集选择ETTh1、天气、用电量三个公开数据集的差异Informer 论文里最常用的是 ETT电力变压器温度数据集其中 ETTh1 是小时级记录包含油温以及多种电力负荷特征。它序列长、周期清晰适合验证长序列预测能力。天气数据集Weather包含 14 个气象变量采样间隔 10 分钟变量之间相关性复杂。用电量数据集ECL包含 321 个客户的电量序列通道多能考察多变量建模能力。我的建议是先用 ETTh1 跑通全流程因为它的周期性明显预测失败时容易用肉眼判断模型是学到了周期还是只会复制。如果你要迁移到自己的业务数据只需把数据换成同样格式的三列日期时间、多个特征值、目标值。不要一上来就上高通道数数据集变量之间的相互干扰会给调参增加很多干扰。3.2 数据预处理与 DataLoader 构造滑动窗口怎么切Informer 的数据加载思路是把连续时间序列切成三元组。encoder 输入是过去seq_len步的特征decoder 输入是最后label_len步的真实值加上pred_len步的占位符预测目标是接下来pred_len步的真实值。切分窗口时每个窗口向后滑动一步或按pred_len步长滑动显然后者样本量更少但训练更快。下面是一个构造训练样本的代码按时间顺序滑窗import numpy as np from sklearn.preprocessing import StandardScaler def create_samples(data, seq_len96, label_len48, pred_len24): # data: [T, C] 已按时间排序 total_len seq_len pred_len xs, ys [], [] for i in range(len(data) - total_len 1): x data[i : i seq_len] # encoder 输入 y data[i seq_len - label_len : i total_len] # decoder 已知尾部 预测目标 xs.append(x) ys.append(y) return np.array(xs), np.array(ys) # 使用示例先对原始数据做标准化 scaler StandardScaler() train_data_norm scaler.fit_transform(train_data) train_x, train_y create_samples(train_data_norm)注意这里y的构造很关键它包含了label_len步的已知历史值也包含pred_len步的未来值。Informer 的 decoder 在训练时会把这两段同时输入模型学习用已知尾部来辅助预测未来。如果你的label_len设得太大decoder 里真实值占比过高模型会偷懒直接复制尾部后面预测指标会很差。这个问题在避坑章里我会再讲。3.3 最小训练脚本模型定义、训练循环与 checkpoint模型部分一般直接复用开源仓库里的Informer类这里不重复贴几百行模型代码重点讲训练循环里和普通 Transformer 不同的地方。Informer 的 encoder 与 decoder 都有各自的时间编码训练时要把时间特征小时、星期等拼接进输入所以 DataLoader 返回的样本通常是(enc_x, enc_x_mark, dec_x, dec_x_mark, target_y)这样的结构。下面是一个训练一个 epoch 的核心代码import torch import torch.nn as nn def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0 for enc_x, enc_x_mark, dec_x, dec_x_mark, true_y in loader: enc_x enc_x.to(device) enc_x_mark enc_x_mark.to(device) dec_x dec_x.to(device) dec_x_mark dec_x_mark.to(device) true_y true_y.to(device) optimizer.zero_grad() # 模型返回预测值注意只取 pred_len 这一段 preds model(enc_x, enc_x_mark, dec_x, dec_x_mark) preds preds[:, -true_y.shape[1]:, :] loss criterion(preds, true_y) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), 0.5) optimizer.step() total_loss loss.item() return total_loss / len(loader)这里的clip_grad_norm_是必须的长时序模型在训练早期容易因为个别样本出现梯度爆炸。dec_x在训练时由label_len的已知值和pred_len的占位符拼接而成在推理时则完全由已知值和不断生成的预测值拼接。很多新手把dec_x当成了普通输入直接传全零结果模型完全无法收敛。训练完成后需要保存 checkpoint建议同时保存模型参数、scaler 参数、以及seq_len/label_len/pred_len/d_model等配置。没有保存 scaler 的教训是推理时重新 fit 标准化得到的预测尺度完全不对。3.4 预测脚本单窗口滚动预测与结果可视化预测阶段最常用的方式是滚动预测用当前窗口的seq_len步预测未来pred_len步然后把预测结果当作已知值再拼上新的历史值继续预测下一个窗口。下面是单次预测的代码def predict_next(model, enc_x, enc_x_mark, dec_x, dec_x_mark, scaler, device): model.eval() with torch.no_grad(): enc_x torch.FloatTensor(enc_x).unsqueeze(0).to(device) enc_x_mark torch.FloatTensor(enc_x_mark).unsqueeze(0).to(device) dec_x torch.FloatTensor(dec_x).unsqueeze(0).to(device) dec_x_mark torch.FloatTensor(dec_x_mark).unsqueeze(0).to(device) pred model(enc_x, enc_x_mark, dec_x, dec_x_mark) pred pred[0, -pred_len:, 0].cpu().numpy() # 反归一化时使用训练集的 scaler而不是测试集的 return scaler.inverse_transform(pred.reshape(-1, 1)).flatten()反归一化这里很容易写错。scaler.inverse_transform输入必须是二维[N, C]如果只有一个变量也要reshape(-1, 1)。如果你输入了零维或者一维数组sklearn 会报维度错误。用 matplotlib 画图时建议把真实值、encoder 输入尾部、预测值画在同一个时间轴上重点看峰谷位置是否对齐。4. Informer 参数讲解从数据维度到超参数的联动关系4.1 时间窗参数 seq_len、label_len、pred_len 的联动seq_len是 encoder 看到的历史长度pred_len是要预测的未来长度label_len是 decoder 输入里已知真实值的长度。这三者不是独立变量。label_len一般取seq_len的 1/4 到 1/8比如seq_len96时label_len24或48。取太大会让 decoder 过度依赖已知尾部取太小则 decoder 缺少足够的上下文来辅助生成。pred_len与seq_len的关系更微妙。很多人以为预测越长历史就要越长实际在 Informer 里seq_len96预测pred_len168的效果甚至比seq_len336预测168更差的时候也有。原因是过长的历史噪声会淹没近期强相关的信息。我的做法是先用seq_len96, pred_len24跑通再逐步拉长pred_len同时观察验证集 loss 的收敛时间。如果pred_len翻倍后 loss 波动明显变大先不要加历史长度而是调小学习率。下面是一组常用的起点配置场景seq_lenlabel_lenpred_len基准测试964824短中期预测962448长期预测16848168极限长预测336483364.2 模型结构参数d_model、d_ff、n_heads、e_layers 与显存d_model是 embedding 维度决定每个时间步的特征表达能力。ETTh1 这种 7 通道数据d_model64通常就够了天气数据有 14 通道我一般从128开始。d_ff是前馈网络的内层维度通常取d_model*4左右。n_heads越多多头越能关注不同的周期但每个头都被随机采样头数太多反而增加稀疏估计的噪声。经验值是n_heads8。e_layers和d_layers分别是 encoder 和 decoder 的层数。Informer 的 encoder 里有卷积蒸馏层每蒸馏一次序列长度减半。层数越多感受野越大但显存和训练时间线性增长。我一般先用e_layers3, d_layers2起步如果显存不够先降d_model而不是先降层数因为层数对精度的影响更敏感。4.3 ProbSparse 专属参数factor 与采样次数怎么定factor是 Informer 独有的参数它直接控制 sparse query 的数量。在实现里u factor * ceil(sqrt(L))所以factor越大参与完整注意力计算的 query 越多模型越接近标准 Transformer但计算量也越大。factor5是我见到最多的默认值但如果你发现预测长周期峰值需要更多关键点我会先试factor7。如果factor设得太小比如2可能会出现一种奇怪的现象训练 loss 正常下降验证集指标也正常但预测曲线失去了突变响应能力。这是因为关键 query 被随机采样漏掉了。判断factor是否够用的一个技巧是在一个验证样本上打印稀疏度得分的 top 10 个 query 位置看它们是否分布在数据突变点附近。如果完全分布均匀说明稀疏度度量没有学到有用结构需要增大factor或检查 time embedding 是否生效。4.4 训练超参数learning rate、batch size、early stopping 的常用配置Informer 这类 Transformer 在长序列上对学习率很敏感。我常用的配置是learning_rate0.0001配合 warmup 500 步和余弦退火。batch size 在 32 到 128 之间取决于显存序列越长batch size 要越小。下面是可视化参数配置时我常用的 YAML 模板task: seq_len: 96 label_len: 48 pred_len: 24 model: d_model: 64 d_ff: 256 n_heads: 8 e_layers: 3 d_layers: 2 factor: 5 dropout: 0.1 train: batch_size: 64 learning_rate: 0.0001 warmup_steps: 500 patience: 5 max_epochs: 30 seed: 2024patience是早停的耐心轮数长时序训练波动大建议patience不少于 5否则很容易在 loss 刚走过一段平台期时被误停。dropout0.1是起步值如果验证集明显震荡可以加到0.2。随机种子务必固定因为 ProbSparse 的采样过程带随机性不固定种子会导致同样的参数跑出不一样的结果这也是避坑章里要重点说的问题。5. 避坑排查跑 Informer 最常见的 5 个翻车现场Informer 的代码结构不复杂但真正跑起来时大部分时间都花在调参和排错上。下面 5 个翻车现场是我在不同的数据集和业务数据上反复遇到的按照现象、原因、解决三个步骤记录方便你对照排查。5.1 训练 loss 震荡不收敛验证集 loss 居高不下现象训练 loss 在 0.3 到 0.5 之间来回跳验证集 loss 不降偶尔还会冲高。原因最常见的是学习率太大加上序列长度长梯度更新方向不稳定另一个是 warmup 步数不足。解决先把 learning rate 降到 1e-4warmup 步数加到 500如果还震荡把 batch size 翻倍让梯度更平滑。建议在训练脚本里打印每一层的梯度范数如果超过 10说明梯度爆炸此时除了降学习率还要检查是否漏了clip_grad_norm_。这个现象在短序列上不明显一旦seq_len超过 336loss 曲线就会变得很敏感。5.2 预测曲线滞后一个周期看起来像把历史尾段平移了现象在负荷或气温预测里预测结果和真实值形状一致但整体滞后了 12 到 24 步。原因Informer 的 decoder 能直接看到 label_len 步的真实历史如果 label_len 太大模型学到的最优策略就是复制已知尾部而不是学习时间依赖。解决把 label_len 从 seq_len/2 降到 seq_len/4 甚至 seq_len/8。另外检查时间特征 mark 里是否包含了未来的小时信息有些实现会把测试集的时间戳全部塞进去模型会钻空子。判断是否出现滞后不要只看 MSE要画图看峰值点。MSE 低但滞后仍然可能发生因为滞后一小段时整体曲线相似平方误差不会太大。5.3 显存爆炸但 factor 已经调得很小现象factor3seq_len192batch_size64 直接 OOM。原因注意力不是唯一的显存消耗点。输入 embedding、position encoding、多头投影、decoder 交叉注意力、梯度每一块都在吃显存。解决优先把 batch size 降到 32 或 16然后把 e_layers 从 3 降到 2把 d_model 从 128 降到 64。顺序不要反因为 e_layers 对显存的影响是线性的而 d_model 的影响是平方的。还可以用torch.cuda.max_memory_allocated()打印峰值显存定位是哪一层占用最大。蒸馏层会对序列长度减半如果没开蒸馏encoder 内每一层都会保留完整序列长度显存会成倍放大。5.4 复现不了一个月前的结果每次跑指标都不一样现象同一份代码、同一个数据集今天跑 MSE 是 0.08明天跑变成 0.11。原因ProbSparse 的采样依赖随机种子而很多人只设置了 numpy 和 random 的种子忘了设置 PyTorch 的 CUDA 种子。解决在每个 epoch 开头固定torch.manual_seed并给 DataLoader 设置worker_init_fn为固定的 seed 函数。还需要注意PyTorch 版本和 CUDA 版本不同会导致卷积蒸馏算子的结果有细微差异所以复现时最好把运行环境也固定下来或者使用 Docker。如果差距在 0.01 以内属于正常波动如果超过 0.05优先检查数据加载顺序是否被 shuffle。5.5 测试集指标异常地好好到不真实现象测试集 MSE 只有 0.02比训练集还低一个数量级。原因常见的是数据泄漏。时间序列切分时如果随机打乱了样本未来信息会混进训练集或者归一化时用全量数据的 mean 和 std模型在测试阶段也知道了全局统计量。解决严格按时间顺序切分训练、验证、测试集切分比例为 6:2:2scaler 只 fit 训练集验证测试都复用同一套参数。还有一个隐蔽点构造样本时确保预测目标区间和输入区间不重叠滑动窗口步长不能小于 pred_len。如果发现某个样本的预测目标和输入共享了同样时间段那就是泄漏。6. 把 Informer 改造成自己的组件注意力替换与参数验证技巧6.1 把 ProbSparse 注意力提取成独立模块如果你已经有自己的时序模型不必整体迁移到 Informer。把前面第 2 章的稀疏注意力逻辑封装成一个nn.Module替换模型里原来的多头注意力即可。需要同时替换的是 attention 部分d_model和n_heads保持原值factor作为新参数传入。替换后先跑一个小数据集验证 loss 能下降再逐步加大factor看收益。class ProbSparseAttention(nn.Module): def __init__(self, d_model, n_heads, factor5): super().__init__() self.factor factor # 内部实现第 2 章的 prob_sparse_attention def forward(self, q, k, v): return prob_sparse_attention(q, k, v, factorself.factor)这样替换时不需要改动其他组件。注意factor要保持可配置因为不同序列长度下最优值不同。6.2 用稀疏度分布做序列可解释性ProbSparse 筛选出的 top query 位置可以当作一种注意力解释工具。在预测某个突变点时打印最强 query 对应的输入时间索引往往能定位到历史中的相似事件。我习惯把这组索引和峰值点画在一张图里用来向业务方说明“模型为什么在这个点做出跳跃预测”。6.3 我的调参习惯与验证流程我自己的习惯是固定数据切分和随机种子后先调seq_len和pred_len再动factor最后才碰d_model。每改动一个参数只跑 5 个 epoch 看趋势不做完整训练。这样一轮参数实验大约能省一半时间。踩过最大的坑是没有保存 scaler导致换机器推理时预测结果完全错位后来我把 scaler、配置、模型权重打包成一份 checkpoint再也没犯过。希望这些经验能帮你在长序列预测上少走几次弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网