| # 2026-04-24 — `v9_real_balanced_10hz` real dump 诊断 |
| |
| 本文档记录 `v9_real_balanced_10hz` 在真实数据 dump 上的直接 CSV 诊断结果,不依赖训练日志里的 aggregate `val_metrics`。 |
| |
| 相关文件: |
| - dump 目录:`checkpoints/v9_10hz_eval_dump/v9_real_balanced_10hz` |
| - 统计脚本:[scripts/analyze_csv_dump.py](/apdcephfs_cq10/share_1603164/user/schmittzhu/code/unilm/beats/scripts/analyze_csv_dump.py) |
| |
| ## 1. 背景 |
| |
| 用户的核心问题不是 “aggregate 指标多少”,而是: |
| |
| 1. `real_ov1 / real_ov2 / real_ov3` 到底差在哪; |
| 2. 是类错、角度错,还是对的 track 没被最终输出; |
| 3. `10Hz + real mix` 到底有没有把模型带坏。 |
|
|
| 为避免继续靠 `oracle_* / F20 / LE_CD` 猜,我们直接分析了导出的 `__pred.csv / __gt.csv`。 |
|
|
| ## 2. 统计脚本 |
|
|
| 新增脚本: |
|
|
| ```bash |
| python3 scripts/analyze_csv_dump.py \ |
| --dump-dir checkpoints/v9_10hz_eval_dump/v9_real_balanced_10hz |
| ``` |
|
|
| 默认行为: |
| - 不做阈值,直接分析 raw `__pred.csv` 的全部 track |
|
|
| 分析阈值后的最终输出: |
|
|
| ```bash |
| python3 scripts/analyze_csv_dump.py \ |
| --dump-dir checkpoints/v9_10hz_eval_dump/v9_real_balanced_10hz \ |
| --threshold 0.5 \ |
| --threshold-sweep 0.3 0.4 0.6 |
| ``` |
|
|
| 脚本会输出两类统计: |
|
|
| ### 2.1 GT-side |
|
|
| 对每个 GT source/frame 看: |
|
|
| - `hit_cls_and_angle` |
| 含义:至少存在一个同类预测,且最佳角误差 `<=20°` |
| - `class_right_angle_wrong` |
| 含义:存在同类预测,但最佳角误差 `>20°` |
| - `no_same_class_pred_but_other_preds_exist` |
| 含义:这帧模型有别的 active 预测,但没有任何同类预测 |
| - `no_pred_in_frame` |
| 含义:这帧一个 active 预测都没有 |
|
|
| ### 2.2 Pred-side |
|
|
| 对 threshold 后的预测看: |
|
|
| - `matched_tp` |
| 含义:能和某个 GT 做同类且 `<=20°` 的匹配 |
| - `same_class_angle_wrong_fp` |
| 含义:有同类 GT,但角度没进 `20°` |
| - `wrong_class_or_spurious_fp` |
| 含义:没有任何同类 GT |
|
|
| ## 3. 一个重要事实:dump 里的 `pred.csv` 不是最终输出 |
|
|
| 这次 `v9_real_balanced_10hz` 的 `pred.csv` 保存的是 **每帧 4 条 raw track 输出**,不是已经过 `activity_prob>=0.5` 筛选后的最终预测。 |
|
|
| 这非常重要,因为它允许我们把问题拆成两层: |
|
|
| 1. **raw 4-track 里有没有可用候选** |
| 2. **过阈值以后,最终留下来的到底是什么** |
|
|
| ## 4. Raw 4-track 结果 |
|
|
| 命令: |
|
|
| ```bash |
| python3 scripts/analyze_csv_dump.py \ |
| --dump-dir checkpoints/v9_10hz_eval_dump/v9_real_balanced_10hz |
| ``` |
|
|
| 核心结果: |
|
|
| ### 4.1 `real_ov1` |
| |
| - `avg_gt/frame = 1.00` |
| - `avg_pred/frame = 4.00` |
| - `hit_cls_and_angle = 54.2%` |
| - `class_right_angle_wrong = 45.8%` |
| - `no_same_class_pred_but_other_preds_exist = 0.0%` |
|
|
| 解释: |
| - raw 4 条里,**每个 GT 都能找到同类候选** |
| - 其中一半以上角度也已经进了 `20°` |
| - 所以单源 real 上,raw 候选并不差 |
|
|
| ### 4.2 `real_ov2` |
| |
| - `avg_gt/frame = 1.98` |
| - `avg_pred/frame = 4.00` |
| - `hit_cls_and_angle = 37.4%` |
| - `class_right_angle_wrong = 56.9%` |
| - `no_same_class_pred_but_other_preds_exist = 5.7%` |
|
|
| 解释: |
| - raw 4 条里,大多数 GT 还是能找到同类候选 |
| - 但**主问题已经是角度本身错** |
|
|
| ### 4.3 `real_ov3` |
| |
| - `avg_gt/frame = 2.88` |
| - `avg_pred/frame = 4.00` |
| - `hit_cls_and_angle = 33.9%` |
| - `class_right_angle_wrong = 41.6%` |
| - `no_same_class_pred_but_other_preds_exist = 24.5%` |
|
|
| 解释: |
| - 即使给满 4 条 raw 候选,仍有 `24.5%` 的 GT 找不到任何同类预测 |
| - 所以 `real_ov3` 从 raw 层面就已经有明显 `class/source binding` 问题 |
|
|
| ## 5. `activity>=0.5` 后的结果 |
|
|
| 命令: |
|
|
| ```bash |
| python3 scripts/analyze_csv_dump.py \ |
| --dump-dir checkpoints/v9_10hz_eval_dump/v9_real_balanced_10hz \ |
| --threshold 0.5 |
| ``` |
|
|
| ### 5.1 `real_ov1` |
| |
| - `avg_gt/frame = 1.00` |
| - `avg_pred/frame = 1.14` |
| - `hit_cls_and_angle = 22.4%` |
| - `class_right_angle_wrong = 40.2%` |
| - `no_same_class_pred_but_other_preds_exist = 37.3%` |
|
|
| 解释: |
| - raw 4-track 时,同类候选是 `100%` 存在的 |
| - 过阈值后,`37.3%` 的 GT 直接变成 “这帧有别的 active 预测,但没有同类预测” |
| - 这说明 `real_ov1` 的主问题不是 “不会预测”,而是: |
| - 对的 track 没被留住 |
| - 或被别的错 track 抢走 |
| - 也就是 **decode / track ranking / calibration** 问题 |
|
|
| ### 5.2 `real_ov2` |
| |
| - `avg_gt/frame = 1.98` |
| - `avg_pred/frame = 1.93` |
| - `hit_cls_and_angle = 17.9%` |
| - `class_right_angle_wrong = 73.9%` |
| - `no_same_class_pred_but_other_preds_exist = 8.2%` |
|
|
| 解释: |
| - 轨数和 GT 基本对齐,不是明显少报 |
| - 主要失败项是 **同类有了,但角度错** |
| - 所以 `real_ov2` 主问题不是 threshold,也不是主要类错,而是 **角度本身错** |
|
|
| ### 5.3 `real_ov3` |
| |
| - `avg_gt/frame = 2.88` |
| - `avg_pred/frame = 1.82` |
| - `247` 帧里有 `161` 帧是 `pred < gt` |
| - `hit_cls_and_angle = 26.3%` |
| - `class_right_angle_wrong = 30.2%` |
| - `no_same_class_pred_but_other_preds_exist = 43.5%` |
|
|
| 解释: |
| - 这里同时有三件事: |
| 1. **active 轨数不够** |
| 2. **同类 track 经常找不到** |
| 3. 即使找到了,同类里也有不少角度不对 |
|
|
| 因此 `real_ov3` 是: |
| - `binding` 错 |
| - `decode` 少亮轨 |
| - `angle` 也错 |
|
|
| 三件事叠在一起。 |
|
|
| ## 6. Threshold sweep:是不是纯阈值问题 |
|
|
| 命令: |
|
|
| ```bash |
| python3 scripts/analyze_csv_dump.py \ |
| --dump-dir checkpoints/v9_10hz_eval_dump/v9_real_balanced_10hz \ |
| --threshold 0.5 \ |
| --threshold-sweep 0.3 0.4 0.6 |
| ``` |
|
|
| ### 6.1 `real_ov1` |
| |
| 从 `0.3 -> 0.6`: |
| |
| - `avg_pred/frame` 几乎不变:`1.14 -> 1.05` |
| - `hit` 基本只在高阈值 `0.6` 时下降 |
| - `no_same_cls` 一直很高:`37.3% -> 43.4%` |
|
|
| 结论: |
| - **不是简单阈值问题** |
| - 更像是对的 track 本来就没排到最终输出前面 |
|
|
| ### 6.2 `real_ov2` |
| |
| 从 `0.3 -> 0.6`: |
| |
| - `avg_pred/frame` 几乎不变:`1.99 -> 1.91` |
| - `hit` 几乎不变:`17.9% -> 17.8%` |
| - `class_right_angle_wrong` 一直卡在 `73%` 左右 |
|
|
| 结论: |
| - **几乎完全不是 threshold 问题** |
| - 就是 **角度头本身错** |
|
|
| ### 6.3 `real_ov3` |
| |
| 从 `0.5 -> 0.3`: |
| |
| - `avg_pred/frame` 从 `1.82 -> 2.15` |
| - `under_frames` 从 `161 -> 140` |
| - `no_same_cls` 从 `43.5% -> 34.7%` |
| - 但 `hit` 只从 `26.3% -> 27.0%` |
|
|
| 结论: |
| - 降阈值确实能缓一点 “少亮轨” |
| - 但收益有限,根问题还在 |
| - 所以 `real_ov3` 不是纯阈值问题 |
|
|
| ## 7. 最终结论 |
|
|
| ### 7.1 每个 split 的主问题 |
|
|
| - `real_ov1` |
| - 主问题:**decode / track ranking / calibration** |
| - 证据:raw 4-track 同类候选 `100%` 存在,但阈值后大量 GT 找不到同类 track |
|
|
| - `real_ov2` |
| - 主问题:**角度本身错** |
| - 证据:轨数基本对,class 也不是主要问题,但 `73.9%` 变成 “同类有了但角度错” |
|
|
| - `real_ov3` |
| - 主问题:**binding 错 + 少亮轨 + 角度错** |
| - 证据: |
| - raw 层 already `24.5%` 找不到同类 |
| - threshold 后 `avg_pred/frame = 1.82 < 2.88` |
| - 同时还有 `30.2%` 的同类角度错 |
|
|
| ### 7.2 这次问题不是一句 “real 很差” 能说清的 |
|
|
| 更准确的说法应该是: |
|
|
| - `real_ov1`:内部候选还可以,但最终输出选坏了 |
| - `real_ov2`:主要是 spatial regression 错 |
| - `real_ov3`:multi-source 下 source binding 和 spatial 一起掉了 |
|
|
| ### 7.3 这也解释了为什么只看 aggregate 指标会误判 |
|
|
| 同一个 `LE_CD / F20` 很差,背后可能是三种完全不同的失败机制: |
|
|
| - 对的候选存在,但没被输出 |
| - 同类 track 有了,但角度偏得很远 |
| - raw 4-track 里就没把 source 绑定出来 |
|
|
| 所以以后继续看 real dump 时,应该固定用 `scripts/analyze_csv_dump.py`,先把问题拆成这三类,再决定改哪一层。 |
|
|
|
|