# 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`,先把问题拆成这三类,再决定改哪一层。