- Зачем: - решения по неопределённым словам и CPU-стоимости должны быть проверяемыми до публикации реализации. - Что: - задокументировано отклонение post-hoc сглаживания Speaker ? по соседним кластерам. - добавлено исследование стоимости каскада и облегчённых embedding-моделей. - Проверка: - `git diff --cached --check` — без ошибок.
13 KiB
ADR-007: Пословная диаризация через sherpa-onnx
Статус: Принято Дата: 2026-08-14
Контекст
Разделение говорящих — главный структурный разрыв между локальным транскриптом и облачными сервисами в сценарии подготовки конспектов и протоколов встреч. Диаризация при этом не является ещё одним движком распознавания: она независимо строит разметку говорящих, которую затем нужно свести с результатом ASR.
Привязка одного говорящего ко всему сегменту распознавания оказалась слишком грубой. На трёх русскоязычных рабочих созвонах чужая реплика не короче секунды встретилась в 6–7% сегментов разговоров на двоих и в 27% сегментов встречи втроём. Сегменты распознавания проходят по тишине, а не по смене говорящего, поэтому сохранить контекст RNN-T и получить реплики можно только через более мелкую единицу сведения.
Эксперимент
Локальная связка sherpa-onnx с сегментацией Pyannote 3.0 и эмбеддингами
WeSpeaker ResNet34 LM проверена на трёх записях с известным составом. Для
автоматического определения числа голосовых кластеров выбран порог 0,89: это
единственное проверенное значение, которое на трёх контрольных фрагментах дало
3 / 2 / 2 кластера. На полной встрече втроём остался ложный кластер длительностью
19,1 секунды; поэтому малые кластеры нельзя молча отбрасывать, а разметку нельзя
считать эталоном точных границ и перекрывающейся речи.
Двуязычная CAMPPlus zh/en оказалась примерно на 30% быстрее и при известном числе участников улучшила прокси-метрику на двух записях, но для неё не нашлось общего автоматического порога без лишних кластеров или склейки реальных голосов. Поэтому она остаётся кандидатом только для будущего режима с обязательным явным числом участников, а не для первой версии.
На доступном слабом Intel baseline, Core i7-6820HQ с урезанным питанием, последовательные ASR и диаризация обработали час записи примерно за 23 минуты. Диаризация увеличивает полное время примерно в 2,4 раза, но остаётся быстрее реального времени и приемлема как явно включаемая функция. Конкретный Core i5 11-го поколения не проверен, поскольку такого устройства нет.
Исходные данные и ограничения зафиксированы в отчётах о калибровке, смешении говорящих и производительности на Intel.
Сглаживание неизвестного говорящего
После приёмки отдельно проверена идея автоматически назначать Speaker ?
известному говорящему, если короткий неизвестный фрагмент находится между двумя
репликами одного и того же Speaker N. На трёх контрольных транскриптах найдено
143 таких неизвестных фрагмента, содержащих 309 слов. У 79 фрагментов соседи
имели одинаковую метку, у 64 — разные.
Для T2 BDMA и Yantar результат грубо сопоставлен с независимыми Hypescribe- транскриптами. Это не gold-разметка: их границы округлены до секунды, а готовый speaker Markdown хранит только начало реплики. Тем не менее эвристика не показала достаточной селективности:
| Эвристика | Изменённых фрагментов на трёх записях | Совпадение на двух записях с опорой |
|---|---|---|
| Все неизвестные между одинаковыми соседями | 79 | 22 / 42 (52%) |
| Только один неизвестный word | 35 | 11 / 20 (55%) |
| Не больше двух words | 50 | 15 / 30 (50%) |
| Одинаковый отображённый timestamp | 42 | 14 / 22 (64%) |
Короткие Да, Нет и Угу часто являются самостоятельной репликой другого
участника. В одном из контрольных случаев неизвестный фрагмент — Угу. — А
даже содержал границу двух голосов, хотя с обеих сторон находился один и тот же
кластер. Поэтому post-hoc сглаживание по соседям скрывает полезную
неопределённость и не применяется. Speaker ? остаётся явным результатом ничьей
или отсутствия временного перекрытия.
Если к этой задаче возвращаться, проверять нужно исходные границы Word и
SpeakerInterval до группировки: отдельно различать нулевой timestamp, реальный
зазор между интервалами и ничью перекрытий. По готовому Markdown такая проверка
невозможна, потому что в нём уже потеряны доли секунды, конец слова и причина
неопределённости.
Решение
Диаризацию реализуем как явно включаемый пост-процессинг через sherpa-onnx.
Первая версия использует Pyannote segmentation 3.0, WeSpeaker ResNet34 LM,
порог кластеризации 0,89 и автоматическое число кластеров; известное число
участников можно передать явно.
Говорящий назначается слову с временной привязкой, а не сегменту распознавания. Каждый ASR-бэкенд приводит свой результат к общему набору слов с положением на временной шкале. Проходы ASR и диаризации независимо получают одно аудио, после чего отдельная операция сводит слова с интервалами разметки говорящих и объединяет соседние слова одного говорящего в реплики. Распознавание по-прежнему выполняется на полных сегментах и сохраняет контекст модели.
Диаризация не вводит грубый fallback на целый сегмент и не переключает устройство ASR ради получения пословных таймкодов. Существующий GPU→CPU fallback распознавания сохраняется и завершается до диаризации. Отсутствие пословных таймкодов или ошибка инициализации диаризатора останавливают запуск до ASR. Ошибка диаризации конкретного файла после успешного ASR не уничтожает полезный результат: сохраняется обычный транскрипт с явным предупреждением и ненулевым статусом, а батч продолжает остальные файлы. Порядок первой реализации, время жизни диаризатора, кеш и подробная матрица поведения находятся в спецификации.
Последствия
- Общий контракт результата распознавания расширяется каноническими словами с временной привязкой; сегменты распознавания сохраняются для совместимости и контроля качества.
- FasterWhisper, ONNX-ASR и OpenVINO должны экспортировать один и тот же пословный контракт. OpenVINO GenAI 2026.x уже предоставляет нужные таймкоды, поэтому ограничение находится в адаптере проекта, а не в движке.
sherpa-onnxстановится обычной runtime-зависимостью, а две модели диаризации скачиваются и кешируются лениво при первом запросе.- Выход остаётся линейным Markdown с анонимными метками
Speaker N. Сопоставление голосов с именами и специальная запись перекрывающейся речи не входят в ядро CLI. - Последовательный режим задаёт корректный baseline. Параллельный запуск и автоматическое включение на мощных устройствах требуют отдельных измерений после стабилизации.
Отклонённые альтернативы
| Альтернатива | Почему отклонена |
|---|---|
| Не делать диаризацию | Оставляет главный продуктовый разрыв, хотя измеренная стоимость допустима для явной функции |
| Мажоритарный говорящий на весь сегмент распознавания | Теряет чужие реплики на всех трёх проверенных записях |
| Сначала диаризация, затем ASR коротких интервалов | Лишает RNN-T длинного контекста и ухудшает согласование и пунктуацию |
pyannote.audio |
Тянет PyTorch и требует Hugging Face token с принятием лицензии |
Сборка поверх приватных деталей onnx-asr |
Экономит небольшую отдельную зависимость ценой нестабильного внутреннего API и собственной кластеризации |
| Параллельные проходы в первой версии | Нет прямого benchmark и измеренного общего пика памяти; сначала нужен корректный последовательный baseline |
Автоматически назначать Speaker ? одинаковому соседнему кластеру |
На двух записях с независимой опорой совпало только 52% назначений; лимиты по словам и округлённому времени не отделили короткие ответы другого участника |