From 5ccd3df87487622b638b73f7c39ae338d2c82a45 Mon Sep 17 00:00:00 2001 From: CaoWangrenbo Date: Thu, 9 Jul 2026 15:50:25 +0800 Subject: [PATCH] docs: add TODO.md Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- TODO.md | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..798f0a5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,109 @@ +# TODO — 训练/评估改进计划 + +--- + +## P1 · 截断反传(TBPTT):修 GRU 隐状态长程失配(新) + +### 问题诊断(2025-07-09) + +`diag_state_mismatch.py` 确认核心问题是 **GRU 隐状态训练/推理分布失配**: + +| checkpoint | A 无状态(h=0 每 8 帧重置) | B 有状态(h 持续滚动) | B/A | +|---|---|---|---| +| epoch 4 (val 7.91) | RMSE 0.14~4.0 | RMSE 1.6~4.3 | 1.1~1.4 | +| epoch 180 (val 9.17) | RMSE 0.10~0.98 | RMSE 2.5~5.4 | 2.8~38.9 | + +训练越久,分叉越大。epoch 180 模型在窗口内几乎背下数据(A=0.14),但一滚动就崩(B=5.4)。**训练 loss 越低,长程越差**。 + +**根因**:训练时每个序列从 `h=0` 开始(seq_len=8),GRU 只见「零后 ≤8 步」的状态分布。推理时 h 滚动数百帧,进入 OOD 区域,预测崩溃。val_loss 从 7.91 升到 9.17 进一步印证过拟合。 + +### 修法:TBPTT + +跨 batch 传递 detach 后的隐状态,让 GRU 暴露于长程状态: + +``` +batch_k: frames [k, k+7] → GRU(fused, h_prev) → h_curr (detach → batch_{k+1}) +batch_{k+1}: frames [k+8, k+15] → GRU(fused, h_detached) → h_next (detach → ...) +``` + +### 具体改动 + +1. **`model.py::VelocityPredictionModel.forward`** + - 签名: `forward(self, events, tilt, h=None) -> (v_body, h_new)` + - `gru_out, h_new = self.gru(fused, h)` + - `model.step` 保持不变 + +2. **`train.py::train_one_epoch`** + - 接受 `h_state` 参数,初值为 `None` + - `pred_seq, h_new = model(events, tilt, h_state)` + - `h_state = h_new.detach()` 传出供下一 batch + - 每个 epoch 重置 `h_state = None` + +3. **`train.py::validate`** + - 保持原样传 `h=None`(每 batch 独立窗口,与当前评估一致) + +4. **`dataset.py`** + - 当前 `create_train_loader` 使用 `shuffle=1000` → 序列打乱 → 无法跨 batch 传 h + - 方案:新增 `create_tbptt_loader`: + - `shuffle=0, deterministic=True` + - `stride=seq_len`(非重叠) + - `num_workers=0`(确保严格时序) + - 场景级 shuffle:在加载前打乱场景列表,但每个场景内帧序严格 + - 直接将原始 `_build_pipeline` 参数由 `stride=1` 改为 `stride=seq_len`,关闭 shuffle + +### 注意事项 + +- 梯度只回传到当前 batch 的 seq_len 步,不回溯更早 batch(detach 保证) +- 首 batch `h=None` 仍从零初始化 +- BN 不受影响(CNN 在 per-frame 维度独立运行) +- TBPTT loader 帧数 = floor(scene_frames / seq_len) * seq_len,末尾不足部分丢弃 +- 需起新 run_id(旧 checkpoint 全部在 h=0 重置模式下训练,不兼容) + +### 验证 + +- `diag_state_mismatch.py` 重新评估:预期 B/A < 1.5 +- 同一场景下 stateful RMSE 接近 stateless RMSE + +--- + +## P2 · 考虑中(未纳入本次) + +### 2.1 归一化被注释 + +确认训练/评估两端 self-consistent(输出+target 都是原始 m/s),暂保留。 + +### 2.2 验证集/测试集划分 + +当前划分可调,涉及 config.py 场景列表调整和重训。 + +### 2.3 速度平滑正则 + +`((pred[:, 1:] - pred[:, :-1]) ** 2).mean()` 加权 `lambda=0.01`。建议 TBPTT 收敛后再加。 + +### 2.4 Loss mask 前几步 + +TBPTT 后 GRU 状态连贯,此问题可能自然缓解,暂不动。 + +--- + +## 落地顺序 + +1. 改 `model.py::forward` 签名,返回 `(pred, h_new)` +2. 改 `train.py` train + validate 适配新签名 +3. 新增/修改 loader 实现 TBPTT 模式(不 shuffle,非重叠) +4. 起新 run_id 训练,观察 val_loss 走势 +5. 用 `diag_state_mismatch.py` 验证 B/A 比值 + +--- + +## 已完成 + +以下为旧版 TODO 中已完成的项目,保留归档。 + +### ~~P1 · 序列级损失监督~~(已完成,2025-07) + +已在 `train.py:54-59` + `model.py:149-153` 实现: +- `model.forward` 返回 `(B, S, 2)`,对整段 `gru_out` 过 head +- 全序列逐 step MSE loss `(B, S, 2) → (B, S) → scalar` +- TensorBoard `loss_step_{i:02d}` 分位数记录 +- 验证通过:输出 shape `(B, 8, 2)`,TensorBoard 观察 loss_step 分布正常