Skip to content

Informer 调试形参

Abstract

这篇只做一件事:

保存当前用于学习 Informer 代码运行流程的 PyCharm 参数,并保证关键模型分支至少执行一次。

1. PyCharm 配置

Script path

text
D:\1sudyta\1ai-self\aistyle\TFB\scripts\run_benchmark.py

Working directory

text
D:\1sudyta\1ai-self\aistyle\TFB

Environment variables

text
KMP_DUPLICATE_LIB_OK=TRUE

2. Parameters

直接复制下面这一整行到 PyCharm 的 Parameters

text
--config-path rolling_forecast_config.json --data-name-list cif_2016_dataset_1.csv --model-name time_series_library.Informer --adapter transformer_adapter --model-hyper-params "{\"batch_size\":4,\"d_model\":32,\"d_ff\":128,\"e_layers\":2,\"d_layers\":1,\"factor\":3,\"horizon\":6,\"n_heads\":2,\"norm\":true,\"seq_len\":24,\"dropout\":0.0,\"lr\":0.0001,\"num_epochs\":1,\"num_workers\":0,\"output_attention\":0}" --strategy-args "{\"horizon\":6,\"tv_ratio\":0.8,\"train_ratio_in_tv\":0.75,\"stride\":6,\"num_rollings\":2}" --num-workers 1 --timeout 600 --save-path debug\cif1_Informer_rolling_cover_distil
来源

