长时间序列预测模型Informer这套东西,我第一次真正上手是在给一套区域用电负荷做跨周预测的时候。当时的需求很朴素:拿过去四天的逐小时数据,预测未来三到十四天。我先按老办法上了LSTM,码了六层,训到第八个epoch就发现一个规律——预测步数一过48,曲线就开始往均值方向塌,越往后越像一条直线,最后干脆摆烂。后来换成标准的Transformer, encoder-decoder结构,效果确实好了一点,但训练时显存直接飙到一张卡装不下,推理阶段还得一步步滚,预测720个点要跑720次前向,线上根本扛不住。
Informer就是在那个节点进入视野的。它本质上是一个为“长输入、长输出”重新设计过的Transformer变体,核心解决三件事:注意力计算量随序列长度平方增长、深层堆叠时的内存爆炸、以及自回归解码带来的龟速推理。它把注意力复杂度压到O(L log L),用蒸馏把序列长度层层减半,再用一个生成式解码器一次性吐出一整段预测。这套设计适合手里有几千到几万条历史记录、数据有明显周期性、需要一次输出几十到几百步的场景,比如电力负荷、交通流量、气象观测、设备传感器读数。但它不是万能药,对极短序列、强突变序列、需要概率区间而不是单点估计的场景,硬上会很难受。
下面我把这套模型从设计动机到跑通细节完整捋一遍,包括我实际踩过的坑和调参时反复验证过的经验值。
1. 长序列预测为什么必须换一套架构思路
1.1 三个绕不开的现实痛点
先说清楚问题,后面所有的设计选择才有落脚点。
第一个痛点是误差累积。传统的递归式预测,一次只输出一个点,然后把这个点塞回输入窗口,再预测下一个。这个流程在业务上很直观,但在数学上是个连乘过程:每一步的误差都会作为下一步的输入,随着步数增加,误差方差不降反升。我实测过一个六层LSTM在小时级负荷数据上的表现,预测24步时MAE还在可接受范围,到96步误差大约翻了一倍多,到336步基本失去参考价值。这不是模型容量不够,而是递归结构本身在放大噪声。
第二个痛点是计算与显存。标准自注意力的时间与空间复杂度都是O(L²),L是序列长度。当输入加输出合起来到720甚至更长时,单头注意力矩阵就是720×720,多头再多层堆起来,激活值占用的显存会迅速吃掉整张卡。更麻烦的是反向传播要保存中间结果,哪怕前向勉强跑得动,训练时也很容易直接OOM。
第三个痛点是注意力的长尾分布。这个点最容易被忽略。把训练好的注意力权重拿出来看,会发现绝大多数query只对少数几个key有强响应,剩下几百个key的权重几乎贴着零。也就是说,完整的L×L矩阵里,大部分计算是在算“几乎没有贡献”的分数。这个观察是ProbSparse注意力的直接来源——既然分数分布是长尾的,那就只算那些“显著”的部分。
注意:这三点必须在同一套设计里一起解决才有意义。只压缩注意力复杂度而不改解码方式,推理依然慢;只改解码而不减内存,长序列训练还是跑不起来。Informer的三个组件是互相咬合的。
1.2 Informer的三处关键改动速览
先把结论摆出来,后面再逐个拆。
ProbSparse自注意力:不再对每个query都算完整注意力,而是用KL散度的一个近似度量挑出“活跃query”,只对这些query做点积注意力,其余位置用Value的均值填充。理论复杂度从O(L²)降到O(L log L)。
自注意力蒸馏:每个encoder层之后(最后一层除外)接一个一维卷积加最大池化,把序列长度砍半。三层encoder下来,长度按144→72→36收敛,特征图变小,显存和计算量同步下降,同时卷积池化本身对局部噪声有一定平滑作用。
生成式解码器:解码器输入不是一个个喂,而是一次性拼成label_len + pred_len的长度,前面label_len段填真实的近期历史,后面pred_len段填占位值(通常是0,在标准化后的空间里0等价于均值),一次前向直接输出全部预测步。
1.3 什么场景该用,什么场景别硬上
我见过不少人拿这套模型去跑只有几百条样本的日频数据,然后抱怨效果不如ARIMA。这不怪模型,是场景错配。
适合上Informer的典型画像:历史数据至少有几千个时间点;目标序列和协变量都有明确的日、周、月周期;需要一次性输出48到720步;线上能接受秒级而不是毫秒级推理;允许用单点预测配合人工经验做区间判断。
不建议硬上的情况:样本量低于两千;序列存在大量阶跃式突变(比如促销日、故障跳变),这类突变更依赖事件特征而不是历史模式;业务要求输出置信区间或分位数(Informer的原生输出只有点估计,要做区间得额外包一层分位数回归或者做残差的bootstrap);推理延迟要求极严的嵌入式场景。
还有一点必须提前说:在pred_len拉到720这种极长跨度时,Informer的中位数误差常常已经很接近“取上周同一时刻的值”这种朴素季节性基线。这不是贬低它,而是提醒你在做评估报告时一定要把季节性朴素基线(seasonal naive)放进去做对照。我见过太多汇报只跟自己以前那版差模型比,数字好看但没有说服力。
2. 核心机制拆解:三个设计背后的账怎么算
2.1 ProbSparse注意力:从平方到线性对数的取舍
标准自注意力对每个query q_i 都与全部key计算点积再softmax。ProbSparse的思路是:先给每个query算一个“重要性分数”,只保留分数最高的那批query参与完整计算。
分数怎么定?原文给出的度量是这样的:对第i个query,定义
M(q_i, K) = max_j (q_i·k_j^T / √d) - (1/L_K) · Σ_j (q_i·k_j^T / √d)
用人话说,就是“这个query与所有key的相似度里,最大值减去平均值”。如果某个query对某几个key响应特别强,最大值会远高于平均值,差值就大;如果它对所有key都差不多,差值趋近于零。这个差值恰好是KL散度的一个近似上界,所以它是在近似衡量“这个query的注意力分布有多偏离均匀分布”。
选出分数最高的 u 个query,u的取值是u = c · ln(L_Q),其中c是常数因子,代码里对应factor参数,默认5。以L_Q=96为例,u = 5 × ln(96) ≈ 5 × 4.564 ≈ 23,也就是96个query里只算23个,剩下73个直接用Value的均值顶上。
实操里有两个必须知道的细节。一个是采样:为了不真的把全部L_K算一遍再挑,实现里会随机采样U = c·ln(L_K)个key来做估计,这意味着每次前向采样的key子集不同,输出会带一点随机性。这个随机性在训练阶段其实起到了轻微的正则作用,但如果你在做确定性推理复现,就需要固定随机种子,否则同一份输入两次推理结果会有微小差异。
另一个是加速比是有门槛的。我在 L=96 的短序列上对比过ProbSparse和全注意力,ProbSparse反而更慢,因为采样、取topk、gather这些索引操作的开销超过了省下来的矩阵乘法。真正拉开差距是在 L 到 512 以上,尤其是720这个量级,显存占用能降一半以上。所以如果你的场景只有96步输入,用不用ProbSparse无所谓;输入窗口拉到几百步,它才开始回本。
注意:ProbSparse不是无损近似。它在某些周期性弱的序列上会漏掉真正重要的query,表现为预测曲线在局部出现不该有的平坦段。判断方法是把
factor从5调到3或8各跑一次,如果结果对这个参数极度敏感,说明你的数据分布不够长尾,这时候老老实实用全注意力(很多实现里把factor设成一个大于L的值就等价退化成全注意力)反而更稳。
2.2 自注意力蒸馏:砍半序列长度的代价与收益
蒸馏那一层写出来只有一行:
X_{t+1} = MaxPool(ELU(Conv1d(X_t)))
Conv1d的kernel设3、stride设1、padding设1,输入输出通道都是d_model,保持特征维度不变;然后ELU激活,再做kernel=3、stride=2、padding=1的最大池化,序列长度变成ceil(L/2)。
它的收益很直接:三层encoder堆下来,序列从144降到72再降到36,注意力矩阵的元素数按四分之一、十六分之一递减,显存占用大幅下降,同时后面几层的感受野反而变大了——因为每层注意力在更短的序列上操作,等效覆盖的原始时间跨度更宽。
代价也很实在。池化是不可逆的信息压缩,细粒度的位置信息会丢。我观察过的一个现象是:当序列里存在持续时间短于池化窗口的尖峰(比如持续两三个小时的异常高负荷),经过第一层蒸馏后这个尖峰就被平滑掉一部分,到第三层基本看不出来了。如果你的业务关心短时异常,蒸馏层反而是个减分项。
还有一个容易忽略的点:蒸馏只在encoder里做,decoder里不做。因为decoder的输出长度必须精确对应pred_len,中间不能变长变短。所以decoder那边该吃多少注意力计算还是吃多少,长跨度预测的显存瓶颈主要落在decoder上,这点在调优时要心里有数。
2.3 生成式解码器:一次吐出720个点是怎么实现的
这是我觉得整套设计里最实用的一处改动,也是最容易被教程讲糊的地方。
传统Transformer解码,训练时用teacher forcing,推理时自回归,一次一步。Informer把它改成:解码器输入序列长度固定为label_len + pred_len。前label_len个位置填入测试段之前的真实历史值(经过同样的标准化),后pred_len个位置填入0(在标准化空间里就是均值)。整个序列一次性送进decoder,输出再经过一个线性层映射回特征维度,只取最后pred_len段计算损失。
为什么填0而不是别的?因为标准化之后0就是训练集的均值,用均值作为未知未来的初始猜测,是偏差最小的先验。模型要学的就是“从均值出发,往正确的方向修正多少”。
这个设计的代价是一处硬约束:推理时必须提供label_len长度的最新真实历史。这不是可选项。线上部署的时候如果你的数据管道有延迟,拿不到最近48个点,模型就没法跑。我在一个项目里就因为这个卡了两天——上游数据同步是每小时一次但偶尔延迟,导致label_len段里混进了NaN,模型输出直接变成一片NaN。后来在数据入口加了一层前向填充加质量标记才解决。
label_len取多大?原文在多数实验里用48,也就是seq_len的一半。我试过把它调到等于pred_len,在电力数据上略好一点点,但训练时间增加明显;调到24又会在预测起点附近出现跳变。经验值:label_len取seq_len的 0.3 到 0.5 之间,绝大多数场景够用。
2.4 输入表示:别小看那层看着不起眼的时间戳嵌入
Informer的输入表示是三项相加:标量投影、位置编码、全局时间戳嵌入。
标量投影就是一个线性层,把每个时间点的特征维度映射到d_model。位置编码用标准的正弦余弦。全局时间戳嵌入是把月、日、星期、小时(分钟级数据再加分钟)各自做成一张embedding表,查出来再相加。这几张表的词表大小是固定的:月12、日32、星期7、小时24、分钟60。
这一步看着简单,但错起来很隐蔽。最常见的错误是freq参数和数据实际粒度对不上。比如数据是15分钟粒度,freq却按小时传,那么“小时”这一维会被算错,“分钟”维干脆不生成,模型学到的时间模式全是错的。表现是训练损失能降但验证损失居高不下,而且预测曲线在每个整点附近出现规律性的台阶。排查方法很简单:把生成的时间特征打印前20行,人眼对一遍原始时间戳。
还有一个细节:日期列如果是字符串,解析时区、解析格式、闰年、夏令时都可能出岔子。我的做法是数据进管道前先把时间列统一转成pandas.to_datetime再显式.dt.tz_localize(None),避免带时区的对象在后面做差值计算时抛异常。
3. 从零跑通:数据、配置与训练全流程
3.1 数据管道与滑动窗口切片
数据管道这块,核心是一个滑动窗口Dataset。窗口长度是seq_len + pred_len,步长1,训练集、验证集、测试集按时间顺序切分,绝对不能shuffle后再切。
class WindowDataset(Dataset): def __init__(self, raw_x, raw_x_mark, raw_y, raw_y_mark, size): self.seq_len, self.label_len, self.pred_len = size self.x = raw_x self.x_mark = raw_x_mark self.y = raw_y self.y_mark = raw_y_mark def __len__(self): # 注意这里必须减1,边界处最容易越界 return len(self.x) - self.seq_len - self.pred_len + 1 def __getitem__(self, idx): s_begin = idx s_end = s_begin + self.seq_len r_begin = s_end - self.label_len r_end = r_begin + self.label_len + self.pred_len return (self.x[s_begin:s_end], self.y[s_begin:s_end], self.x_mark[s_begin:s_end], self.y_mark[r_begin:r_end], self.y[r_begin:r_end])这段代码里有两个地方特别容易翻车。第一是__len__的边界,早期实现里有人写len - seq_len - pred_len,少了那个+1,导致最后一个窗口取不到,测试集尾部几十个点永远不被评估,指标会虚高一点点。第二是归一化,StandardScaler只能在训练段上fit,然后transform验证段和测试段。我见过把全量数据一起fit的写法,这就是典型的数据泄漏,指标能好看10%以上,但上线就崩。
scaler = StandardScaler() scaler.fit(train_df[cols].values) # 只在训练段fit train_arr = scaler.transform(train_df[cols].values) val_arr = scaler.transform(val_df[cols].values) test_arr = scaler.transform(test_df[cols].values)如果做多变量预测,还要决定是全局共用一套均值方差,还是每个变量各自一套。我的做法是每个变量独立标准化,因为电力、温度、流量的量纲差异太大,共用一套会让小量纲变量在损失里几乎没有权重。
3.2 关键超参怎么定:给出一套能直接用的配置
先给一个我反复验证过的基线配置,以小时级数据的ETT这类公开数据集为例。
| 参数 | 取值 | 说明 |
|---|---|---|
| seq_len | 96 | 小时级数据4天,是24的整数倍,覆盖完整日周期 |
| label_len | 48 | seq_len的一半,解码器起点的真实历史长度 |
| pred_len | 24/48/168/336/720 | 分别对应1天、2天、1周、2周、1个月 |
| d_model | 512 | 隐藏维度,8头时每头64维 |
| n_heads | 8 | head_dim = d_model / n_heads = 64 |
| d_ff | 2048 | 前馈层维度,取4倍d_model是Transformer惯例 |
| e_layers | 2或3 | 3层时有两次蒸馏,144→72→36 |
| d_layers | 1 | 解码器1层足够,加层收益很小但显存涨得快 |
| factor | 5 | ProbSparse的采样因子c |
| dropout | 0.05 | 时间序列数据通常不需要大dropout |
| batch_size | 32 | 单卡16G显存的安全值 |
| learning_rate | 1e-4 | Adam,配合每轮衰减 |
| train_epochs | 6 | 这类模型收敛很快,6轮后基本过拟合 |
关于seq_len的选择,有个简单的推理过程:数据是小时级,最强的周期是24小时和168小时(一周)。取96既是24的整数倍,也接近半个周周期,能同时覆盖日周期的四个完整循环。如果你取100,模型就没法利用“每天同一时刻”这种对齐关系,效果会明显差一截。同样的逻辑,分钟级数据取1440(一天)的整数分之一,日频数据取7或14。
d_ff为什么是4倍d_model?这是从原始Transformer延续下来的经验比例,前馈层承担了大部分非线性变换的容量,太窄会导致欠拟合,太宽参数暴涨但收益递减。参数量估算一下:单层encoder的注意力部分约4·d_model²= 4×262144 ≈ 1.05M,前馈部分约2·d_model·d_ff= 2×512×2048 ≈ 2.1M,加起来约3.1M。3层encoder约9.4M,加上embedding、decoder、输出投影,整体在11到12M量级。这个规模在单张消费级显卡上完全跑得动。
factor的实际意义值得单独说一下:u = 5 × ln(96) ≈ 23,也就是说只有约四分之一的query参与完整注意力计算。如果你的seq_len拉到336,u = 5 × ln(336) ≈ 29,参与比例降到不足9%,加速更明显但近似误差也更大。这是我建议在长序列场景把factor适当调小(比如4)的原因。
3.3 训练脚本与显存调优实录
一条能直接跑的命令长这样:
python -u run.py \ --is_training 1 \ --model informer \ --data custom \ --root_path ./dataset/ \ --data_path load_hourly.csv \ --features M \ --target OT \ --freq h \ --seq_len 96 \ --label_len 48 \ --pred_len 336 \ --e_layers 3 \ --d_layers 1 \ --factor 5 \ --d_model 512 \ --d_ff 2048 \ --n_heads 8 \ --dropout 0.05 \ --train_epochs 6 \ --batch_size 32 \ --learning_rate 0.0001 \ --patience 3 \ --des exp_long_horizon--features M表示多变量预测多变量,--target指定主目标列,损失只在这列上算。如果你的需求是“用多个外生变量预测单一目标”,改用--features MS,这时候输出维度会降到1,显存也能省不少。
--freq h必须和数据粒度严格一致,可选 h(小时)、t(分钟)、d(日)、w(周)、m(月)。这一项错了前面说过,时间特征全废。
显存不够的时候,按这个优先级往下砍:
- 先降batch_size,32→16→8。这是最不影响模型结构的操作,代价只是训练噪声变大、收敛慢一点。
- 再降d_model,512→256。注意d_model降了n_heads要跟着降,保持每头维度64比较稳,256配4头。
- 再降e_layers,3→2。少一层就少一次蒸馏,序列长度收敛得慢,显存和计算都省。
- 最后才考虑混合精度。AMP在注意力softmax那里容易出数值问题,我的经验是开启后要监控loss是否出现NaN,前200步特别关键。如果一定要开,把softmax前的scale做一下clamp会更稳。
还有一个容易被忽略的显存占用来源:日志和中间结果保存。有些实现每步都把预测结果、注意力权重存到列表里,训练几百步后这些列表本身就吃掉几个G。我的做法是只在验证阶段收集结果,训练阶段只记标量loss。
3.4 评估与反归一化:这一步做错,前面全白干
评估阶段最容易犯的错,是直接在标准化空间里算MSE和MAE。这样算出来的数字很好看,因为数据被压到均值0方差1附近,误差绝对值天然小。但这个数字对业务毫无意义——你没法跟业务方说“我们的MAE是0.08”,人家问0.08什么单位,你答不上来。
正确做法是先把预测值和真值都做inverse_transform,回到原始量纲,再算MAE、RMSE、MAPE。
pred = scaler.inverse_transform(model_output) # 回到原始量纲 true = scaler.inverse_transform(ground_truth) mae = np.mean(np.abs(pred - true)) rmse = np.sqrt(np.mean((pred - true) ** 2))MAPE要小心,如果原始数据里存在接近0的值,MAPE会爆炸。电力负荷在凌晨低谷时可能接近0,我一般用 sMAPE 或者干脆只用MAE加RMSE。
比单个MSE数字更有诊断价值的,是分horizon误差曲线。把pred_len=336的预测拆成14段,每24步算一个MAE,画出来看误差随步长的增长斜率。如果斜率在前段平缓后段陡增,说明模型只是学会了复制最近值,长期依赖没学到;如果整体平缓但起点就高,说明输入表示或者归一化有问题。这张图我在每次调参时都会看,比盯总MSE管用得多。
4. 常见问题与排查实录
4.1 损失不降或者剧烈震荡
训练损失从第一轮就卡在某个值附近不动,先排查这四项,按可能性排序。
时间特征生成错误。freq参数和实际粒度不符时,模型看到的时间编码是错的,学不到周期模式。表现是loss能降一点但很快平台化,验证集误差远高于训练集。排查方法是把x_mark的前20行打出来,和原始时间列人工比对。
归一化用错范围。全量fit导致泄漏的话,训练loss会异常低(因为测试信息漏进来了),验证loss却很高。这种情况反而好识别。真正难查的是“用了原始未归一化数据直接训练”,loss会直接飙到几千然后NaN。
标签对齐错位。r_begin = s_end - label_len这个偏移写错一位,模型就是在学“预测错位一个时间点”的任务,loss能降但预测曲线整体滞后一步。这个错误极其隐蔽,检查方法是在测试集上把预测曲线和真值曲线画在同一张图里放大看起点是否对齐。
学习率过大。1e-4是安全值,如果用了1e-3,前几个batch的loss会剧烈跳动甚至出现NaN。Informer用的是GELU加LayerNorm,理论上比原版Transformer稳,但注意力里那个除√d的缩放配合大学习率仍然容易炸。
4.2 长horizon尾段崩坏
pred_len拉到336或720时,前几十步预测还挺贴合,后面逐渐摊平成一条直线。我在这上面花的时间最多,试过的方案和实际效果如下。
| 尝试 | 实际效果 | 结论 |
|---|---|---|
| 加大label_len到96 | 起点改善,尾段无变化 | 作用有限 |
| 加大seq_len到336 | 尾段明显改善,显存涨一倍 | 有效但代价高 |
| 损失对远端加权 | 尾段略好,近端变差 | 需要权衡,gamma取0.5左右 |
| 加入傅里叶频域特征 | 周期性强的数据有效 | 值得试 |
| 换成patch化输入 | 效果提升最明显 | 根本性方案 |
| 加季节性朴素基线对照 | 发现模型其实没赢多少 | 必做 |
这里的核心认知是:长跨度预测的瓶颈不在模型容量,而在信息本身。720步之后的不确定性,任何模型都消不掉。所以我现在的做法是,pred_len超过336就把预测结果当作“趋势参考”而不是“精确数值”交付,同时把季节性朴素基线一起给业务方看,让对方建立合理预期。
还有一个具体技巧:在decoder输出端加一个可学习的位置相关偏置项(形状是pred_len × n_features),初值设0。这相当于给模型一个“每个预测步允许有自己的系统性偏移”的自由度,实测能让尾段的平坦化推迟大约30%的步数。代价是参数量增加pred_len × n_features,对720×7这种规模完全可接受。
4.3 显存与速度的典型症状速查
| 症状 | 最可能原因 | 处置办法 |
|---|---|---|
| 训练第一个batch就OOM | batch_size或seq_len过大 | 先降batch到8验证能否跑通 |
| 训练几轮后突然OOM | 中间结果累积未释放 | 检查是否有列表在无界append |
| 显存正常但速度极慢 | ProbSparse在短序列上的索引开销 | seq_len<128时改全注意力 |
| 推理延迟高 | decoder层数过多 | d_layers保持1层 |
| 多卡训练不加速 | 数据管道成瓶颈 | num_workers调到4到8 |
| 单步时间波动大 | 数据加载在同步等待 | 开pin_memory并预取 |
其中“多卡不加速”这条我印象很深。一开始以为是通信开销,profiling之后发现GPU利用率只有30%,瓶颈在数据加载——滑动窗口的切片操作在Python层做,每个batch要跑几十次numpy切片。把Dataset改成预先构造索引数组、__getitem__里只做一次数组索引之后,吞吐直接翻了一倍多。
注意:不要一上来就开多卡。这个模型参数量才10M出头,单卡完全够用,多卡带来的同步开销在小模型上经常是负收益。先把单卡跑满再说。
4.4 一份可以直接对照的排查清单
遇到效果不达预期,按这个顺序走一遍,八成问题能定位:
- 打印一条完整的样本:x的shape、y的shape、时间特征的数值、归一化前后的取值范围。
- 用同一份数据跑一遍“复制最近值”的平凡基线,看模型的MAE是不是真的低于它。
- 把训练集上的预测结果画出来,如果训练集上都拟合不好,问题在模型或管道,不在泛化。
- 检查测试集是不是时间上真的在训练集之后,中间有没有重叠。
- 把
factor从一个极大值(等价全注意力)调到5,看结果差多少,判断ProbSparse是否在拖后腿。 - 关掉蒸馏层(把e_layers设1)再跑一次,判断蒸馏是不是把关键信息滤掉了。
- 最后再动学习率和dropout。
这套流程看似笨,但比漫无目的地调参快得多。我现在的习惯是每次换新数据集,先花二十分钟把这七步跑一遍,形成一个基线认知,后面所有改动都和这个基线比。
5. 这套架构的演进脉络与选型建议
5.1 后续方案修正了哪些遗留问题
Informer之后,这个方向上有几类明显的修正思路,了解它们能帮你判断自己该不该继续用Informer。
一类是质疑注意力必要性的。有工作直接用“输入线性映射加季节性分解”的极简结构,在多个公开数据集上把Transformer类模型打得很难看。这个结论对从业者的启示是:先跑线性基线,如果线性模型已经够用,就不要上Transformer。我在两个项目里验证过,其中一个日频销量预测场景,线性分解模型的效果确实优于调过参的Informer,而且训练快了二十倍。
一类是patch化输入的。把长序列切成一个个子片段当作token,而不是每个时间点一个token。这样序列长度直接降一个数量级,注意力计算量骤减,同时每个patch内部保留了局部形状信息——恰好补上了Informer蒸馏层丢失局部细节的短板。我现在做长跨度预测的默认起点已经改成patch化方案,Informer更多作为对照基线。
还有一类是换注意力维度的。把注意力从时间维搬到变量维,让每个变量成为一个token,专门建模变量间的相关性。这对多变量场景(比如几十个传感器同时预测)收益明显,但如果你的变量数只有个位数,收益有限。
至于频域方向的尝试,把序列变换到频域只保留低频分量再变回来,对强周期数据效果不错,但实现复杂度高,调参空间大,我一般放在第三顺位考虑。
5.2 我的实际选型顺序
经过这几年的反复试错,我现在拿到一个长时间序列预测需求,处理顺序是这样的。
第一步,永远先建朴素基线。季节性朴素(取上周同位置)、移动平均、线性回归,三个基线一小时内能跑完,它们给出的误差量级决定了后面值不值得投入。
第二步,如果基线误差不能接受,先上轻量的线性分解模型。训练快、调参少、可解释性好,很多时候这就够了。
第三步,如果线性模型明显不够(通常是多变量、强非线性交互的场景),再上Transformer类。这时候Informer是个合理的起点,因为它的代码实现成熟、社区资料多、跑通成本低。但要清楚它的定位是“能跑通的基线”,不是“最终方案”。
第四步,确定要用Transformer之后,先试patch化输入,再考虑蒸馏和稀疏注意力。我的实测结论是:在中等长度(96到336)的场景下,patch化的收益大于ProbSparse,而蒸馏层在数据噪声大的时候反而是负担——它会把你想要捕捉的短时异常一起滤掉。
最后分享一个我在实际部署里觉得最值钱的习惯:把每次实验的完整配置、随机种子、数据切分边界、以及那条分horizon误差曲线,全部存进一个结构化的记录里。这个模型对随机种子比想象中敏感,同一个配置换个种子,pred_len=336的MAE能差5%到8%。没有这份记录,两周后你根本说不清“上次那个好结果”到底是怎么来的。我吃过这个亏,一个效果不错的配置没记全,后面复现了三天才对上。