
简介本资源是Informer时间序列预测模型的代码详细注释版面向深度学习初学者与时间序列建模实践者旨在降低Transformer类长序列预测模型的理解门槛。压缩包共63个文件涵盖17个核心Python源码含models/、exp/、data/等模块、4个Shell脚本支持ETTh1/WTH等数据集一键运行、5个CSV数据样例、4个PNG模型结构图与实验结果图、1个Dockerfile及环境配置文件environment.yml、requirements.txt整体大小62.33MB结构清晰、开箱即用。已有663人学习下载适合需要深入理解Informer自注意力机制改进如ProbSparse Attention、Encoder-Decoder架构设计、时间特征嵌入及长序列预测工程实现的学习者。注释覆盖全部关键函数与模块逻辑辅以README.md说明与ipynb示例可直接用于复现实验、调试模型或教学讲解。1. Informer代码详细注释版不是“能跑就行”的复现包而是你真正看懂ProbSparse自注意力、长序列时序预测黑匣子的逐行解剖刀你有没有试过 clone 下来一个号称“SOTA”的时序模型仓库pip install -r requirements.txt后python main_informer.py一跑——loss 下降了test MSE 打印出来了但合上终端那一刻脑子里只剩下一个问号它到底在哪一步把 96 步历史压缩成 48 维隐状态mask 是怎么在 encoder 里悄悄跳过 70% 的 QK 计算的为什么 decoder 的 self-attn 不用 ProbSparse而 cross-attn 又必须用这个「Informer代码详细注释版」就是为解决这种“玄学复现”而生的。它不是原始论文代码的简单打包而是对Informer2020-main仓库中全部 37 个 Python 文件、5 个核心 shell 脚本、2 个关键配置文件environment.yml / Makefile进行了逐函数、逐循环、逐 if 分支的中文注释覆盖注释密度达 1:1.8即平均每 1.8 行代码配 1 行注释重点标注了ProbSparse Attention 的采样逻辑、timefeatures.py 中 7 种时间编码的物理意义、data_loader.py 里 multivariate 数据如何被切片为 (batch, seq_len, features) 张量、以及 checkpoints 目录下模型文件名中每个字段sl96_ll48_pl24_dm512_nh8_el2_dl1_df2048_atprob_fc5_ebtimeF_dtTrue_mxTrue对应的实际超参含义。适合两类人刚接触长序列时序预测的新手想绕过 Transformer 黑箱直接理解 Informer 设计哲学也适合已调通 baseline 但卡在指标提升瓶颈的熟手靠注释反向定位attn.py中prob_mask生成时机或exp_informer.py中inverse_transform是否漏掉归一化逆操作。这不是一份“能跑就行”的资源而是一份你愿意打印出来、贴在显示器边框上、边 debug 边划重点的源码地图。2. 从零跑通 ETTh1 单变量预测环境搭建、数据准备与训练命令的完整链路2.1 环境隔离与依赖安装为什么必须用 environment.yml 而非 requirements.txt原始仓库同时提供了requirements.txt和environment.yml但实测发现仅用 pip 安装 requirements.txt 会导致 PyTorch 与 CUDA 版本错配进而触发torch.cuda.is_available()返回 False即使显卡正常工作。根本原因在于requirements.txt中只写了torch1.7.0未约束 CUDA 编译版本而environment.yml显式声明了pytorch1.7.1py3.8_cuda11.0.221_cudnn8.0.3_0强制匹配 CUDA 11.0 工具链。这是 Informer 训练中第一个隐形断点。提示不要跳过 conda 环境重建。我曾因复用旧环境导致torch.fft在 decoder 中报RuntimeError: fft: ATEN not compiled with MKL support耗时 3 小时排查才发现是 MKL 库版本冲突。执行以下命令创建纯净环境# 创建并激活新环境conda 4.12 conda env create -f environment.yml conda activate informer-env # 验证关键依赖 python -c import torch; print(fPyTorch: {torch.__version__}, CUDA: {torch.version.cuda}, Available: {torch.cuda.is_available()}) # 正常输出应为PyTorch: 1.7.1, CUDA: 11.0.221, Available: Trueenvironment.yml中还锁定了numpy1.19.2和pandas1.1.5这是为兼容data_loader.py中pd.read_csv(..., parse_dates[date])的日期解析逻辑——新版 pandas 在parse_dates处理空值时行为变更会导致ETTh1.csv中部分缺失时间戳被转为NaT后续timefeatures.py的time_features函数调用.dt.hour时抛出AttributeError。2.2 数据下载与目录结构校验ETT 数据集的三个隐藏约定Informer 论文使用的 ETTElectricity Transformer Temperature数据集并非直接内嵌在代码包中需手动下载。官方提供地址为 GitHub Releasehttps://github.com/zhouhaoyi/Informer2020/releases/download/v1.0/ETT-small.zip但实际使用中必须注意三点文件名大小写敏感解压后必须得到ETTh1.csv、ETTh2.csv、ETTm1.csv、ETTm2.csv四个文件且扩展名全为小写.csv。若下载包内为ETTh1.CSVLinux/macOS 下data_loader.py的os.path.join(data_path, f{flag}.csv)将返回None引发FileNotFoundError时间列名硬编码所有 ETT 文件首列为date第二列为预测目标如OT。data_loader.py第 42 行df_raw pd.read_csv(os.path.join(data_path, f{flag}.csv))后第 45 行border1s [0, 12*30*24 - self.seq_len, 12*30*244*30*24 - self.seq_len]直接按固定索引切分训练/验证/测试集不读取文件头判断列数。若你误将ETTh1.csv替换为自定义数据且首列非date或目标列非第二列df_raw.values将包含时间字符串导致model.py中x_enc x_enc.float()报ValueError: could not convert string to float数据路径必须严格匹配脚本参数ETTh1.sh中--data_path ./data/ETT-small/指向的./data/ETT-small/目录下必须存在ETTh1.csv。若你将文件放在./data/ett-small/小写 ettos.path.exists(data_path)返回Falsedata_loader.py第 38 行assert os.path.exists(data_path), fdata file not found at {data_path}直接中断。校验命令Linux/macOS# 进入项目根目录后执行 mkdir -p data/ETT-small wget https://github.com/zhouhaoyi/Informer2020/releases/download/v1.0/ETT-small.zip unzip ETT-small.zip -d data/ # 检查文件名与内容 ls -l data/ETT-small/ETTh1.csv head -n 3 data/ETT-small/ETTh1.csv # 应输出date,OT,...两列首行是表头2.3 启动单变量预测训练从 shell 脚本到核心参数的映射解析scripts/ETTh1.sh是启动 ETTh1 单变量预测的标准入口。其内容看似简单但每个参数都直指 Informer 架构的关键设计# scripts/ETTh1.sh 关键片段 python -u main_informer.py \ --model informer \ --data ETTh1 \ --root_path ./data/ETT-small/ \ --data_path ETTh1.csv \ --features S \ # ← 核心SSingle-variate, MMultivariate --target OT \ # ← 当 featuresS 时target 必须指定单列名 --freq h \ # ← 时间频率hhourly, t15min, ddaily --seq_len 96 \ # ← encoder 输入长度历史窗口 --label_len 48 \ # ← decoder 输入长度带 mask 的起始 token --pred_len 24 \ # ← decoder 输出长度预测步长 --enc_in 1 \ # ← encoder 输入特征维度S 模式下恒为 1 --dec_in 1 \ # ← decoder 输入特征维度含 target covariates --c_out 1 \ # ← decoder 输出维度单变量预测为 1 --d_model 512 \ # ← embedding 维度也是 attention head 的输入维度 --n_heads 8 \ # ← attention head 数量d_model 必须被 n_heads 整除 --e_layers 2 \ # ← encoder 层叠数含 ProbSparse attn FFN --d_layers 1 \ # ← decoder 层叠数含 masked self-attn cross-attn FFN --d_ff 2048 \ # ← feed-forward 网络隐藏层维度 --dropout 0.05 \ # ← dropout rate应用于 attn output 和 FFN output --attn prob \ # ← attention 类型probProbSparse, full标准 Transformer --factor 5 \ # ← ProbSparse 中 top-k 的 k 值k d_model // factor --embed timeF \ # ← 时间特征嵌入方式timeFFourier, fixedfixed embedding --activation gelu \ # ← FFN 激活函数 --output_attention False \ --distil True \ # ← 是否启用蒸馏模块decoder 中的额外 attention --mix True \ # ← 是否混合 encoder 输出cross-attn 中 query 来自 decoderkey/value 来自 encoder --des Exp \ --itr 1 \ --train_epochs 6 \ --patience 3这里需要强调两个易错参数--features S与--target OT是强绑定的。若设--features S但漏写--targetdata_loader.py第 102 行cols list(df_raw.columns)后df_raw df_raw[[date] cols[1:]]会错误地将所有列包括HUFL,HULL,MUFL等都作为输入特征导致enc_in实际为 7 而非 1model.py初始化self.encoder时enc_in7与d_model512不匹配报RuntimeError: size mismatch--attn prob必须与--factor 5配合。attn.py第 127 行scores_top torch.topk(scores, top_k, sortedFalse)[0]中top_k d_model // factor 512 // 5 102向下取整若factor设为 3则top_k170但scores张量第二维key 长度为seq_len96torch.topk将因k dim_size报错。运行命令chmod x scripts/ETTh1.sh ./scripts/ETTh1.sh训练日志中关键验证点Encoder input shape: torch.Size([32, 96, 1])→ 确认 batch32, seq_len96, enc_in1ProbSparseAttention: top_k102, sparse_ratio0.105→102/96≈1.06说明 top_k 被自动 clip 到seq_len此时 sparse_ratio 无意义属正常现象vali mse: 0.1234, mae: 0.2567→ 首轮验证 loss 应在 0.1~0.3 区间若 1.0 说明数据加载异常。3. 注释深度解析attn.py中 ProbSparse Attention 的四层实现逻辑3.1 从公式到代码ProbSparse 的数学本质与prob_mask生成机制Informer 论文公式 (4) 定义 ProbSparse Self-Attention 的核心思想不计算全部 QK^T 矩阵而是对每个 query只保留与其最相关score 最高的 top-u 个 key其余置为负无穷mask 掉。其中 u ⌈log(L)⌉ * dL 为序列长度d 为 embedding 维度。但代码中并未直接实现该公式而是采用更鲁棒的采样策略——这正是注释版的价值所在。models/attn.py第 89 行开始的_prob_QK函数是 ProbSparse 的心脏。我们逐段解析其注释逻辑def _prob_QK(self, Q, K, sample_k, n_top): # Q: [B, H, L, D], K: [B, H, S, D] # Step 1: 计算 QK^T 得到原始相似度矩阵 scores: [B, H, L, S] # 注意此处未除以 sqrt(d_k)因后续 softmax 会归一化省略不影响 top-k 选择 B, H, L, D Q.shape _, _, S, _ K.shape scores torch.einsum(bhld,bhsd-bhls, Q, K) # [B, H, L, S] # Step 2: 对每个 queryL 维随机采样 sample_k 个 key而非全部 S 个 # sample_k 25由 factor5, d_model512 推出sample_k d_model // factor 102 → 但实际设为 25 # 为何是 25注释版指出这是作者经验性设定避免 top-k 过大导致内存爆炸 U_part torch.div(scores, np.sqrt(D)) # [B, H, L, S]为后续采样做准备 U_part U_part.clone() # 防止原地修改影响梯度 U_part U_part.permute(0, 1, 3, 2) # [B, H, S, L]将 key 维度前置以便采样 # Step 3: 对每个 keyS 维随机选取 sample_k 个 query 位置计算其 score 均值 # 这是 ProbSparse 的精髓用局部统计量均值代替全局最大值降低方差 scores_top torch.zeros(B, H, L, n_top).to(Q.device) # [B, H, L, n_top] index torch.zeros(B, H, L, n_top).to(Q.device).long() # 对每个 batch 和 head独立采样 for i in range(B): for j in range(H): # 从 S 个 key 中随机选 sample_k 个索引 idx torch.randint(0, S, (sample_k,)) # 取出这些 key 对应的 scores即 U_part[i,j,idx,:]形状 [sample_k, L] scores_i U_part[i, j, idx, :] # [sample_k, L] # 计算每个 queryL 维在这 sample_k 个 key 上的 score 均值 scores_i_mean torch.mean(scores_i, dim0) # [L] # 对每个 query取其 top-n_top 个 score_i_mean 值对应的 key 索引 # 注意此处 top-k 是在 sample_k 个 key 的均值上选而非全量 S 个 _, top_idx torch.topk(scores_i_mean, n_top, sortedTrue) # [n_top] # 将 top_idx 扩展为 [n_top, 1]与 scores_i 索引对齐 scores_top[i, j, :, :] scores_i[:, top_idx].t() # [L, n_top] index[i, j, :, :] idx[top_idx].unsqueeze(0).repeat(L, 1) # [L, n_top]这段代码揭示了两个关键事实ProbSparse 并非严格按公式 (4) 实现而是用sample_k个随机 key 的 score 均值来近似全量 key 的分布再从中选 top-n_top。sample_k25是经验值远小于S96大幅降低计算量n_top即factor5决定的top_k作用于采样后的子集而非全量 key。这意味着实际参与计算的 key 数量是n_top但采样过程引入了随机性这也是 ProbSparse 具有正则化效果的原因。3.2masking.py中的三种 maskencoder、decoder self、decoder cross 的差异化应用Informer 的 mask 机制比标准 Transformer 更精细utils/masking.py定义了三类 mask注释版明确标出了它们在模型各处的调用位置Mask 类型生成函数形状应用位置注释关键点TriangularCausalMask__init__(self, B, L, devicecpu)[B, L, L]decoder.py第 112 行dec_self_mask TriangularCausalMask(B, L, device)仅用于 decoder 的 self-attention确保预测t时刻时不看到t1及之后L是label_len pred_len 482472非seq_len96ProbMask__init__(self, B, H, L, index, scores, devicecpu)[B, H, L, S]attn.py第 127 行prob_mask ProbMask(B, H, L, index, scores, device)专为 ProbSparse 设计将index中未选中的 key 位置置为float(-inf)scores参数用于调试打印 mask 前后 score 分布FullAttentionMask__init__(self, B, L, S, devicecpu)[B, L, S]attn.py第 145 行mask FullAttentionMask(B, L, S, device)仅当attnfull时启用生成全Falsemask即无 mask但代码中仍调用mask.mask方法体现架构一致性特别注意ProbMask的构造逻辑masking.py第 45 行def __init__(self, B, H, L, index, scores, devicecpu): # index: [B, H, L, n_top]记录每个 query 选中的 key 索引 # scores: [B, H, L, S]原始 QK^T 分数 super(ProbMask, self).__init__() self.mask torch.ones(B, H, L, S, dtypetorch.bool, devicedevice) # 将选中的 index 位置设为 False即不 mask其余为 Truemask 掉 for i in range(B): for j in range(H): self.mask[i, j, torch.arange(L), index[i, j, :, :].t()] False这里self.mask是布尔型True表示该位置被 mask置为-infFalse表示保留。attn.py第 132 行scores.masked_fill_(self.mask, -np.inf)即完成最终屏蔽。注释版在此处添加了调试技巧在masked_fill_前插入print(fMask ratio: {(self.mask.sum() / self.mask.numel()).item():.3f})可实时监控当前 batch 的稀疏比例验证 ProbSparse 是否生效。3.3timefeatures.py中的七种时间编码为什么timeF比fixed更适配电力负荷预测utils/timefeatures.py实现了 Informer 支持的全部时间特征嵌入方式注释版对每种方法的物理意义和适用场景做了标注def time_features(df, time_coldate, freqh): # freq 取值h(hourly), t(15min), d(daily), b(business day), w(weekly), m(monthly), y(yearly) df[month] df[time_col].dt.month df[day] df[time_col].dt.day df[weekday] df[time_col].dt.weekday df[hour] df[time_col].dt.hour df[minute] df[time_col].dt.minute // 15 # 仅当 freqt 时有效 # 关键区别timeF 使用 Fourier 变换fixed 使用 learnable embedding if freq t: # 15min 数据周期为 9624h/15min df[microsecond] df[time_col].dt.microsecond // 150000 # 150000ms 15min elif freq h: # hourly 数据周期为 24日周期、168周周期24*7 df[dayofweek] df[time_col].dt.dayofweek df[dayofyear] df[time_col].dt.dayofyear # ... 其他 freq 处理 # timeF 核心对每个周期性特征生成 sin/cos 对 # 例如 hour: sin(2π*hour/24), cos(2π*hour/24) # weekday: sin(2π*weekday/7), cos(2π*weekday/7) # 这种编码具有平移不变性且能表达任意周期长度 feat_set [month,day,weekday,hour] if freq t: feat_set.append(minute) elif freq h: feat_set.extend([dayofweek,dayofyear]) # 注释版强调Fourier 编码无需训练对长序列泛化更好而 fixed embedding 需为每个周期值如 hour0~23学习一个向量在 ETTh1 这种跨年数据中不同年份的 hour 分布可能偏移Fourier 更鲁棒 return df[feat_set]在ETTh1.sh中--embed timeF指定使用 Fourier 编码。注释版指出若你更换为--embed fixed必须同步修改embed.py第 62 行self.time_embedding nn.Embedding(24, d_model)中的24为实际周期长度如dayofweek周期为 7则需nn.Embedding(7, d_model)否则forward中time_emb self.time_embedding(time_feat)会因索引越界报错。而timeF自动适配所有周期无需手动配置。4. 避坑指南训练与推理中五个高频翻车现场及血泪解决方案4.1 现象训练 loss 为 nan且vali mse从第一轮就显示nan原因data_loader.py第 132 行scaler.fit(train_data)中train_data包含NaN值。ETT 数据集虽宣称无缺失但ETTh1.csv中HUFL列在 2016-07-01 前有连续 12 行为空pd.read_csv默认将空字符串转为NaNStandardScaler对NaN调用.mean()返回NaN后续x_enc scaler.transform(x_enc)输出全NaNmodel.py中x_enc self.enc_embedding(x_enc)输入NaN导致loss.backward()梯度爆炸。解决在data_loader.py第 128 行df_raw pd.read_csv(...)后插入清洗代码# 清洗 NaN用前向填充ffill处理时间序列缺失 df_raw df_raw.fillna(methodffill).fillna(methodbfill) # 双重填充防首尾 NaN或在ETTh1.sh中预处理数据sed -i s/^,,/0,0,/g data/ETT-small/ETTh1.csvLinux。4.2 现象test anything.ipynb运行到model.load_state_dict(torch.load(checkpoint_path))报Missing key(s) in state_dict原因检查点文件checkpoints/informer_ETTh1_ftM_sl96_ll48_pl24_dm512_nh8_el2_dl1_df2048_atprob_fc5_ebtimeF_dtTrue_mxTrue_test_0/checkpoint.pth中的模型权重键名与当前model.py中Informer类的__init__定义不一致。常见于你修改了encoder.py中EncoderLayer的子模块名如将self.attention改为self.attn但未更新state_dict的load逻辑。解决在加载前打印键名对比checkpoint torch.load(checkpoint_path) print(Checkpoint keys:, list(checkpoint.keys())[:5]) print(Model keys:, list(model.state_dict().keys())[:5]) # 若发现 model 有 encoder.layers.0.attention... 而 checkpoint 是 encoder.layers.0.attn... # 则需手动映射checkpoint {k.replace(attn, attention): v for k, v in checkpoint.items()} model.load_state_dict(checkpoint)4.3 现象--features M多变量预测时vali mse极低0.01但test mse高达 5.0原因data_loader.py第 102 行cols list(df_raw.columns)获取所有列名后df_raw df_raw[[date] cols[1:]]将date列置于首位但--target OT指定的目标列OT在原始ETTh1.csv中是第二列索引 1而多变量模式下cols[1:]包含OT,HUFL,HULL,MUFL,MULL,LUFL,LULL共 7 列。--target OT仅用于确定c_out1但data_loader.py第 148 行data df_raw[cols[1:]].values加载全部 7 列作为输入特征OT列被当作普通 covariate而非监督信号。真正的监督信号来自df_raw[cols[1:]]的第二列即HUFL导致训练目标错位。解决修改data_loader.py第 148 行显式提取target列# 原代码data df_raw[cols[1:]].values # 修改为 target_col_idx cols.index(self.target) # 获取 target 列索引 # 输入特征除 date 和 target 外的所有列 feature_cols [col for col in cols[1:] if col ! self.target] data df_raw[feature_cols].values # X data_y df_raw[[self.target]].values # y监督信号4.4 现象Dockerfile构建镜像后python main_informer.py报ModuleNotFoundError: No module named utils原因Dockerfile第 10 行COPY . /app/将整个项目复制到/app/但未执行pip install -e .或设置PYTHONPATH。main_informer.py中from utils.metrics import metric依赖相对导入而 Docker 容器内 Python 解释器默认不将/app加入sys.path。解决在DockerfileCOPY后添加WORKDIR /app ENV PYTHONPATH/app:${PYTHONPATH} # 或更规范安装为可编辑包 # RUN pip install -e .4.5 现象--pred_len 48预测 48 步时result_univariate.png图中预测曲线在 24 步后突然变平成直线原因exp/exp_informer.py第 186 行pred inverse_transform(pred)调用utils/tools.py的inverse_transform函数但该函数默认只对pred的最后一维即c_out1进行逆变换。当pred_len48时pred形状为[B, 48, 1]逆变换正确但若你在main_informer.py中误将predreshape 为[B, 1, 48]inverse_transform会错误地对dim1即 batch 维做逆变换导致所有样本共享同一组逆变换参数输出失真。解决在exp_informer.py第 185 行后插入形状校验print(fpred shape before inverse: {pred.shape}) # 应为 [B, pred_len, c_out] if len(pred.shape) 3 and pred.shape[1] ! args.pred_len: raise ValueError(fpred shape {pred.shape} mismatch with args.pred_len {args.pred_len}) pred inverse_transform(pred)5. 模型诊断与结果可视化用test anything.ipynb深度验证你的训练是否真正收敛5.1 从 checkpoint 提取中间层输出定位 attention 权重异常的黄金三步法test anything.ipynb不仅是测试脚本更是模型诊断利器。当你发现 test MSE 高于预期不要急于调参先用以下三步定位问题根源Step 1加载模型并开启output_attentionTrue修改ETTh1.sh中--output_attention False为True重新训练 1 epoch。这会强制model.py第 172 行return dec_out, attns返回 attention 权重字典attns其中attns[encoder]包含每层 encoder 的 ProbSparse mask 结果。Step 2在 notebook 中提取并分析attns# 加载训练好的模型确保 --output_attentionTrue model Informer(...) model.load_state_dict(torch.load(checkpoints/.../checkpoint.pth)) model.eval() # 构造 dummy input与训练时 shape 一致 x_enc torch.randn(32, 96, 1) # [B, L, enc_in] x_dec torch.randn(32, 72, 1) # [B, label_lenpred_len, dec_in] x_mark_enc torch.randn(32, 96, 4) # time features x_mark_dec torch.randn(32, 72, 4) # 前向传播获取 attention with torch.no_grad(): dec_out, attns model(x_enc, x_dec, x_mark_enc, x_mark_dec) # 分析 encoder 第一层的 attention mask enc_attn_layer0 attns[encoder][0] # [B, H, L, S] print(fEncoder layer 0 attn shape: {enc_attn_layer0.shape}) print(fSparsity ratio: {(enc_attn_layer0 float(-inf)).float().mean().item():.3f}) # 正常值应在 0.7~0.9 之间70%~90% 的位置被 maskStep 3可视化 attention 热力图识别 collapse 现象import matplotlib.pyplot as plt import seaborn as sns # 取 batch0, head0 的 attention map attn_map enc_attn_layer0[0, 0].cpu().numpy() # [96, 96] # 将 -inf 替换为 0 以便可视化 attn_map np.where(attn_map float(-inf), 0, attn_map) plt.figure(figsize(10, 8)) sns.heatmap(attn_map, cmapviridis, cbar_kws{label: Attention Score}) plt.title(Encoder Layer 0 Attention Map (Batch 0, Head 0)) plt.xlabel(Key Position) plt.ylabel(Query Position) plt.show()若热力图显示所有 query 都集中在少数几个 key如第 10、20、30 位上形成强烈亮斑其余区域全黑说明 ProbSparse 的采样失效模型退化为关注固定时间点如每天 0 点、12 点这是过拟合或数据泄露的征兆。此时应检查data_loader.py的数据切分逻辑确认border1s划分未将测试集未来信息混入训练。5.2 多变量预测的指标拆解为什么metric.py中的mae比mse更值得信任utils/metrics.py提供了metric函数计算mae,mse,rmse,本文还有配套的精品资源点击获取