Appearance
FITS 调试形参
Abstract
这篇只做一件事:
保存用于学习 FITS 代码运行流程的 PyCharm / VSCode 参数,并保证模型内部关键循环至少执行一次。
1. PyCharm 配置
Script path
text
D:\1sudyta\1ai-self\aistyle\TFB\scripts\run_benchmark.pyWorking directory
text
D:\1sudyta\1ai-self\aistyle\TFBEnvironment variables
text
KMP_DUPLICATE_LIB_OK=TRUE2. 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-path | rolling_forecast_config.json | rolling forecast 策略 |
--data-name-list | cif_2016_dataset_1.csv | 小单变量数据集 |
horizon (strategy) | 6 | 每次预测 6 步 |
tv_ratio | 0.8 | 前 80% 进入 train/valid |
train_ratio_in_tv | 0.75 | train/valid 内部再切 |
stride | 6 | rolling 每次移动 6 步 |
num_rollings | 2 | 只滚 2 次 |
--num-workers | 1 | 外层只开 1 个 worker,方便断点 |
--timeout | 600 | 单任务最长 600 秒 |
模型参数
| 参数 | 当前值 | 进入 FITS 后的意义 |
|---|---|---|
model-name | fits.FITS | 加载 ts_benchmark/baselines/fits/fits.py 中的 FITS 类 |
seq_len | 48 | 历史输入长度;rfft_len = 48//2+1 = 25 |
horizon | 6 | pred_len=6;length_ratio = 54/48 = 1.125 |
base_T | 24 | 基础周期 = 24步(日周期) |
H_order | 2 | 保留基频+2倍频 |
cut_freq(自动计算) | 16 | (48//24+1)×2+10=16;16 < 25,满足约束 ✓ |
freq_len_out(派生) | 18 | int(16 × 1.125) = 18 |
full_freq_len(派生) | 28 | (48+6)//2+1 = 28 |
individual | false | 走共享 Linear 分支 |
batch_size | 2 | 2条样本每批 |
norm | true | 框架层 StandardScaler;模型内部还有 RIN |
num_epochs | 1 | 只训练 1 轮,快速进 forward |
loss | MSE | MSELoss |
patience | 3 | 早停容忍次数(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=True 时 forward() 中有:
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。
ts_benchmark/baselines/fits/fits.pyFITS.__init__(...)— 看cut_freq是如何计算的((seq_len // base_T + 1) * H_order + 10)- 确认:
self.config.cut_freq = 16
ts_benchmark/baselines/fits/fits.pyFITS._process(...)— 确认:只有input被传入模型,其余参数被忽略
ts_benchmark/baselines/fits/fits_model.pyFITSModel.forward(x)第 1 行 — 看x.shape = (2, 48, 1)进入
ts_benchmark/baselines/fits/fits_model.pyx_mean = torch.mean(x, dim=1, keepdim=True)— 看x_mean.shape = (2, 1, 1)
ts_benchmark/baselines/fits/fits_model.pylow_specx = torch.fft.rfft(x, dim=1)— 看low_specx.shape = (2, 25, 1), dtype=cfloat
ts_benchmark/baselines/fits/fits_model.pylow_specx[:, self.dominance_freq:] = 0— 确认:dominance_freq=16,low_specx[:, 16:]共 9 个复数被置零
ts_benchmark/baselines/fits/fits_model.pylow_specx = low_specx[:, 0:self.dominance_freq, :]— 看low_specx.shape = (2, 16, 1)
ts_benchmark/baselines/fits/fits_model.pylow_specxy_ = self.freq_upsampler(low_specx.permute(0, 2, 1)).permute(0, 2, 1)— 看中间 shape:(2,1,16)→(2,1,18)→(2,18,1)
ts_benchmark/baselines/fits/fits_model.pylow_specxy = torch.zeros(...)— 看low_specxy.shape = (2, 28, 1),及赋值后前 18 行有数、后 10 行为零
ts_benchmark/baselines/fits/fits_model.pylow_xy = torch.fft.irfft(low_specxy, dim=1)— 看low_xy.shape = (2, 54, 1),dtype 变回 float32
ts_benchmark/baselines/fits/fits_model.pyreturn 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,是双重保险。