Skip to content

FITS 调试形参

Abstract

这篇只做一件事:

保存用于学习 FITS 代码运行流程的 PyCharm / VSCode 参数,并保证模型内部关键循环至少执行一次。


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 fits.FITS --model-hyper-params "{\"batch_size\":2,\"seq_len\":48,\"horizon\":6,\"base_T\":24,\"H_order\":2,\"individual\":false,\"norm\":true,\"lr\":0.001,\"num_epochs\":1,\"num_workers\":0,\"loss\":\"MSE\",\"patience\":3}" --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_FITS_rolling_min"

2.1 VSCode 调试配置

在仓库根目录的 .vscode/launch.json 中加入(或替换现有配置):

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "TFB FITS rolling debug",
      "type": "debugpy",
      "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", "fits.FITS",
        "--model-hyper-params", "{\"batch_size\":2,\"seq_len\":48,\"horizon\":6,\"base_T\":24,\"H_order\":2,\"individual\":false,\"norm\":true,\"lr\":0.001,\"num_epochs\":1,\"num_workers\":0,\"loss\":\"MSE\",\"patience\":3}",
        "--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_FITS_rolling_min"
      ]
    }
  ]
}
FITS 与 Transformer 模型的关键区别

FITS 不需要 --adapter transformer_adapter 参数(它自己继承 DeepForecastingModelBase,不走 TransformerAdapter)。 如果误加了 --adapter,框架会报错找不到对应适配器。


3. 这组参数的第一性

这组参数不是为了刷 benchmark 分数,而是为了读代码:

