File size: 5,550 Bytes
74e4281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# 数据准备问题排查

## 现象

### 推理报错

```text
KeyError: 'file .../multispeaker_session_7.wav is already part of AUDIO_RTTM_MAP, 
it might be duplicated, Note: file basename must be unique'
```

### 训练异常(6小时训炸)

用户推测为数据问题导致,与推理报错同源。

---

## 根因分析

### 1. Manifest 格式不匹配

数据准备脚本 `run_all_speakers_simulation.sh` 生成的 manifest 为 **训练格式(segment-level)**```json
{"audio_filepath": ".../multispeaker_session_0.wav", "offset": 1.164,  "duration": 19.82, ...}
{"audio_filepath": ".../multispeaker_session_0.wav", "offset": 20.692, "duration": 1.825, ...}
{"audio_filepath": ".../multispeaker_session_0.wav", "offset": 21.982, "duration": 39.57,  ...}
```

同一个 wav 文件通过不同 `offset`/`duration` 被切分为多个 segment 行。训练 dataloader 按 segment 逐条加载,这是正确的。

但推理脚本 `e2e_diarize_speech.py:370` 调用 `audio_rttm_map()` 时:

```python
# speaker_utils.py:113
uniqname = get_uniqname_from_filepath(filepath=meta['audio_filepath'])
# → os.path.splitext(os.path.basename(filepath))[0]
# → "multispeaker_session_0"   (所有 segment 返回相同的值)

if uniqname not in AUDIO_RTTM_MAP:
    AUDIO_RTTM_MAP[uniqname] = meta
else:
    raise KeyError(...)   # 第二条 segment 触发此分支
```

**`get_uniqname_from_filepath()` 仅取 basename,不区分 offset。当同一个 wav 文件有多个 segment 行时,第二条及之后的行必定触发 KeyError。**

### 2. 推理期望的 Manifest 格式

推理端 `audio_rttm_map()` 设计的前提是 **每个 wav 文件只出现一次**,即 **file-level manifest**:

```json
{"audio_filepath": ".../multispeaker_session_0.wav", "offset": 0, "duration": null, "rttm_filepath": ".../multispeaker_session_0.rttm", "num_speakers": 10}
{"audio_filepath": ".../multispeaker_session_1.wav", "offset": 0, "duration": null, "rttm_filepath": ".../multispeaker_session_1.rttm", "num_speakers": 10}
```

关键字段:
- `offset` = 0(从文件开头处理)
- `duration` = null(处理完整文件)

### 3. 当前数据结构

| Manifest | 行数 | 唯一 wav 文件数 | 平均 segment/wav | 格式 |
|----------|------|----------------|-----------------|------|
| `train.json` | 24,804 | 1,800 | 13.8 | segment-level |
| `dev.json` | 3,842 | 180 | 21.3 | segment-level |
| `test.json` | 3,772 | 180 | 21.0 | segment-level |

> 所有 split 均包含重复的 `audio_filepath`,直接用于推理必然报错。

### 4. 训练异常的原因推测

训练不会直接调用 `audio_rttm_map()`,因此 KeyError 不会出现在训练 loop 中。但以下问题可能导致训练失败:

1. **显存 OOM**:当前 10spk config 中 `batch_size=12`、`session_len_sec=1200`,在 V100-32G 上可能显存不足。
2. **Validation 阶段问题**:如果训练配置中某些验证逻辑间接调用了 `audio_rttm_map()`,会触发同样的 KeyError。
3. **数据完整性**:segment 切分生成时可能存在边界不齐、重叠或缺失的问题。

---

## 修复方案

### 方案 1: 生成推理专用 Manifest(推荐)

```python
#!/usr/bin/env python3
"""从 segment-level manifest 生成 file-level inference manifest"""
import json
import os
from pathlib import Path

def generate_infer_manifest(segment_manifest_path: str, output_path: str):
    seen = set()
    with open(segment_manifest_path) as f_in, open(output_path, 'w') as f_out:
        for line in f_in:
            d = json.loads(line)
            key = d.get('audio_filepath')
            if not key or key in seen:
                continue
            seen.add(key)
            # file-level format for inference
            entry = {
                "audio_filepath": d["audio_filepath"],
                "offset": 0,
                "duration": None,
                "rttm_filepath": d.get("rttm_filepath"),
                "num_speakers": d.get("num_speakers"),
                "text": "-",
            }
            f_out.write(json.dumps(entry) + "\n")
    print(f"Generated {len(seen)} entries → {output_path}")

if __name__ == "__main__":
    base = "data/dump/simulated_10spk_dataset"
    for split in ["train", "dev", "test"]:
        generate_infer_manifest(
            f"{base}/{split}.json",
            f"{base}/{split}_infer.json",
        )
```

生成后修改 `infer.sh` 中 `dataset_manifest` 指向新的 `test_infer.json`。

### 方案 2: 修改音频文件命名避免冲突

确保数据仿真时每个 segment 对应独立命名的 wav 文件(`multispeaker_session_0_seg001.wav`),但这会增加大量文件复制/软链接。

### 方案 3: 修改 `audio_rttm_map()` 对重复做去重

```python
# speaker_utils.py:115
if uniqname not in AUDIO_RTTM_MAP:
    AUDIO_RTTM_MAP[uniqname] = meta
else:
    logging.warning(f"Duplicate audio entry skipped: {meta['audio_filepath']}")
    continue
```

但这只是绕过错误,不代表推理正确——后续逻辑仍可能因缺少正确的 file-level 映射而出错。

---

## 建议的完整修复流程

1. 用方案 1 脚本生成 `train_infer.json` / `dev_infer.json` / `test_infer.json`
2. 推理时使用 `test_infer.json` 作为 manifest
3. 训练时继续使用 `train.json` / `dev.json`(segment-level 格式)
4. 为避免后续混淆,在 `run_all_speakers_simulation.sh` 末尾自动生成 file-level manifest
5. 测试训练:用 `batch_size` 从小开始(如 2-4),观察 GPU 显存使用和 loss 趋势,逐步上调