scripts/multivariate_forecast/*_script/Informer.sh 里的正式 benchmark 命令一般是 rolling_forecast_config.json + time_series_library.Informer + transformer_adapter

这里把正式参数缩小成学习版:小数据集、小 seq_len/horizon、小 d_model/d_ffe_layers=2d_layers=1num_epochs=1num_rollings=2

e_layers=2 是刻意设置:Informer 的 ConvLayer distilling 数量是 e_layers - 1,如果写成 e_layers=1,ConvLayer 实际不会跑。

2.1 VSCode 调试配置

VSCode 不把参数写成一整行,而是写进 .vscode/launch.jsonargs 数组。

先在 VSCode 里执行:

text
Ctrl+Shift+P
-> Python: Select Interpreter
-> 选择 D:\Anaconda\envs\tfb\python.exe

然后在仓库根目录创建或修改:

text
D:\1sudyta\1ai-self\aistyle\TFB\.vscode\launch.json

加入下面配置:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "TFB Informer rolling debug",
      "type": "python",
      "request": "launch",
      "program": "${workspaceFolder}\\scripts\\run_benchmark.py",
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal",
      "env": {
        "KMP_DUPLICATE_LIB_OK": "TRUE"
      },
      "args": [
        "--config-path", "rolling_forecast_config.json",
        "--data-name-list", "cif_2016_dataset_1.csv",
        "--model-name", "time_series_library.Informer",
        "--adapter", "transformer_adapter",
        "--model-hyper-params", "{\"batch_size\":4,\"d_model\":32,\"d_ff\":128,\"e_layers\":2,\"d_layers\":1,\"factor\":3,\"horizon\":6,\"n_heads\":2,\"norm\":true,\"seq_len\":24,\"dropout\":0.0,\"lr\":0.0001,\"num_epochs\":1,\"num_workers\":0,\"output_attention\":0}",
        "--strategy-args", "{\"horizon\":6,\"tv_ratio\":0.8,\"train_ratio_in_tv\":0.75,\"stride\":6,\"num_rollings\":2}",
        "--num-workers", "1",
        "--timeout", "600",
        "--save-path", "debug\\cif1_Informer_rolling_cover_distil"
      ]
    }
  ]
}

关键区别:

text
PyCharm Parameters:
一整行字符串,JSON 需要用 \" 转义。

VSCode args:
一个参数一个数组元素,JSON 参数整体作为一个字符串元素。

2.2 为什么这版不是 e_layers=1

Informer 构造 encoder 时:

python
self.encoder = Encoder(
    [
        EncoderLayer(...)
        for l in range(configs.e_layers)
    ],
    (
        [ConvLayer(configs.d_model) for l in range(configs.e_layers - 1)]
        if configs.distil and ("forecast" in configs.task_name)
        else None
    ),
    norm_layer=torch.nn.LayerNorm(configs.d_model),
)

当前 adapter 默认:

text
distil = True
task_name = short_term_forecast

所以:

text
conv_layers 数量 = e_layers - 1

如果旧参数是:

text
e_layers = 1

那么:

text
EncoderLayer 数量 = 1
ConvLayer 数量 = 0

Encoder.forward(...) 虽然进入 if self.conv_layers is not None 分支,但这里不会执行任何 distilling:

python
for i, (attn_layer, conv_layer) in enumerate(zip(self.attn_layers, self.conv_layers)):
    ...

因为:

text
zip([EncoderLayer_0], []) = []

所以旧版参数没有真正跑到 ConvLayer.forward(...)

修正版:

text
e_layers = 2

会得到:

text
EncoderLayer 数量 = 2
ConvLayer 数量 = 1

执行顺序变成:

text
EncoderLayer_0
-> ConvLayer_0   ← distilling 至少跑 1 次
-> EncoderLayer_1
-> final LayerNorm

3. 当前参数含义

数据与任务

  • --data-name-list cif_2016_dataset_1.csv
    • 使用很小的单变量数据,方便看清 Informer 的代码流。
  • --config-path rolling_forecast_config.json
    • 使用 rolling forecast 评测协议。
  • --strategy-args
    • horizon = 6
      • 每次预测未来 6 步。
    • tv_ratio = 0.8
      • 前 80% 作为 train/valid 区域。
    • train_ratio_in_tv = 0.75
      • train/valid 区域内部再切训练段。
    • stride = 6
      • rolling forecast 每次测试窗口往后移动 6 步。
    • num_rollings = 2
      • 只滚 2 次,减少调试负担。

模型

  • --model-name time_series_library.Informer
    • 当前调试模型是 Informer。
  • --adapter transformer_adapter
    • 走 TFB 的 transformer 统一 adapter。
  • seq_len = 24
    • encoder 输入历史窗口长度。
  • horizon = 6
    • 会被 Config 映射成 pred_len = 6
  • d_model = 32
    • embedding 后每个 token 的隐藏维度。
  • n_heads = 2
    • 多头注意力 head 数。
    • 每个 head 的维度是 d_model / n_heads = 16
  • factor = 3
    • ProbAttention 采样 query/key 时使用的稀疏采样系数。
  • e_layers = 2
    • encoder 放 2 层,保证 ConvLayer distilling 至少执行 1 次。
  • d_layers = 1
    • decoder 只放 1 层,便于调试。
  • d_ff = 128
    • EncoderLayer / DecoderLayer 内部 FFN 隐藏维度。
  • dropout = 0.0
    • 调试时关闭 dropout,减少随机性。
  • output_attention = 0
    • 不额外返回 attention 矩阵,先看主输出。
label_len 不建议在这里手动作为主参数理解

Informer 的 decoder 输入长度是:

text
label_len + pred_len

但在这个 TFB 流程里,forecast_fit(...) 会根据数据形态自动补齐 label_len

  • 单变量数据:label_len = horizon
  • 多变量数据:label_len = seq_len // 2

当前 cif_2016_dataset_1.csv 是单变量,所以调试时重点看:

text
label_len = horizon = 6
dec_input.length = label_len + horizon = 12

4. 当前小例子的关键 shape

当前数据是单变量,所以模型初始化前后会得到:

text
C = enc_in = dec_in = c_out = 1

当前 batch 里大概率会看到:

text
input / x_enc:         (4, 24, 1)
input_mark / x_mark:   (4, 24, time_feature_dim)

target:                (4, 12, 1)
target_mark:           (4, 12, time_feature_dim)

dec_input:             (4, 12, 1)

其中:

text
seq_len = 24
horizon = pred_len = 6
label_len = 6
decoder 输入长度 = label_len + pred_len = 12

进入 Informer 后的主 shape:

text
x_enc:          (4, 24, 1)
x_mark_enc:     (4, 24, time_feature_dim)
x_dec:          (4, 12, 1)
x_mark_dec:     (4, 12, time_feature_dim)

enc_embedding:  (4, 24, 32)
dec_embedding:  (4, 12, 32)

encoder out:    (4, 13, 32)
decoder out:    (4, 12, 1)
final output:   (4, 6, 1)

Encoder 长度从 24 变成 13 的原因是 ConvLayer.forward(...)

python
x = self.downConv(x.permute(0, 2, 1))   # Conv1d 后长度 24 -> 26
x = self.norm(x)
x = self.activation(x)
x = self.maxPool(x)                     # MaxPool1d 后长度 26 -> 13
x = x.transpose(1, 2)

所以当前 encoder 主链是:

text
enc_embedding:     (4, 24, 32)
EncoderLayer_0:    (4, 24, 32)
ConvLayer_0:       (4, 24, 32) -> (4, 13, 32)
EncoderLayer_1:    (4, 13, 32)
LayerNorm:         (4, 13, 32)
time_feature_dim

DataEmbedding 使用 embed="timeF",时间特征维度由 freq 决定。

这个小数据集的 date 是数字序列,TFB 加载后会转成 datetime,再由 forecast_fit(...) 推断 freq。调试时不需要先记死 time_feature_dim,在断点里直接看 input_mark.shape 最准确。

5. Informer 和 PatchTST 的调试差异

PatchTST 的主链是:

text
x_enc
-> PatchEmbedding
-> Encoder
-> FlattenHead
-> output

Informer 的主链是:

text
x_enc + x_mark_enc
-> enc_embedding
-> encoder

x_dec + x_mark_dec
-> dec_embedding
-> decoder(self-attn + cross-attn)
-> projection
-> output

所以 Informer 的断点重点不是 patch,而是:

text
1. dec_input 怎样构造
2. x_mark_enc / x_mark_dec 怎样进入 DataEmbedding
3. Encoder 里的 ProbAttention
4. Encoder 里的 ConvLayer distilling
5. Decoder 里的 self-attention 和 cross-attention

5.1 当前参数覆盖的关键路径

这组参数保证下面这些模型内部结构都会实际执行:

结构是否执行原因
Encoder.forward 的 distilling 分支distil=Truetask_name 包含 forecast
EncoderLayer_0e_layers=2
ConvLayer_0e_layers - 1 = 1
EncoderLayer_1distilling 后最后单独执行 attn_layers[-1]
Decoder.forward 循环d_layers=1
DecoderLayer.self_attentiondecoder layer 固定执行
DecoderLayer.cross_attentiondecoder layer 固定执行
ProbAttention._prob_QKencoder/decoder attention 都使用 ProbAttention

6. 断点顺序

第一轮只看代码流,先不要看论文。

  1. ts_benchmark/baselines/time_series_library/adapters_for_transformers.py
    • TransformerAdapter._process(...)
    • dec_input = cat(label_len 历史尾部, horizon 零占位)
  2. ts_benchmark/baselines/time_series_library/models/Informer.py
    • forward(...)
    • 看进入 short_term_forecast(...)
  3. ts_benchmark/baselines/time_series_library/models/Informer.py
    • short_forecast(...)
    • 看标准化、embedding、encoder、decoder、反标准化。
  4. ts_benchmark/baselines/time_series_library/layers/Embed.py
    • DataEmbedding.forward(...)
    • value_embedding + temporal_embedding + position_embedding
  5. ts_benchmark/baselines/time_series_library/layers/Transformer_EncDec.py
    • Encoder.forward(...)
    • 当前 e_layers=2,重点看 EncoderLayer_0 -> ConvLayer_0 -> EncoderLayer_1
  6. ts_benchmark/baselines/time_series_library/layers/SelfAttention_Family.py
    • AttentionLayer.forward(...)
    • Q/K/V projection -> view(B,L,H,-1)
  7. ts_benchmark/baselines/time_series_library/layers/SelfAttention_Family.py
    • ProbAttention.forward(...)
    • _prob_QK -> _get_initial_context -> _update_context
  8. ts_benchmark/baselines/time_series_library/layers/Transformer_EncDec.py
    • ConvLayer.forward(...)
    • permute -> Conv1d -> BatchNorm1d -> ELU -> MaxPool1d -> transpose,长度 24 -> 13
  9. ts_benchmark/baselines/time_series_library/layers/Transformer_EncDec.py
    • Decoder.forward(...)
    • 看 decoder layer 循环。
  10. ts_benchmark/baselines/time_series_library/layers/Transformer_EncDec.py
  • DecoderLayer.forward(...)
  • 看 self-attention、cross-attention、FFN。

7. 当前学习主线

text
run_benchmark
-> pipeline
-> eval_model
-> RollingForecast._eval_batch
-> forecast_fit
-> _process
-> Informer.forward
-> Informer.short_forecast
-> DataEmbedding encoder侧
-> Encoder
-> EncoderLayer_0
-> ProbAttention
-> ConvLayer distilling
-> EncoderLayer_1
-> ProbAttention
-> DataEmbedding decoder侧
-> Decoder
-> self-attention + cross-attention
-> projection
-> output[:, -pred_len:, :]

这一轮的第一性:

先看清 Informer 怎样把 encoder 历史窗口和 decoder 半真实半占位窗口送进 encoder-decoder 结构,再用 ProbSparse attention 和 decoder cross-attention 生成未来窗口。

8. 最小数学例子的对应关系

当前命令的最小理解参数:

text
B = 4
seq_len = 24
label_len = 6
pred_len = 6
C = 1
d_model = 32
n_heads = 2
head_dim = 16
e_layers = 2
d_layers = 1

decoder 输入构造:

text
target:    (4, 12, 1)
历史部分: target[:, :6, :]      -> (4, 6, 1)
未来占位: zeros_like(last 6步)   -> (4, 6, 1)
拼接:     cat(dim=1)            -> (4, 12, 1)

attention 拆头:

text
enc_embedding: (4, 24, 32)
Q/K/V Linear:  (4, 24, 32)
view:          (4, 24, 2, 16)

encoder distilling:

text
EncoderLayer_0 output: (4, 24, 32)
ConvLayer_0:
  permute:             (4, 24, 32) -> (4, 32, 24)
  Conv1d:              (4, 32, 24) -> (4, 32, 26)
  MaxPool1d:           (4, 32, 26) -> (4, 32, 13)
  transpose:           (4, 32, 13) -> (4, 13, 32)
EncoderLayer_1 output: (4, 13, 32)

decoder cross-attention:

text
query 来自 decoder:  (4, 12, 2, 16)
key/value 来自 encoder: (4, 13, 2, 16)
score: (4, 2, 12, 13)

最终输出:

text
decoder projection: (4, 12, 1)
取最后 pred_len:   (4, 6, 1)

*记录并在线阅读我的笔记*