docs(diarization): добавлены отчёт и скрипт калибровки
- Зачем: - необходим устойчивый дефолт модели эмбеддингов и порога на русской речи. - Что: - задокументирован выбор WeSpeaker ResNet34 LM с порогом 0,89. - добавлен возобновляемый скрипт свипа моделей и параметров диаризации. - Проверка: - uvx --cache-dir .uv-cache ruff check scripts/benchmarks/diarization_calibration.py. - uv run --cache-dir .uv-cache pytest: 243 passed, 1 skipped.
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# Калибровка модели эмбеддингов и порога диаризации
|
||||
|
||||
**Дата:** 2026-08-14
|
||||
|
||||
**Статус:** выбор конфигурации для проектирования. Не приёмка
|
||||
производительности на целевом Intel Core i5 11-го поколения.
|
||||
|
||||
## Решение
|
||||
|
||||
Для автоматического определения числа участников использовать:
|
||||
|
||||
- эмбеддинги `wespeaker_en_voxceleb_resnet34_LM.onnx`;
|
||||
- `FastClusteringConfig.threshold=0.89`;
|
||||
- `num_clusters=-1` по умолчанию.
|
||||
|
||||
Если число участников известно, передавать его через `num_clusters`: это
|
||||
устраняет остаточные кластеры и служит страховкой от особенностей записи. На
|
||||
трёх проверенных фрагментах явное число участников не ухудшило прокси-метрику
|
||||
качества WeSpeaker.
|
||||
|
||||
Двуязычная `3dspeaker_speech_campplus_sv_zh_en_16k-common_advanced.onnx`
|
||||
быстрее и при известном числе участников лучше на одной из двух записей с
|
||||
таймкодами, но для автоматического режима не нашлось общего порога без лишних
|
||||
кластеров или склейки реальных голосов. Поэтому она не выбрана по умолчанию.
|
||||
|
||||
## Что проверялось
|
||||
|
||||
Свип выполнялся на трёх русскоязычных рабочих созвонах с известным составом:
|
||||
|
||||
| Запись | Участников | Короткий фрагмент | Полный прогон кандидата | SHA-256 |
|
||||
|---|---:|---:|---:|---|
|
||||
| `2026-07-10 Data Test внутренний статус.mp4` | 3 | 07:00–12:00 | 25:59,9 | `1057616B42E8ADD00E0EB975B02BDEF0EC9F6CDFEC6DBF488E0C60423C9B7B87` |
|
||||
| `2026-07-29 T2 BDMA уточнение задачи от Ильи.mp4` | 2 | 00:00–05:00 | 14:50,9 | `51866D247FE3EDA134CDD884F707B1F1DB8855B492E8BD14D2B56B62476255ED` |
|
||||
| `2026-08-12 Созвон с Максом Мерлином по T2 Forecast и Yantar.mp4` | 2 | 00:00–05:00 | 20:22,2 | `4422F04E2771091A0648E5422D14A31DD7CA2C8EEF7ED9F4A5C63F43D8CA6400` |
|
||||
|
||||
Для первой записи число участников взято из согласованного MoM и не зависит от
|
||||
диаризации. Для двух остальных рядом с медиа лежат транскрипты Hypescribe с
|
||||
таймкодами и метками спикеров. Они получены другим инструментом и использованы
|
||||
как независимая грубая опорная разметка.
|
||||
|
||||
Hypescribe ставит метку только в начале реплики и не размечает точные границы,
|
||||
тишину и наложения голосов. Поэтому ниже считается не DER, а **mapped speaker
|
||||
purity**: лучший взаимно-однозначный маппинг кластеров на опорные метки по
|
||||
суммарному перекрытию. Метрика подходит для сравнения конфигураций на одной
|
||||
записи, но не является абсолютной оценкой диаризации.
|
||||
|
||||
Кластер считается содержательным, если в нём не меньше `max(5 с, 2% длины
|
||||
записи)` речи. Это только диагностический показатель: готовый CLI не должен
|
||||
молча отбрасывать малые кластеры без отдельного решения.
|
||||
|
||||
## Модели
|
||||
|
||||
Во всех прогонах использовалась одна сегментация
|
||||
`sherpa-onnx-pyannote-segmentation-3-0`.
|
||||
|
||||
| Роль | Модель | Языки обучения | Размер | SHA-256 |
|
||||
|---|---|---|---:|---|
|
||||
| выбранная | `wespeaker_en_voxceleb_resnet34_LM.onnx` | английский, VoxCeleb2 | 26 530 550 | `E9848563DA86F263117134DFD7AD63C92355B37DE492B55E325400C9D9C39012` |
|
||||
| многоязычная альтернатива | `3dspeaker_speech_campplus_sv_zh_en_16k-common_advanced.onnx` | китайский + английский | 28 281 164 | `AA3CFC16963A10586A9393F5035D6D6B57E98D358B347F80C2A30BF4F00CEBA2` |
|
||||
| дополнительная разведка | `3dspeaker_speech_eres2net_base_sv_zh-cn_3dspeaker_16k.onnx` | китайский | 39 593 761 | `1A331345F04805BADBB495C775A6DDFFCDD1A732567D5EC8B3D5749E3C7A5E4B` |
|
||||
| сегментация | `model.onnx` из `sherpa-onnx-pyannote-segmentation-3-0` | — | 5 992 913 | `220AD67CA923BEF2FA91F2390C786097BF305BCEB5E261D4AF67B38E938E1079` |
|
||||
|
||||
WeSpeaker сам помечает VoxCeleb-модель как английскую и распространяет её под
|
||||
CC BY 4.0. Репозиторий 3D-Speaker и модель CAMPPlus на ModelScope используют
|
||||
Apache 2.0; в исходниках 3D-Speaker модель явно описана как обученная на
|
||||
большом китайско-английском корпусе. ONNX-файлы брались из официального релиза
|
||||
`k2-fsa/sherpa-onnx`, а не из сторонних зеркал.
|
||||
|
||||
Источники:
|
||||
|
||||
- [список и лицензирование моделей WeSpeaker](https://github.com/wenet-e2e/wespeaker/blob/master/docs/pretrained.md);
|
||||
- [карточка `wespeaker-voxceleb-resnet34-LM`](https://huggingface.co/Wespeaker/wespeaker-voxceleb-resnet34-LM);
|
||||
- [исходники и лицензия 3D-Speaker](https://github.com/modelscope/3D-Speaker);
|
||||
- [официальный релиз ONNX-моделей sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx/releases/tag/speaker-recongition-models).
|
||||
|
||||
## Свип WeSpeaker
|
||||
|
||||
Порог сначала проверялся крупным шагом, затем уточнялся около переходов между
|
||||
числом кластеров. В ячейках — общее число кластеров; жирным выделено точное
|
||||
совпадение с известным числом участников.
|
||||
|
||||
| Порог | Data Test, 3 | T2 BDMA, 2 | Yantar, 2 |
|
||||
|---:|---:|---:|---:|
|
||||
| 0,85 | **3** | **2** | 3 |
|
||||
| 0,87 | **3** | **2** | 3 |
|
||||
| 0,88 | **3** | **2** | 3 |
|
||||
| **0,89** | **3** | **2** | **2** |
|
||||
| 0,90 | 2 | **2** | **2** |
|
||||
| 0,95 | 2 | **2** | 1 |
|
||||
| явное `num_clusters` | **3** | **2** | **2** |
|
||||
|
||||
`0,89` — единственное проверенное значение, которое без знания числа
|
||||
участников дало правильное количество кластеров на всех трёх фрагментах. На
|
||||
двух записях с опорными метками purity составила 0,767 и 0,787. Явное число
|
||||
участников дало те же значения.
|
||||
|
||||
## Сравнение с 3D-Speaker
|
||||
|
||||
### CAMPPlus, китайский + английский
|
||||
|
||||
| Порог | Data Test, 3 | T2 BDMA, 2 | Yantar, 2 |
|
||||
|---:|---:|---:|---:|
|
||||
| 0,85 | 7 | 7 | 7 |
|
||||
| 0,90 | 6 | 5 | 5 |
|
||||
| 0,95 | 5 | 5 | 5 |
|
||||
| 0,99 | 4 | 4 | 4 |
|
||||
| 1,00 | 4 | 4 | 4 |
|
||||
| 1,05 | **3** | 3 | 3 |
|
||||
| 1,10 | 2 | **2** | 3 |
|
||||
| явное `num_clusters` | **3** | **2** | **2** |
|
||||
|
||||
При `1,05` на двух записях остаётся по одному малому остаточному кластеру, а
|
||||
при `1,10` трёхсторонняя встреча уже склеивается до двух голосов. Общего
|
||||
автоматического порога нет.
|
||||
|
||||
При явном числе участников purity равна 0,801 на T2 BDMA и 0,911 на Yantar.
|
||||
Это лучше WeSpeaker на 0,034 и 0,124 соответственно. Однако на контрольной
|
||||
трёхсторонней записи один из трёх принудительных кластеров оказался меньше
|
||||
порога содержательности, поэтому улучшение по двум текстовым прокси нельзя
|
||||
обобщать на все записи.
|
||||
|
||||
### ERes2Net base, китайский
|
||||
|
||||
Эта модель проверялась дополнительно, но не считается выполнением требования
|
||||
о многоязычной альтернативе. Даже на пороге 0,99 она дала 5 / 4 / 7 кластеров
|
||||
вместо 3 / 2 / 2. При явном числе участников purity составила 0,688 и 0,907:
|
||||
результат неоднородный и автоматический режим заметно хуже выбранного.
|
||||
|
||||
## Полные прогоны выбранного кандидата
|
||||
|
||||
После свипа `WeSpeaker + 0,89` прогнан на всех трёх записях целиком.
|
||||
|
||||
| Запись | Кластеры | Содержательные | Речь по кластерам, с | Остаток сверх ожидаемых | Purity | Время | RTF |
|
||||
|---|---:|---:|---|---:|---:|---:|---:|
|
||||
| Data Test | 4 | 3 | 573,1 / 487,4 / 342,2 / 19,1 | 1,3% | — | 189,0 с | 0,121 |
|
||||
| T2 BDMA | 2 | 2 | 748,7 / 52,3 | 0% | 0,749 | 112,3 с | 0,126 |
|
||||
| Yantar | 2 | 2 | 733,2 / 259,8 | 0% | 0,906 | 151,4 с | 0,124 |
|
||||
|
||||
На полной контрольной записи остаётся ложный кластер на 19,1 с, но три
|
||||
содержательных кластера совпадают с известным составом. Повторный полный прогон
|
||||
на 0,9 дал тот же результат: JSON-массивы всех 340 интервалов на 0,89 и 0,9
|
||||
совпали в точности, включая границы и номера кластеров. Поэтому к кандидату
|
||||
0,89 непосредственно применима слуховая проверка отрезка 07:00–12:00,
|
||||
выполненная для результата из
|
||||
[разведочного замера](2026-08-12-diarization-feasibility.md): три основных
|
||||
голоса стабильны, остаточный кластер ложный, есть небольшие пропуски второго
|
||||
голоса, а наложения голосов определяются не полностью. Новая калибровка не
|
||||
устраняет эти ограничения сегментации.
|
||||
|
||||
На двух полных разговорах purity отличается от короткого фрагмента: 0,749
|
||||
против 0,767 и 0,906 против 0,787. Это подтверждает, что короткий свип годится
|
||||
для отсева конфигураций, а финальный кандидат надо проверять целиком.
|
||||
|
||||
## Производительность
|
||||
|
||||
Условия: AMD Ryzen 7 8845H, Windows 11 build 26200, Python 3.13.13,
|
||||
`sherpa-onnx` 1.13.5, `onnxruntime` 1.28.0, NumPy 2.4.3, 8 потоков CPU.
|
||||
Загрузка моделей и декодирование медиа не входят в измерение.
|
||||
|
||||
Средний RTF на коротких фрагментах:
|
||||
|
||||
| Модель | RTF | Относительно WeSpeaker |
|
||||
|---|---:|---:|
|
||||
| WeSpeaker ResNet34 LM | 0,118 | 1,00× |
|
||||
| CAMPPlus zh/en | 0,083 | 0,70× |
|
||||
| ERes2Net base zh | 0,152 | 1,29× |
|
||||
|
||||
CAMPPlus примерно на 30% быстрее WeSpeaker в этом эксперименте. Это плюс для
|
||||
варианта с известным числом участников, но замер на AMD не заменяет приёмку на
|
||||
целевом Intel Core i5 11-го поколения.
|
||||
|
||||
## Воспроизводимость
|
||||
|
||||
Свип выполняется скриптом
|
||||
[`scripts/benchmarks/diarization_calibration.py`](../../scripts/benchmarks/diarization_calibration.py).
|
||||
Он принимает JSON-манифест с путями к моделям и записям, декодирует указанные
|
||||
фрагменты через ffmpeg, последовательно сохраняет каждый результат и может
|
||||
возобновить прерванный прогон.
|
||||
|
||||
Пример:
|
||||
|
||||
```powershell
|
||||
uv run python scripts/benchmarks/diarization_calibration.py `
|
||||
--manifest diarization-calibration.json `
|
||||
--output diarization-calibration-results.json `
|
||||
--work-dir .scratch/diarization-calibration `
|
||||
--threads 8
|
||||
```
|
||||
|
||||
Сырые JSON содержат локальные пути к конфиденциальным рабочим записям и сами
|
||||
интервалы диаризации, поэтому в репозиторий не добавляются. Для проверки
|
||||
артефактов выше приведены SHA-256 медиа и моделей.
|
||||
|
||||
## Ограничения и следующий шаг
|
||||
|
||||
- Три записи принадлежат одному типу русскоязычных рабочих созвонов; это не
|
||||
репрезентативная выборка для всех микрофонов, шумов и акцентов.
|
||||
- Опорные метки двух записей грубые и не дают посчитать DER.
|
||||
- На слух проверен только фрагмент 07:00–12:00 контрольной записи; перед
|
||||
выпуском нужен слуховой контроль плотного диалога на финальной сборке.
|
||||
- Калибровка выбирает эмбеддинги и кластеризацию, но не решает ошибки границ и
|
||||
неполное распознавание наложений голосов.
|
||||
- Производительность должна отдельно приниматься на целевом Intel Core i5.
|
||||
|
||||
Для спецификации зафиксировать WeSpeaker + 0,89 как автоматический дефолт,
|
||||
отдельную опцию явного числа участников и отсутствие автоматического
|
||||
отбрасывания малых кластеров. CAMPPlus zh/en можно оставить кандидатом для
|
||||
будущего режима с обязательным `num_clusters` после расширенной слуховой
|
||||
проверки.
|
||||
Reference in New Issue
Block a user