| # 0416 Spatial-BEATs 实验记录 |
|
|
| 本文档记录 2026-04-16 对话期间的所有改动、实验和结果。 |
|
|
| ## 1. Bug 修复 |
|
|
| ### 1.1 方位角坐标系修复(val_predictions 显示 bug) |
| |
| **问题**:val_predictions jsonl 文件中 `gt_azimuth_deg` 是 DCASE 标准 `[-180°, 180°]`,但 `pred_azimuth_deg` 被 `torch.remainder(..., 360.0)` 转成了 `[0°, 360°]`。导致 GT=-168° 和 Pred=192° 看起来差 360° 但实际是同一个方向。 |
|
|
| **影响**:仅影响 val_predictions 文件的可读性。**训练 loss 和 epoch log metrics 不受影响**(`_circular_distance_deg` 对两种坐标系都能正确计算圆周距离)。 |
|
|
| **修复**(`spatial_loss.py`): |
| - `_azi_ele_deg_from_direction_vector`:去掉 `torch.remainder(..., 360.0)`,`atan2` 直接返回 `[-180°, 180°]` |
| - 新增 `_to_dcase_azimuth()` 工具函数 |
| - `build_validation_examples`、`build_pretrunk_ast_validation_examples` 中 pred_azi 使用 `_to_dcase_azimuth` 转换 |
|
|
| ### 1.2 Vocabulary 标签修复(65→63 类) |
|
|
| **问题 1**:`female_singing`(171例) 和 `male_singing`(104例) 与 `singing` 是父子关系,模型在 65 类 softmax 下完全区分不了(test accuracy=0%)。 |
|
|
| **问题 2**:`string_instrument` 类中有 **1644 个样本**(22.8%)是 Hi-hat/Crash_cymbal/Cymbal 打击乐,被错误映射。这些应该归入 `percussion`。 |
| |
| **修复**(`fix_vocabulary_and_manifests.py`): |
| - `female_singing` + `male_singing` → 合并到 `singing`(65→63 类) |
| - `string_instrument` 中 `mono_primary_label` 为 Hi-hat/Crash_cymbal/Cymbal 的样本 → 重标为 `percussion` |
| - 对 ov1/ov2/ov3 manifest 全部原地修复,备份为 `.bak_20260416` |
| - `final_vocabulary.csv` 重编号为连续的 1~63 |
|
|
| **受影响文件**: |
| | 文件 | 变更 | |
| |---|---| |
| | `final_vocabulary.csv` | 65→63 类,去掉 female_singing/male_singing | |
| | `ov1_foa.jsonl` | 460 条 singing 合并 + 1644 条 cymbal 修正 | |
| | `ov2_foa.jsonl` / `ov3_foa.jsonl` | 无变动(这些类别未出现) | |
|
|
| **代码 default 值更新**: |
| | 文件 | 变更 | |
| |---|---| |
| | `spatial_beats.py` | `source_num_classes: 65 → 63` | |
| | `spatial_dataset.py` | `num_classes: 65 → 63` | |
| | `spatial_modules.py` | 6 处函数参数默认值 `65 → 63` | |
| | `spatial_atst.py` | `source_num_classes: 65 → 63` | |
|
|
| ### 1.3 load_checkpoint strict 改为 non-strict |
| |
| **问题**:stage1→stage2 resume 时,如果两阶段 model config 不完全一致(比如 stage1 无 semantic_anchor 但 stage2 有),`strict=True` 会报错 missing key。 |
|
|
| **修复**(`train_spatial_beats.py`):`load_checkpoint` 改用 `strict=False`,missing/unexpected key 打印 warning 不报错。 |
|
|
| ## 2. v2 Test 集完整评测 |
|
|
| 编写了 `eval_spatial_beats.py` 评测脚本,在 ov1 **完整 test 集(1800 样本)** 上评测了 v2 stage2 best.pt。 |
|
|
| **checkpoint**:`checkpoints/spatial_beats_ov1_local_spatial_v2_exp/02_spatial/best.pt` |
|
|
| | 指标 | 值 | |
| |---|---| |
| | class_acc | **43.83%** | |
| | azi_mae | **19.87°** | |
| | ele_mae | 8.66° | |
| | dist_mae | 0.554 m | |
| | SELD F1 | 0.3139 | |
| | SELD LR | 0.6956 | |
| | SELD LE | 8.64° | |
| | **SELD Score ↓** | **0.6027** | |
|
|
| ### 63 类映射后(post-hoc) |
|
|
| | 指标 | 65类 | 63类映射 | |
| |---|---|---| |
| | class_acc | 43.83% | **44.44%** (+0.6%) | |
| | 空间指标 | 不变 | 不变 | |
| |
| 提升来自 singing 子类合并(+11 个正确样本)。 |
| |
| ### Per-class 分析亮点 |
| |
| - **Accuracy=0 的 4 个类**:female_singing, male_singing, tape, wood |
| - **Accuracy≥0.7 的 5 个类**:bird(70.6%), guitar(70.9%), thunderstorm(76%), knock(77.8%), car(100%) |
| - **最大混淆来源**:父子标签层级冲突(guitar↔keyboard_instrument, wind_instrument→musical_instrument, male_singing→singing) |
| |
| ## 3. bypass / purify 实验结果 |
| |
| 两个新架构实验的 stage1 都**全 trunk 解冻**(12 层),目标是用更激进的策略达到更高 class_acc。 |
|
|
| | 实验 | Stage1 策略 | Stage1 class_acc | Stage2 SELD | |
| |---|---|---|---| |
| | bypass | 全解冻 + bypass_local_fusion + 零空间 | ~25% ❌ | 0.743 | |
| | purify | 全解冻 + freeze_local_spatial + 零空间 | ~25% ❌ | 0.816 | |
| | **v2(对照)** | top-2 + semantic_anchor + 空间多任务 | **~56%** ✅ | **0.603** | |
|
|
| **结论**:全 trunk 解冻 + SpatialBEATs 架构 = 崩。top-2 解冻更稳定。 |
|
|
| ## 4. 分类能力损失分析 |
|
|
| ### 纯 BEATs 分类实验(无空间任务) |
|
|
| | 解冻策略 | val_acc | |
| |---|---| |
| | head_only(trunk 全冻) | 62.6% | |
| | top-8(层 4-11) | **69.1%** | |
| | full(全 12 层) | 70.0% | |
|
|
| ### SpatialBEATs 分类损失链 |
|
|
| ``` |
| 纯 BEATs 62.6%(trunk 全冻) |
| → SpatialBEATs v2 stage1: 56% (-6.6%: CNN噪声 + 多任务干扰 + 只解冻top-2) |
| → SpatialBEATs v2 stage2: 44% (-12%: λ_dir=12 空间梯度冲击语义) |
| ``` |
|
|
| ### 关键发现:v4 的 trunk 起点 |
|
|
| v4 config 中 `class_finetuned_ckpt` 之前指向的是 `head_only/best.pt`(62.6%),其 trunk 权重和原始 BEATs **完全相同**(head_only 全冻 trunk 训练)。已修正为指向 `02_full/best.pt`(70%),获取 FSD50K 适配过的 trunk。 |
|
|
| ## 5. 失败实验 |
|
|
| ### v3 / v3ws(bypass + top-8/top-4) |
| - v3 epoch1: cls=0.116 ❌ |
| - v3ws epoch1: cls=0.080 ❌ |
| - 原因:bypass 模式 + SpatialBEATs = DDP 不稳定 |
|
|
| ### v3b / v3bws(freeze_local_spatial + top-8/top-4) |
| - v3b 15 epoch: cls=35.67%,azi=89.66°(空间几乎随机) |
| - v3bws: 无效果 |
| - 原因:多变量同时改动(top-8 + freeze_local_spatial + ddp_find_unused + 无 anchor),不如 v2 的组合 |
|
|
| ## 6. 新增架构:Frame-Level Track Supervision |
|
|
| ### 动机 |
|
|
| 当前 `mono_ast` 是 clip-level 预测(attention pool → 1 class + 1 direction),无法: |
| 1. 输出 DCASE 格式逐帧检测 |
| 2. 扩展到 ov2/ov3 多源 |
| 3. 约束 trunk 逐帧表征质量 |
|
|
| ### 实现 |
|
|
| 在 `readout_scheme="local_spatial"` 下新增可选 `FrameTrack` 分支,与 clip-level head **并行运行**: |
|
|
| ``` |
| fused_spatial_embeddings [B, T_s, D] |
| ├── attention pool → clip-level mono_ast prediction (已有) |
| └── SourceQueryDecoder → [B, K, T_s, D] → FrameTrack prediction (新增) |
| ``` |
|
|
| **完全复用已有代码**:`SourceQueryDecoder`、`FrameTrackPredictionHeads`、`compute_frame_track_losses` 一行都没改。 |
|
|
| **控制开关**: |
| - `SpatialBEATsConfig.enable_frame_track: bool = False` |
| - `SpatialLossConfig.enable_frame_track_loss: bool = False` |
| - 默认关闭,所有现有实验零影响 |
|
|
| **改动文件**: |
| | 文件 | 改动 | |
| |---|---| |
| | `spatial_beats.py` | Config flag + __init__ 创建 head + forward 产出 | |
| | `spatial_loss.py` | Config flag | |
| | `train_spatial_beats.py` | run_train_step 追加 loss + validate 追加 examples + preset | |
| | `spatial_modules.py` | **不改** | |
|
|
| **新 preset**:`ov1_local_spatial_v4f_spatial` |
|
|
| ## 7. 当前实验矩阵 |
|
|
| ### 正在跑 |
|
|
| | 实验 | 状态 | 配置要点 | |
| |---|---|---| |
| | v4 stage1 | 运行中 | v2 架构复刻 + 63 类 + **70% trunk init** | |
|
|
| ### 等 v4 stage1 结束后 |
|
|
| | 实验 | 脚本 | 配置要点 | |
| |---|---|---| |
| | v4 stage2 | `run_ov1_v4.sh` | v2 复刻(λ_dir=12, anchor=0.5) | |
| | v4g stage2 | `run_ov1_v4g.sh` | 温和版(λ_dir=6, λ_cls=2, anchor=1.5) | |
| | v4f stage2 | `run_ov1_v4f.sh` | v4 + 并行 frame-level track head | |
| |
| ### 预期效果 |
| |
| | 实验 | 预期 class_acc | 预期 azi_mae | 新能力 | |
| |---|---|---|---| |
| | v4 stage2 | ~44%(同 v2) | ~20° | baseline | |
| | v4g stage2 | **~50%+** | ~25-30° | 分类更好,空间稍差 | |
| | v4f stage2 | ~44% + frame metrics | ~20° | DCASE 逐帧输出 | |
| |
| ## 8. 新增文件清单 |
| |
| | 文件 | 用途 | |
| |---|---| |
| | `eval_spatial_beats.py` | 独立评测脚本,支持所有 preset | |
| | `fix_vocabulary_and_manifests.py` | 一次性 vocab+manifest 修复脚本 | |
| | `run_ov1_v3.sh` | v3 实验(bypass,已失败) | |
| | `run_ov1_v3ws.sh` | v3ws 实验(bypass+warmstart,已失败) | |
| | `run_ov1_v3b.sh` | v3b 实验(freeze_local_spatial,效果差) | |
| | `run_ov1_v3bws.sh` | v3bws 实验(同上+warmstart) | |
| | `run_ov1_v4.sh` | v4 实验(v2 复刻 + 63 类 + 70% trunk) | |
| | `run_ov1_v4g.sh` | v4g 温和空间版 | |
| | `run_ov1_v4f.sh` | v4f frame-level track 版 | |
|
|
| ## 9. 关键经验总结 |
|
|
| 1. **不要全解冻 trunk**:纯 BEATs 全解冻 OK(70%),但 SpatialBEATs 架构下全解冻必崩(25%)。top-2 是当前唯一验证过的安全策略。 |
|
|
| 2. **bypass_local_fusion 不可用**:在 DDP 训练下导致不稳定,即使只是 top-8 解冻也会崩。freeze_local_spatial 稍好但仍差。 |
|
|
| 3. **Semantic anchor 有效**:v2 的 anchor(λ=0.5)让 stage2 class_acc 从 25%(无 anchor 的 kaldi_spatial)保到 44%。 |
|
|
| 4. **标签质量是分类瓶颈**:65 类中父子层级冲突(singing/female_singing、musical_instrument/string_instrument)和标签映射 bug(cymbal→string_instrument)贡献了大量"假错误"。 |
|
|
| 5. **trunk 初始化很重要**:v4 之前一直用的是 `head_only/best.pt`(trunk ≡ 原始 BEATs),现在改为 `02_full/best.pt`(trunk 已适配 FSD50K,70%),预期 stage1 起点更高。 |
|
|
| 6. **一次只改一个变量**:v3b/v3bws 同时改了 5 个变量导致无法诊断,v4 只改了 trunk init 这一项。 |
|
|