- Зачем: - необходим устойчивый дефолт модели эмбеддингов и порога на русской речи. - Что: - задокументирован выбор 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.
210 lines
14 KiB
Markdown
210 lines
14 KiB
Markdown
# Калибровка модели эмбеддингов и порога диаризации
|
||
|
||
**Дата:** 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` после расширенной слуховой
|
||
проверки.
|