
简介面向时间序列预测初学者与进阶研究者这套Informer实战案例将代码、数据集与参数讲解整合在一起核心聚焦ProbSparse自注意力机制与自注意力蒸馏技术以O(LlogL)的复杂度捕捉序列长期依赖并有效处理极长输入序列。资源共64个文件包含17个Python脚本、17个NumPy数据文件、16个编译缓存、6个工程配置、3个CSV数据集、2个预训练模型权重及1个环境配置YAML等压缩包整体115.95MB目录按源码、实验、结果和配置划分结构清晰便于查阅。目前已有2862人学习下载读者可借助ETTh1数据集完成单变量和多变量预测复现修改序列长度等参数观察模型表现也可直接加载checkpoint.pth文件运行验证并参考预测结果npy文件分析精度。配套代码对Informer的注意力机制、编码器蒸馏等关键模块均有实现适合需要动手实践并深入理解长序列预测模型的开发者快速上手与二次开发。1. Informer实战长序列预测为什么必须换掉标准Transformer做时间序列预测的人迟早会遇到一个坎序列一长Transformer就翻车。标准Transformer的自注意力复杂度是O(L²)你输入一千个时间点计算量直接奔着百万级去显存吃不消、训练慢到怀疑人生。Informer就是冲着这个痛点来的它把复杂度压到O(L log L)靠的是一套叫ProbSparse自注意力机制——简单说不是所有query都值得跟所有key做点积真正重要的只有一小部分。这份Informer2020实战资源自带ETTh1电力变压器数据集、完整训练代码和已经跑出来的checkpoint适合正在做长序列预测、想复现Informer论文但又不想从零搭环境的人。读完这篇你能把每个参数的含义、数据怎么进模型、结果文件怎么解读全部捋顺踩坑记录也一并奉上。2. 先把代码包摊开Informer2020的目录结构与ProbSparse核心实现2.1 资源里到底有什么文件清单与功能归属拿到压缩包先别急着跑把目录结构过一遍。这个项目沿用了Informer官方的工程组织方式核心代码全部在models、data、exp三个目录里其余是配置和结果产物。Informer2020-main/ ├── data/ # 数据集与数据加载器 │ ├── ETTh1.csv # 训练用电力变压器数据 │ ├── ETTh1-Test.csv # 测试集 │ ├── data_loader.py # 数据集类与预处理 │ └── __pycache__/ # 编译缓存可忽略 ├── models/ # 模型核心 │ ├── attn.py # ProbSparse注意力实现 │ ├── encoder.py # 编码器蒸馏层 │ ├── decoder.py # 解码器 │ ├── embed.py # 嵌入层 │ └── model.py # Informer整体组装 ├── exp/ │ ├── exp_basic.py # 实验基类 │ └── exp_informer.py # 训练/验证/测试主流程 ├── main_informer.py # 入口参数全在这 ├── environment.yml # conda依赖建议直接用它建环境 ├── checkpoints/ # 已训练好的模型权重 └── results/ # 预测结果npy文件常见的误区是直接拿main_informer.py开跑一报错就慌。实际上models/attn.py才是这套代码的灵魂你后面调参、改模型都要从它入手。results目录下的pred.npy、true.npy是已经跑出来的预测结果可以用来验证你的环境是否正常也可以直接拿来画图分析。checkpoints里有pl4和pl24两组权重对应预测长度4和24的已训练模型省去你从头训练的时间。2.2 ProbSparse自注意力attn.py里的概率筛选逻辑打开models/attn.py核心类叫ProbAttention。它的思路跟标准注意力最大的区别在第一步先筛query再算attention。标准做法是所有query和所有key做点积而ProbSparse假设大部分query对输出的贡献是冗余的只要挑出少数活跃的query就够。class ProbAttention(nn.Module): def __init__(self, mask_flagTrue, factor5, scaleNone, attention_dropout0.1, output_attentionFalse): super(ProbAttention, self).__init__() self.factor factor self.scale scale self.mask_flag mask_flag self.output_attention output_attention self.dropout nn.Dropout(attention_dropout) def _prob_QK(self, Q, K, sample_k, n_top): # Q: [B, H, L, D] - [B, H, L, D] B, H, L, D Q.shape _, _, S, _ K.shape # 对L个query采样sample_k个key计算得分 Q_K torch.matmul(Q, K.transpose(-2, -1)) # 实际用采样后的K M Q_K.max(dim-1)[0] - torch.log(Q_K.mean(dim-1) 1e-12) # 取M分数最高的n_top个query M_top M.topk(n_top, sortedFalse)[1] # 只对选中的query计算完整attention Q_reduce Q.index_select(2, M_top) Q_K_reduce torch.matmul(Q_reduce, K.transpose(-2, -1)) return Q_K_reduce, M_top核心逻辑在_prob_QK里先用采样方式估计每个query的稀疏度得分MM的公式是max - log(mean)这一项在数学上对应KL散度的近似——如果某个query的注意力分布越偏离均匀分布说明它越有信息量M值越高。然后只对M最高的前n_top个query做完整的QK点积其余query直接用均匀分布近似。n_top由参数factor控制默认factor5实际选取的query数就是factor * log(序列长度)这也是O(L log L)复杂度的来源。参数层面你只需要关心factor和mask_flag。factor调大保留的query越多模型表达能力增强但计算量上升mask_flag在训练时开、验证时关它控制是否对future位置做mask防止信息泄漏。我一般习惯先保持factor5不动看训练曲线是否收敛再决定要不要增减。2.3 自注意力蒸馏encoder层间怎么瘦身Informer的第二个杀手锏是自注意力蒸馏实现在models/encoder.py的distil操作里。蒸馏的直观理解是每经过一层编码器把序列长度压缩一半让模型在高层看到更大感受野同时减少后续层的计算量。class ConvLayer(nn.Module): def __init__(self, c_in): super(ConvLayer, self).__init__() self.downConv nn.Conv1d(in_channelsc_in, out_channelsc_in, kernel_size3, padding1, stride1) self.norm nn.BatchNorm1d(c_in) self.activation nn.GELU() self.maxPool nn.MaxPool1d(kernel_size3, stride2, padding1) def forward(self, x): x self.downConv(x.permute(0, 2, 1)) x self.norm(x) x self.activation(x) x self.maxPool(x) x x.permute(0, 2, 1) return xConvLayer做的事是一维卷积降噪 → 批归一化 → GELU激活 → 最大池化把长度减半。这个模块插在每层encoder之后e_layers2时就压缩一次e_layers3时压缩两次。序列长度128进来经过两层蒸馏后变成32解码器拿到的语义特征更凝练。这里有个容易忽略的细节蒸馏只发生在encoder侧decoder不做池化因为解码器需要对齐输出长度。你看到的结果目录里el2_dl1这个标记就表示encoder两层、decoder一层这是论文里验证过性价比最高的组合。想加深网络时优先加e_layersdecoder层数加到2以上对长序列预测的收益不大反而拖慢训练。3. 数据接入ETTh1数据集与data_loader的预处理全流程3.1 ETTh1数据长什么样14个特征与电力变压器场景data/ETTh1.csv是电力变压器温度预测数据集里的hourly版本记录了两个变电站的油温和电力负荷数据时间跨度从2016年7月到2018年7月一共17420行。每一行除了时间戳还有14个特征列——包括油温、六个外部电力负荷因素以及它们的衍生量。打开csv看一眼前几行格式很干净date,HUFL,HULL,MUFL,MULL,LUFL,LULL,OT 2016-07-01 00:00:00,174.58,172.86,160.66,162.11,176.54,175.16,30.06 2016-07-01 01:00:00,174.72,172.94,160.90,162.16,176.86,175.34,30.29最后一行不用管是模型要预测的目标——油温OTOil Temperature。其余列是各种电力负荷特征单位是兆瓦。这里有个关键点ETTh1是小时粒度的采样同系列还有ETTm1是15分钟粒度如果拿ETTm1跑这套参数序列长度和训练时间都要重新调。3.2 data_loader时间特征编码、标准化与三种特征模式data/data_loader.py里的DatasetETT类负责把csv读成模型能吃的张量。整个预处理分四步解析时间戳、特征标准化、时间特征编码、滑窗切分。class DatasetETT(Dataset): def __init__(self, root_path, flagtrain, sizeNone, featuresS, data_pathETTh1.csv, targetOT, scaleTrue, timeenc0, freqh): # size [seq_len, label_len, pred_len] self.seq_len size[0] self.label_len size[1] self.pred_len size[2] # flag决定读取训练/验证/测试集 type_map {train: 0, val: 1, test: 2} self.set_type type_map[flag] self.__read_data__() def __read_data__(self): self.scaler StandardScaler() df_raw pd.read_csv(os.path.join(self.root_path, self.data_path)) # 按7:2:1切分训练/验证/测试 border1 int(len(df_raw) * 0.7) - self.seq_len border2 int(len(df_raw) * 0.9) # featuresM用全部列S只用target列MS用全部列但target做标签 if self.features M or self.features MS: cols_data df_raw.columns[1:] elif self.features S: cols_data [self.target] df_data df_raw[cols_data] # 只在训练段上fit标准化 if self.scale: train_data df_data[border1:border2] self.scaler.fit(train_data.values) data self.scaler.transform(df_data.values) # 时间特征编码 df_stamp df_raw[[date]] data_stamp time_features(pd.to_datetime(df_stamp[date].values), freqself.freq) self.data_x data[border1:border2] self.data_y data[border1:border2]这段代码里最容易踩坑的是border1 int(len(df_raw) * 0.7) - self.seq_len。这里减掉seq_len是为了保证训练集里每个样本都能取到完整的输入窗口如果你改小seq_len切分边界会变测试集样本数也会跟着变。StandardScaler只在训练段上fit验证和测试都用训练集的均值方差做变换——这点很重要很多人自己写预处理时把标准化在全集上做会导致数据泄漏测试指标虚高。时间特征编码走的是utils/timefeatures.pytimeenc0时用timeF模式生成hour、weekday、day、month等周期性特征维度固定是4timeenc1时用fixed模式直接做embedding。默认推荐timeF它对周期性模式的泛化更好fixed适合数据周期不明显的场景。3.3 序列切分seq_len / label_len / pred_len 三种窗口的关系Informer的输入设计和普通LSTM不一样它有三个长度参数seq_len是encoder看到的完整历史长度label_len是decoder起始部分的已知真值长度pred_len是要预测的未来长度。在__getitem__里三者组合成一个样本def __getitem__(self, index): s_begin index s_end s_begin self.seq_len r_begin s_end - self.label_len r_end r_begin self.label_len self.pred_len seq_x self.data_x[s_begin:s_end] seq_y self.data_y[r_begin:r_end] seq_x_mark self.data_stamp[s_begin:s_end] seq_y_mark self.data_stamp[r_begin:r_end] return seq_x, seq_y, seq_x_mark, seq_y_mark注意r_begin是从s_end - label_len开始的不是从s_end开始。意思是decoder输入的前label_len个位置是已知真值后面pred_len个位置是0占位用来让decoder自回归生成。这个已知真值拼接预测位的设计是Informer能比纯自回归模型更快的原因——它不需要一步步生成decoder一次前向就能输出全部预测。项目默认参数sl126_ll64_pl24表示看126小时历史其中64小时已知真值作为decoder起点预测未来24小时。如果你发现预测结果前几个点特别准、后面漂移大多半是label_len给得太短试试把ll提到和sl的2/3比例。4. 训练与推理main_informer参数逐项拆解与两种运行模式4.1 超参数字符串的密码从sl126_ll64_pl24到dm512_nh8checkpoints和results目录下的文件名是一串压缩过的超参数看懂它你就能直接复现任意一组实验。以informer_custom_ftMS_sl126_ll64_pl24_dm512_nh8_el2_dl1_df2048_atprob_fc5_ebtimeF_dtTrue_mxTrue_test_0为例逐段拆解标记含义取值ftMSfeatures多变量全部特征输入M / S / MSsl126输入序列长度seq_len126ll64标签长度label_len64pl24预测长度pred_len24dm512d_model嵌入维度512nh8注意力头数8el2encoder层数2dl1decoder层数1df2048前馈网络维度d_ff2048atprob注意力类型为ProbSparsefull表示标准注意力fc5factor稀疏因子5ebtimeFembedding类型timeFdtTrue蒸馏开启True/FalsemxTrue混合embedding开启True/Falsetest_0第几轮测试0/1这些参数全部暴露在main_informer.py的argparse里不需要改代码命令行直接传。d_model512配合n_heads8意味着每个头64维这是Transformer系的经典配置。d_ff2048是前馈网络隐藏维度通常取d_model的4倍。如果你显存吃紧先把d_model降到256d_ff降到1024精度损失在5%以内但训练速度能快一倍。4.2 多变量 vs 单变量features参数的选型逻辑features参数决定模型看到什么特征三种取值对应三种实验设定featuresS只用目标列OT单变量预测适合验证模型核心能力featuresM输入全部14列特征输出也预测全部14列适合多任务场景featuresMS输入全部特征但loss只计算OT这一列这是论文主推模式结果目录里既有ftMS也有ftM的实验对比下来ftMS在OT上的MSE通常比ftM低10%-20%因为模型能从电力负荷特征里学到油温变化的因果线索。我自己的经验是如果你换到自己的数据集不知道选哪个模式先跑MS它结合了信息量和目标聚焦两个优势一般不会翻车。S模式适合做baseline对比用来确认你加的特征到底有没有用。4.3 跑一次训练命令、日志与checkpoint落盘先用environment.yml建环境CPU版本可以直接用pip安装基础依赖conda env create -f environment.yml conda activate informer # CPU环境下把requirements里torch相关改成cpu版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu跑训练的命令从main_informer.py的标准写法开始以MS模式、预测24小时为例python main_informer.py \ --model informer \ --data ETTh1 \ --features MS \ --seq_len 126 \ --label_len 64 \ --pred_len 24 \ --d_model 512 \ --n_heads 8 \ --e_layers 2 \ --d_layers 1 \ --d_ff 2048 \ --attn prob \ --factor 5 \ --distil True \ --batch_size 32 \ --learning_rate 0.0001 \ --loss mse \ --train_epochs 10训练日志会实时打印每个epoch的train loss和val loss同时exp_informer.py会在每个epoch结束时做一次验证集评估。训练完成后模型权重自动存到checkpoints下命名规则就是4.1节那个长串。main_informer.py还有几个影响训练行为的开关--do_predict会在验证结束后对测试集做预测并把npy写入results--use_amp开启混合精度训练能省一半显存但需要GPU支持——CPU环境千万别开会直接报错。--lradj控制学习率调度默认type1在训练中期做阶梯下降如果你用自己数据发现loss降不下去改成type2用余弦退火通常能救回来。5. Informer复现避坑5个常见报错与效果翻车记录5.1 维度对不上的祖传报错现象跑main_informer.py时在forward阶段报错类似RuntimeError: The size of tensor a (64) must match the size of tensor b (32)或者mat1 and mat2 shapes cannot be multiplied。原因这个项目里最经典的坑是seq_len和label_len的关系没配对。Informer的decoder输入长度是label_len pred_len而embedding层对序列长度敏感尤其是distilTrue时encoder每层池化减半如果seq_len不能被2整除最后一层的尺寸就乱了。解决我的习惯是seq_len设成2的倍数label_len设成seq_len的一半左右。比如sl126_ll64是安全的但sl127_ll50就等着报错吧。另外检查--cols参数是不是跟你的csv列数匹配数据集换了列数没改cols_data也会炸。5.2 蒸馏层数开太高loss直接变NaN现象训练几轮后loss变成nan或者val loss在第5轮突然跳成无穷大。原因ConvLayer里的MaxPool1d(kernel_size3, stride2)要求输入长度至少为3e_layers4且seq_len32时经过两层蒸馏后长度变8第三层再池化直接非法梯度爆炸。另一个高频原因是learning_rate开太大Adam在d_model512的大模型上承受不了1e-3级别的初始lr。解决先把e_layers设为2保底确认seq_len足够长再加层。学习率从1e-4起步loss爆炸就降一个量级。也可以把--loss从mse换成smape它对异常值的敏感度低一些训练更稳定。5.3 换了数据集标准化和embedding没跟着改现象换成自己的业务数据后指标奇差无比MSE比论文里大了几十倍画出来的预测曲线是一条水平线。原因DatasetETT的freq参数还留在默认的h如果你的数据是分钟级或日级时间特征编码完全错位。更隐蔽的是timeenc0的timeF模式假设数据有周期性日粒度的数据用hour特征纯属加了噪声。解决先看你的数据采样频率小时级用freqh15分钟用freqt日级用freqd。然后重新调seq_len让窗口覆盖足够周期——日数据至少seq_len30覆盖一个月小时数据seq_len168覆盖一周。最后对比featuresM和featuresS的差距如果M模式反而更差说明你加的特征跟目标没有因果关系果断切回S。5.4 pycache和Python版本不一致导致的玄学错误现象代码明明没改换一台机器重新跑就报ModuleNotFoundError或者ValueError: unknown local dtype float16。原因压缩包里带着__pycache__目录和.pyc文件这些是Python 3.9环境下编译的字节码换到3.10或3.11项目里Python加载字节码时会因为magic number不匹配拒绝执行然后报一些看起来八竿子打不着的错误。还有个坑是.pyc里有绝对路径信息多人协作时路径对不上也会炸。解决拿到代码第一步就删掉所有__pycache__目录和.pyc文件——它们对运行没有任何作用留着只会制造玄学问题。命令行一把梭find . -type d -name __pycache__ -exec rm -rf {} find . -name *.pyc -delete环境依赖严格按environment.yml来Python版本用3.9最保险这个项目就是在3.9下写的。5.5 预测长度和标签长度搭配不合理现象模型能跑通但预测结果前16个点跟真值贴得很近后面8个点突然水平漂移看起来像阶梯状。原因label_len64, pred_len24时decoder的前64步直接copy真值模型只需要生成后24步。如果把label_len压到32模型需要更长距离的自回归误差累积效应放大。反过来说pred_len4时大部分输出靠copy模型压根没学会真正的时序外推。解决短预测pl4保持label_lenseq_len/2以上长预测pl24、pl48时label_len至少要有pred_len的2倍。如果显存允许label_len往大了给它本质上是在给模型喂标准答案给越多预测越稳代价是推理时你需要多准备已知真值。6. 验证预测结果并做增量实验从.npy到指标输出的完整回路6.1 结果目录解析true.npy / pred.npy / metrics.npy 各是什么训练跑完results目录下每个实验名对应三个文件。true.npy是测试集真实值pred.npy是模型预测值形状都是(样本数, pred_len, 变量数)metrics.npy只有一个数是整体MSE。用numpy就可以快速检查预测质量import numpy as np pred np.load(results/informer_custom_ftMS_sl126_ll64_pl24_dm512_nh8_el2_dl1_df2048_atprob_fc5_ebtimeF_dtTrue_mxTrue_test_0/pred.npy) true np.load(results/informer_custom_ftMS_sl126_ll64_pl24_dm512_nh8_el2_dl1_df2048_atprob_fc5_ebtimeF_dtTrue_mxTrue_test_0/true.npy) # pred shape: (batch, pred_len, feature_dim) print(pred.shape, true.shape) # 取第一个样本、OT列(索引7)做对比 ot_pred pred[0, :, 7] ot_true true[0, :, 7] print(MSE:, np.mean((ot_pred - ot_true) ** 2))注意pred.npy里存的是标准化之后的值如果你想画跟原始数据同一量纲的曲线得用训练时的scaler做逆变换。data_loader.py里scaler在train段拟合你可以单独写个脚本load回去把scaler.inverse_transform(pred.reshape(-1, feature_dim))再reshape回原形状。不然你画的曲线跟csv里的油温对不上会误判模型效果。还有real_prediction.npy是--do_predict模式对最新一段无真值数据的预测适合做上线前的模拟推理。6.2 加载checkpoint做二次预测的一个小技巧main_informer.py默认从头训练但你已经有了checkpoints/下的权重可以跳过训练直接用# 修改main_informer.py或在exp_informer.py里加一个分支 # 核心是构造args后用ExpInformer类的predict方法 from exp.exp_informer import ExpInformer args ... # 用parse_args()构建保证和checkpoint文件名参数一致 exp ExpInformer(args) exp.model.load_state_dict(torch.load(checkpoints/informer_custom_ftMS_sl126_ll64_pl24_dm512_nh8_el2_dl1_df2048_atprob_fc5_ebtimeF_dtTrue_mxTrue_test_0/checkpoint.pth)) exp.predict(setting, True) # 第二个参数True表示加载已有权重这里的关键是args必须和训练时完全一致包括features、seq_len、label_len这些有一个不一致就会在load_state_dict阶段报缺key的错。我一般会把训练命令存成一个shell脚本预测时直接复用从源头杜绝参数漂移。6.3 进阶调factor与distil把MSE再压一档当你的baseline跑通后把--attn prob改成--attn full跑一次标准Transformer对比MSE。你会发现在长序列场景下ProbSparse不仅速度更快精度往往还反超——因为稀疏筛选相当于隐式正则化过滤掉了噪声query的干扰。在这个基础上做两个小实验factor3和factor7分别跑factor太小保留的query太少会丢信息太大会让ProbSparse退化成标准注意力最优值通常在数据集相关处ETTh1上5就是一个均衡点。distilFalse也值得跑一组对照开启蒸馏会丢失序列细节对pl24这种中长预测可能不利。我自己的习惯是每次改一个参数固定其他所有变量跑完用6.1节的脚本算MSE记录到一张对比表里。Epoch不要设太大Informer在ETTh1上通常10个epoch内就收敛了多跑只是过拟合。从那以后我每次拿到新数据集都会强制走一遍删pycache→生成shell脚本→先跑S模式baseline→再开MS调factor的完整流程这套流程帮我少踩了无数坑希望帮到你。本文还有配套的精品资源点击获取