text
1. seq_len=48:保证 cut_freq=16 < rfft_len=25,模型能正常运行而不报 shape 错误。
2. base_T=24, H_order=2:cut_freq=(48//24+1)×2+10=16;每日周期+2阶谐波,与论文默认设置对齐。
3. batch_size=2:shape 好看,断点里 tensor 维度小。
4. num_epochs=1:快速进入训练 forward,不用等完整训练。
5. num_rollings=2:rolling forecast 只滚 2 次,减少调试时间。
6. individual=false:走共享 Linear 分支,代码路径最短最清晰。
7. norm=true:开启框架层的 StandardScaler 归一化(与模型内部的 RIN 是两层独立归一化)。
8. lr=0.001:比默认 0.0001 大 10 倍,1 个 epoch 的 loss 更能看出变化。
9. 数据用 cif_2016_dataset_1.csv:小数据(单变量),进入模型快,enc_in=1。

4. 参数含义

数据与策略参数

参数当前值作用
--config-pathrolling_forecast_config.jsonrolling forecast 策略
--data-name-listcif_2016_dataset_1.csv小单变量数据集
horizon (strategy)6每次预测 6 步
tv_ratio0.8前 80% 进入 train/valid
train_ratio_in_tv0.75train/valid 内部再切
stride6rolling 每次移动 6 步
num_rollings2只滚 2 次
--num-workers1外层只开 1 个 worker,方便断点
--timeout600单任务最长 600 秒

模型参数

参数当前值进入 FITS 后的意义
model-namefits.FITS加载 ts_benchmark/baselines/fits/fits.py 中的 FITS
seq_len48历史输入长度;rfft_len = 48//2+1 = 25
horizon6pred_len=6length_ratio = 54/48 = 1.125
base_T24基础周期 = 24步(日周期)
H_order2保留基频+2倍频
cut_freq(自动计算)16(48//24+1)×2+10=1616 < 25,满足约束 ✓
freq_len_out(派生)18int(16 × 1.125) = 18
full_freq_len(派生)28(48+6)//2+1 = 28
individualfalse走共享 Linear 分支
batch_size22条样本每批
normtrue框架层 StandardScaler;模型内部还有 RIN
num_epochs1只训练 1 轮,快速进 forward
lossMSEMSELoss
patience3早停容忍次数(num_epochs=1 实际不会触发)

5. 为什么这次能覆盖关键循环

5.1 训练 epoch 循环至少跑 1 次

DeepForecastingModelBase.forecast_fit(...) 中:

python
for epoch in range(config.num_epochs):
    self.model.train()
    for i, (input, target, input_mark, target_mark) in enumerate(self.train_data_loader):
        ...
        out_loss = self._process(input, target, input_mark, target_mark)

当前:

text
num_epochs = 1

所以:

text
range(1) = [0]
epoch=0 一定被执行
for batch 循环也至少执行 1 次(cif_2016 数据足够)
FITS._process → FITSModel.forward 至少被调用 1 次

5.2 forward 内部所有步骤都会执行

FITS 的 forward() 是顺序执行,无可跳过的分支(individual=False 路径直接走 shared Linear):

text
RIN → rfft → LPF → freq_upsampler → zero_pad → irfft → energy_comp → de-norm

每个步骤必然执行,可以在任意一行打断点观察。

5.3 individual=True 分支的循环(单独测试)

individual=Trueforward() 中有:

python
for i in range(self.channels):
    low_specxy_[:, :, i] = self.freq_upsampler[i](
        low_specx[:, :, i].permute(0, 1)
    ).permute(0, 1)

当前 individual=False(默认),此 for 循环不会执行

若要测试此分支,修改 --model-hyper-params"individual": true,并用多变量数据(需 enc_in > 1):

text
--data-name-list AQShunyi.csv
--model-hyper-params "... \"individual\":true ..."

此时 channels = enc_in = AQShunyi 的列数,for 循环执行 enc_in 次(对数据集至少 2 次)。

⚠️ individual=True 分支有冗余 permute

low_specx[:, :, i].permute(0, 1) 对 2D 张量是恒等操作(不改变任何东西)。 详见 [[02-Layer1-forward主链]] §5.5。


6. 当前小例子的关键 shape

cif_2016_dataset_1.csv 是单变量数据:

text
enc_in = dec_in = c_out = 1(单变量)

当前 batch 的核心 shape:

text
input(历史序列): (2, 48, 1)
target(训练目标): (2, seq_len+pred_len, 1) — FITS 不直接用
input_mark:         (2, 48, time_dims)         — FITS 完全不用
target_mark:        (2, ..., time_dims)         — FITS 完全不用

FITS._process(...) 只取 input,进入 FITSModel.forward(x) 后:

text
x:                      (2, 48, 1)

x_mean(RIN):           (2, 1, 1)
x_var(RIN):            (2, 1, 1)
x(归一化后):           (2, 48, 1)

low_specx(rfft):       (2, 25, 1)  complex
low_specx(LPF截取):    (2, 16, 1)  complex   ← cut_freq=16

[permute前 →共享 Linear 分支]:
  low_specx.permute(0,2,1): (2, 1, 16) complex
  freq_upsampler(Linear 16→18): (2, 1, 18) complex
  permute 回: (2, 18, 1) complex
  low_specxy_: (2, 18, 1)

low_specxy(零填充):     (2, 28, 1)  complex   ← full_freq_len=28
low_xy(irfft):          (2, 54, 1)  real       ← 54=48+6
low_xy(能量补偿):       (2, 54, 1)             ← × 1.125
xy(反归一化):           (2, 54, 1)

_process 返回的 output:   (2, 54, 1)
训练截取 [:, -6:, :]:     (2, 6, 1)  ← 实际预测结果

7. 断点顺序

第一轮只看数据流动,不用手算 FFT。

  1. ts_benchmark/baselines/fits/fits.py

    • FITS.__init__(...) — 看 cut_freq 是如何计算的((seq_len // base_T + 1) * H_order + 10
    • 确认:self.config.cut_freq = 16
  2. ts_benchmark/baselines/fits/fits.py

    • FITS._process(...) — 确认:只有 input 被传入模型,其余参数被忽略
  3. ts_benchmark/baselines/fits/fits_model.py

    • FITSModel.forward(x) 第 1 行 — 看 x.shape = (2, 48, 1) 进入
  4. ts_benchmark/baselines/fits/fits_model.py

    • x_mean = torch.mean(x, dim=1, keepdim=True) — 看 x_mean.shape = (2, 1, 1)
  5. ts_benchmark/baselines/fits/fits_model.py

    • low_specx = torch.fft.rfft(x, dim=1) — 看 low_specx.shape = (2, 25, 1), dtype=cfloat
  6. ts_benchmark/baselines/fits/fits_model.py

    • low_specx[:, self.dominance_freq:] = 0 — 确认:dominance_freq=16low_specx[:, 16:] 共 9 个复数被置零
  7. ts_benchmark/baselines/fits/fits_model.py

    • low_specx = low_specx[:, 0:self.dominance_freq, :] — 看 low_specx.shape = (2, 16, 1)
  8. ts_benchmark/baselines/fits/fits_model.py

    • low_specxy_ = self.freq_upsampler(low_specx.permute(0, 2, 1)).permute(0, 2, 1) — 看中间 shape:(2,1,16)→(2,1,18)→(2,18,1)
  9. ts_benchmark/baselines/fits/fits_model.py

    • low_specxy = torch.zeros(...) — 看 low_specxy.shape = (2, 28, 1),及赋值后前 18 行有数、后 10 行为零
  10. ts_benchmark/baselines/fits/fits_model.py

    • low_xy = torch.fft.irfft(low_specxy, dim=1) — 看 low_xy.shape = (2, 54, 1),dtype 变回 float32
  11. ts_benchmark/baselines/fits/fits_model.py

    • return xy, low_xy * torch.sqrt(x_var) — 看 xy.shape = (2, 54, 1), xy[0, -6:, 0] = 最终 6 步预测值

8. 当前学习主线

text
run_benchmark
→ pipeline
→ evaluate_model
→ RollingForecast._eval_batch
→ FITS.forecast_fit                    ← DeepForecastingModelBase
  → multi_forecasting_hyper_param_tune
  → cut_freq 计算
  → _init_model → FITSModel.__init__
    → freq_upsampler = nn.Linear(16, 18).to(cfloat)
  → for epoch=0 / for batch:
    → FITS._process(input, ...)
      → FITSModel.forward(x)
        → RIN: 减均值 / 除标准差
        → rfft(x, dim=1): (2,48,1) → (2,25,1) cfloat
        → LPF: [:, 16:]=0 → [:, 0:16, :] → (2,16,1)
        → freq_upsampler: permute→Linear(16→18)→permute → (2,18,1)
        → zero_pad: (2,28,1)
        → irfft: (2,54,1) float32
        → × 1.125(能量补偿)
        → × sqrt(x_var) + x_mean(反归一化)
        → return xy(2,54,1), low(2,54,1)
      → output = xy
    → output[:, -6:, :series_dim]  →  (2, 6, 1)
    → MSE loss with target[:, -6:, :]

这一轮的第一性:

先看清 FITS 如何把时域信号变换到频域、做 LPF + 复数 Linear 插值、再还原时域,整个过程没有注意力机制,只靠一个 ~10K 参数的复数 Linear。


9. 重要提醒:两层独立归一化

FITS 运行时有两层归一化,容易混淆:

text
第一层(框架层):StandardScaler(sklearn)
  位于 DeepForecastingModelBase.forecast_fit()
  对整个训练集拟合后,对输入数据做 z-score 归一化
  当 norm=true 时激活

第二层(模型层):RIN
  位于 FITSModel.forward() 前 5 行
  对每个 batch 内每条序列单独归一化(实例归一化)
  不受 norm 参数控制,每次 forward 都执行

在断点里看到的 x(进入 forward 时)已经是经过 StandardScaler 归一化的;RIN 再做一次 per-instance 归一化。这不是 bug,是双重保险。

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