# Калибровка модели эмбеддингов и порога диаризации **Дата:** 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` после расширенной слуховой проверки.