docs(diarization): собраны исследования и калибровка для карты #18
+43
-14
@@ -181,27 +181,56 @@ LLM для чистки текста, сопоставление Speaker N с и
|
||||
|
||||
### Диаризация — разделение говорящих
|
||||
|
||||
**Что:** Опциональный пост-процессинг (не четвёртый бэкенд): сегменты
|
||||
уже несут таймкоды; диаризация даёт интервалы «кто когда говорил»;
|
||||
merge по перекрытию интервалов; formatter ломает абзац на смене спикера
|
||||
и подписывает `Speaker 1:`. Ставится как extra:
|
||||
`uv sync --extra diarization`.
|
||||
**Что:** Опциональный пост-процессинг (не четвёртый бэкенд): диаризация
|
||||
даёт интервалы «кто когда говорил», результат сводится с сегментами ASR,
|
||||
formatter ломает абзац на смене спикера и подписывает `Speaker 1:`.
|
||||
Ставится как extra: `uv sync --extra diarization`.
|
||||
|
||||
**Почему:** Без спикеров MoM не собрать — это ключевой разрыв с облаком
|
||||
по внешнему ревью, и никакое качество распознавания его не компенсирует.
|
||||
Заодно естественно решает «разбивку на реплики» (приоритет №4).
|
||||
|
||||
**Варианты реализации (ключевое решение, нужен ADR):**
|
||||
**Промежуточный статус 2026-08-12:** проведена разведка, описанная в
|
||||
[разведочном замере диаризации](benchmarks/2026-08-12-diarization-feasibility.md).
|
||||
Она закрыла вопрос о движке и открыла более важный вопрос о единице привязки.
|
||||
|
||||
- **sherpa-onnx** — диаризация целиком на onnxruntime (сегментация
|
||||
pyannote в ONNX + спикер-эмбеддинги), без torch, в духе нашего
|
||||
onnx-стека и «no cloud, no API keys».
|
||||
- **pyannote.audio** — стандарт качества, но тянет torch и требует
|
||||
HF-токен с принятием лицензии моделей — трение с духом проекта.
|
||||
**Движок — вопрос практически закрыт.** `sherpa-onnx` ставится на Windows с
|
||||
Python 3.13, содержит готовый `OfflineSpeakerDiarization`, не тянет torch и не
|
||||
требует токена Hugging Face; модели сегментации и эмбеддингов весят около 33 МБ.
|
||||
Скорость — 11,1× RTFx, то есть примерно полторы длительности ASR. Вариант
|
||||
`pyannote.audio` остаётся отклонённым по прежней причине: torch и HF-токен с
|
||||
принятием лицензии. Отдельный ADR имеет смысл заводить вместе с решением о
|
||||
единице привязки, а не только про движок.
|
||||
|
||||
**Уточнить перед запуском:** качество обоих вариантов на русской речи
|
||||
и перекрывающихся репликах; скорость на CPU (диаризация — второй проход
|
||||
по всему аудио); лицензии моделей сегментации/эмбеддингов.
|
||||
Замечание для будущих заходов: обе ML-части диаризации уже лежат в
|
||||
`onnx-asr` 0.12 — `PyAnnoteVad` содержит полную локальную сегментацию pyannote
|
||||
(powerset на трёх спикеров, склейка окон), а `WespeakerEmbeddings` даёт
|
||||
эмбеддинги. Публичный API схлопывает сегментацию до речь/не-речь, `load_se` не
|
||||
экспортирован, кластеризации нет. Собирать диаризацию самим на этих деталях —
|
||||
экономия 33 МБ ценой опоры на приватный API; при разведке этот путь не
|
||||
выбирался.
|
||||
|
||||
**Единица привязки — настоящая развилка, решения нет.** Схема «мажоритарный
|
||||
спикер на весь ASR-сегмент», записанная здесь раньше, замером не подтвердилась:
|
||||
27% сегментов содержат не менее секунды чужой речи, и на них приходится больше
|
||||
половины времени транскрипта. Причина — границы сегментов идут по тишине
|
||||
(Silero VAD), а в ВКС собеседники отвечают встык. Варианты:
|
||||
|
||||
- **пословная привязка** — `onnx-asr` отдаёт потокенные таймкоды
|
||||
(`TimestampedResult`), сегмент режется на границе токена при смене
|
||||
говорящего; ASR по-прежнему видит длинное аудио, контекст RNN-T и пунктуация
|
||||
не страдают. Недоступно на OpenVINO GenAI — там потокенных таймкодов нет;
|
||||
- **диаризация первым проходом**, ASR по интервалам говорящего — чистота
|
||||
гарантирована, но короткие куски лишают RNN-T контекста и портят пунктуацию;
|
||||
- **привязка к сегменту с честной пометкой** — оставить огрубление, но считать
|
||||
чистоту и предупреждать в шапке, как уже делается для повторов и потери
|
||||
хвоста.
|
||||
|
||||
**Уточнить перед запуском:** воспроизводится ли доля 27% на других записях,
|
||||
включая разговор на двоих; правильность границ диаризации на слух, а не только
|
||||
совпадение числа говорящих; калибровка порога кластеризации (на пороге из
|
||||
примеров получилось 29 спикеров вместо трёх); эмбеддинги, обученные не только
|
||||
на английском; производительность на целевом Intel Core i5.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
# Разведочный замер диаризации sherpa-onnx
|
||||
|
||||
**Дата:** 2026-08-12
|
||||
|
||||
**Статус:** разведка на одной записи и одной нецелевой машине. Не приёмка.
|
||||
|
||||
## Цель
|
||||
|
||||
Ответить на два вопроса перед проектированием диаризации:
|
||||
|
||||
1. Сколько времени диаризация добавляет к транскрипции.
|
||||
2. Достаточно ли приписывать спикера целому ASR-сегменту по мажоритарному
|
||||
перекрытию — то есть допустима ли схема из
|
||||
[бэклога](../backlog.md#диаризация--разделение-говорящих) без пословной
|
||||
привязки.
|
||||
|
||||
Ни модель по умолчанию, ни код проекта в рамках замера не менялись. Все скрипты
|
||||
выполнялись вне репозитория.
|
||||
|
||||
## Ограничения замера
|
||||
|
||||
Результаты ниже — разведка, а не основание для решения:
|
||||
|
||||
- **одна запись** вместо трёх, принятых в [ADR-006](../adr/006-onnx-asr-backend.md);
|
||||
- **нецелевая машина**: AMD Ryzen 7 8845H, тогда как приёмка производительности
|
||||
по бэклогу требует Intel Core i5 11-го поколения;
|
||||
- **границы диаризации не проверены на слух** — сверялось только число
|
||||
говорящих и косвенный текстовый признак;
|
||||
- **порог кластеризации подобран по этой же записи**, то есть на ней же и
|
||||
проверен.
|
||||
|
||||
## Оборудование и условия
|
||||
|
||||
- ноутбук Lenovo 83D5 (та же машина, что в
|
||||
[сравнении turbo](2026-08-12-openvino-large-v3-turbo-comparison.md#повторный-прогон-на-amd-ryzen-7-8845h));
|
||||
- AMD Ryzen 7 8845H, 8 ядер / 16 логических процессоров;
|
||||
- 29,8 ГиБ LPDDR5X;
|
||||
- Windows 11 Корпоративная, сборка 26200, схема питания «Сбалансированная»;
|
||||
- Python 3.13.13, `onnx-asr` 0.12.0, `onnxruntime` 1.28.0,
|
||||
`faster-whisper` 1.2.1, `sherpa-onnx` 1.13.5;
|
||||
- прогоны последовательные, без конкурирующей нагрузки;
|
||||
- модели предварительно скачаны; время загрузки моделей в замер не входит.
|
||||
|
||||
## Контрольная запись
|
||||
|
||||
| Файл | Длительность | Размер | SHA-256 |
|
||||
|---|---|---|---|
|
||||
| `2026-07-10 Data Test внутренний статус.mp4` | 26:00 | 34 217 769 | `1057616B42E8ADD00E0EB975B02BDEF0EC9F6CDFEC6DBF488E0C60423C9B7B87` |
|
||||
|
||||
Запись выбрана потому, что рядом лежит согласованный MoM, из которого известен
|
||||
состав: **три участника** — Маша, Дима, Роман. Это даёт независимую опорную
|
||||
точку для проверки числа говорящих.
|
||||
|
||||
## Модели диаризации
|
||||
|
||||
| Роль | Модель | Размер | Источник |
|
||||
|---|---|---|---|
|
||||
| Сегментация | `sherpa-onnx-pyannote-segmentation-3-0` | 6,9 МБ | релизы `k2-fsa/sherpa-onnx` |
|
||||
| Эмбеддинги | `wespeaker_en_voxceleb_resnet34_LM.onnx` | 26,5 МБ | релизы `k2-fsa/sherpa-onnx` |
|
||||
|
||||
Обе загружаются в `onnxruntime`, torch и токен Hugging Face не требуются.
|
||||
Эмбеддинги обучены на англоязычном VoxCeleb; их пригодность для русской речи в
|
||||
этом замере не проверялась.
|
||||
|
||||
## Стоимость по времени
|
||||
|
||||
Параметры ASR — модель по умолчанию ONNX-пути, `gigaam-v3-e2e-rnnt` INT8,
|
||||
язык задан явно.
|
||||
|
||||
| Стадия | Время | RTFx |
|
||||
|---|---:|---:|
|
||||
| Декодирование аудио | 1,6 с | ~990× |
|
||||
| ASR `gigaam-v3-e2e-rnnt` INT8 | 95,3 с | 16,4× |
|
||||
| Диаризация, 8 потоков | 140,7 с | 11,1× |
|
||||
| **Последовательно, итого** | **236 с** | **6,6×** |
|
||||
|
||||
Диаризация дороже самой транскрипции и занимает около 60% общего времени. При
|
||||
последовательном исполнении 26-минутная запись обрабатывается 3,9 минуты вместо
|
||||
1,6; часовая — примерно 9 минут вместо 3,7.
|
||||
|
||||
Масштабирование по потокам слабое: 4 потока дают 154 с, 8 потоков — 141 с,
|
||||
выигрыш 9%. Закладываться на увеличение числа потоков не следует.
|
||||
|
||||
Проходы ASR и диаризации независимы по данным, поэтому их можно совместить во
|
||||
времени; тогда общее время стремится к максимуму из двух, а не к сумме. Прямой
|
||||
замер параллельного режима не проводился.
|
||||
|
||||
Пиковую память процесса снять не удалось из-за ошибки в измерительном скрипте.
|
||||
|
||||
## Порог кластеризации
|
||||
|
||||
Число говорящих подбиралось автоматически (`num_clusters=-1`); варьировался
|
||||
порог `FastClusteringConfig.threshold`.
|
||||
|
||||
| Конфигурация | Найдено спикеров | Из них с речью ≥30 с | Речь по спикерам, мин |
|
||||
|---|---:|---:|---|
|
||||
| порог 0,5 | 29 | 11 | 7,4 / 5,5 / 1,6 / 1,2 |
|
||||
| порог 0,7 | 12 | 7 | 7,4 / 7,0 / 2,2 / 2,2 |
|
||||
| **порог 0,9** | **4** | **3** | **9,6 / 8,1 / 5,7 / 0,3** |
|
||||
| явное `k=5`, порог 0,5 | 4 | 3 | 9,6 / 8,1 / 5,7 / 0,3 |
|
||||
|
||||
На пороге 0,9 число содержательных кластеров совпало с составом из MoM: три
|
||||
говорящих с 9,6, 8,1 и 5,7 минуты речи плюс остаточный кластер на 0,3 минуты.
|
||||
На пороге 0,5 получилось 29 говорящих вместо трёх — десятикратное
|
||||
переразбиение.
|
||||
|
||||
Время от порога не зависит (153–157 с во всех конфигурациях): кластеризация
|
||||
стоит доли секунды, платится за сегментацию и эмбеддинги.
|
||||
|
||||
Два следствия. Первое: порог кластеризации — основная ручка качества, и
|
||||
значение по умолчанию из примеров `sherpa-onnx` для этого материала непригодно.
|
||||
Второе: одного удачного совпадения на одной записи недостаточно, чтобы принять
|
||||
0,9 за дефолт, а явное указание числа участников нужно как страховка.
|
||||
|
||||
## Чистота ASR-сегментов
|
||||
|
||||
Основной вопрос замера. Для каждого из 274 ASR-сегментов посчитано перекрытие
|
||||
с интервалами каждого говорящего (диаризация на пороге 0,9, 340 интервалов).
|
||||
Чистота — доля мажоритарного говорящего в суммарном перекрытии сегмента.
|
||||
|
||||
| Категория | Сегментов | Доля | Времени |
|
||||
|---|---:|---:|---:|
|
||||
| Чистых, один говорящий | 164 | 59,9% | 8,3 мин |
|
||||
| С поддакиванием, <1 с чужой речи | 30 | 10,9% | 2,4 мин |
|
||||
| **С чужой репликой, ≥1 с** | **74** | **27,0%** | **11,2 мин** |
|
||||
| Без спикера вообще | 6 | 2,2% | 0,0 мин |
|
||||
|
||||
| Порог чистоты | Сегментов ниже порога | Доля | Времени |
|
||||
|---|---:|---:|---:|
|
||||
| < 0,95 | 100 | 36,5% | 13,0 мин |
|
||||
| < 0,90 | 91 | 33,2% | 11,6 мин |
|
||||
| < 0,80 | 72 | 26,3% | 9,1 мин |
|
||||
| < 0,70 | 56 | 20,4% | 7,1 мин |
|
||||
|
||||
**27% сегментов содержат не менее секунды чужой речи, и на них приходится
|
||||
11,2 минуты из 21,9 — больше половины транскрипта.** У 20% сегментов
|
||||
мажоритарный говорящий занимает менее двух третей сегмента.
|
||||
|
||||
### Подтверждение из текста ASR
|
||||
|
||||
Загрязнённые сегменты содержат диалог, и это видно независимо от диаризации —
|
||||
`gigaam-v3-e2e` обучена с диалоговой пунктуацией и сама ставит тире на смене
|
||||
реплики:
|
||||
|
||||
```
|
||||
[533,8-551,1] 17,3 с, мажоритарный spk3, чистота 0,67
|
||||
«— В нашем, по-моему.— В нашем?— В нашем.— А, отлично.— Ну, у нас просто
|
||||
есть некий дата-тест, который на самом деле...— Мы же у них не
|
||||
разворачиваем.—»
|
||||
|
||||
[432,9-448,6] 15,7 с, мажоритарный spk2, чистота 0,58
|
||||
«Дим, а вот то, что ты из образа вытаскивал, там есть чё-то на что
|
||||
посмотреть?— Бэг, бэг там есть, пи»
|
||||
|
||||
[694,2-706,5] 12,3 с, мажоритарный spk2, чистота 0,37
|
||||
spk2 = 5,4 с, spk1 = 4,9 с, spk3 = 4,3 с — три человека в одном сегменте
|
||||
```
|
||||
|
||||
Акустическая диаризация и пунктуация ASR указывают на одно и то же, поэтому
|
||||
доля 27% вряд ли объясняется только ошибками кластеризации.
|
||||
|
||||
Причина загрязнения — в способе нарезки: границы сегментов на ONNX-пути даёт
|
||||
Silero VAD, то есть они проходят по тишине. В разговоре по ВКС участники
|
||||
отвечают встык, паузы длиной с порог VAD не возникает, и диалог попадает в один
|
||||
сегмент. Предположение о том, что задержка канала связи сама создаёт паузу на
|
||||
смене говорящего, этими данными не подтверждается.
|
||||
|
||||
Приведённые сегменты — сырой выход ASR. `formatter.py` объединяет их в абзацы
|
||||
длиной до 60 секунд, поэтому в готовом транскрипте загрязнение будет выше.
|
||||
|
||||
## Выводы
|
||||
|
||||
- Диаризация через `sherpa-onnx` работает на целевой платформе без torch и без
|
||||
токена Hugging Face; связка сегментация + эмбеддинги весит около 33 МБ.
|
||||
- Стоимость — 11,1× RTFx, примерно 1,5 длительности ASR. Последовательный
|
||||
запуск даёт 2,5-кратное замедление, совмещение проходов может сократить
|
||||
накладные расходы, но отдельно не измерялось.
|
||||
- Автоматическая оценка числа говорящих с порогом из примеров даёт 29 спикеров
|
||||
вместо трёх. Порог требует калибровки, а явное указание числа участников —
|
||||
отдельной ручки.
|
||||
- Привязка спикера к целому ASR-сегменту по мажоритарному перекрытию
|
||||
огрубляет результат существенно: 27% сегментов и больше половины времени
|
||||
транскрипта содержат чужую речь длиннее секунды. Схема из бэклога в этом виде
|
||||
непригодна.
|
||||
- `onnx-asr` отдаёт потокенные таймкоды (`TimestampedResult`), поэтому
|
||||
пословная привязка на ONNX-пути достижима. У OpenVINO GenAI такого выхода
|
||||
нет, что ограничивает диаризацию на `--device openvino-*`.
|
||||
|
||||
## Что нужно проверить дальше
|
||||
|
||||
- Повторить измерение чистоты сегментов ещё на двух-трёх записях, включая
|
||||
разговор на двоих, и убедиться, что 27% — не свойство именно этой встречи.
|
||||
- Проверить границы диаризации на слух или сверкой с внешним транскриптом:
|
||||
совпадение числа говорящих не доказывает правильность интервалов.
|
||||
- Сравнить эмбеддинги, обученные не только на английском, на русской речи.
|
||||
- Измерить производительность на целевом Intel Core i5 11-го поколения.
|
||||
- Измерить параллельный режим ASR и диаризации.
|
||||
Reference in New Issue
Block a user