Files
local-transcriber/docs/specs/2026-08-14-speaker-diarization.md
Dmitriy Dementiev b8347004da docs(diarization): уточнены правила сведения и формат
- Зачем:
  - спецификация не должна оставлять неоднозначности перед реализацией диаризации.
- Что:
  - уточнены сведение слов, таймкоды и предупреждение о малом кластере.
  - очищены терминология и граница между ADR и спецификацией.
  - сохранён результат сравнения WeSpeaker и CAMPPlus.
- Проверка:
  - git diff --cached --check и проверка относительных ссылок.
2026-08-14 17:09:27 +03:00

16 KiB
Raw Permalink Blame History

Диаризация говорящих в транскрипте

Проблема

Текущий транскрипт знает только сегменты распознавания. Их границы проходят по тишине и не совпадают со сменой говорящего, поэтому один сегмент может содержать несколько реплик. Назначение одной метки всему сегменту искажает структуру диалога и делает транскрипт слабым сырьём для конспекта или протокола встречи.

ADR-007 выбирает явную пословную диаризацию через sherpa-onnx. Эта спецификация фиксирует форму первой реализации, не меняя принятые решения.

Цели

  • По явному запросу строить реплики говорящих, сохраняя текст и длинный контекст ASR.
  • Поддержать один контракт слов с временной привязкой на FasterWhisper, ONNX-ASR и OpenVINO.
  • Сохранить предсказуемый single- и batch-режим при отсутствии речи, ошибках диаризации и малых голосовых кластерах.
  • Выдать компактный Markdown, удобный и человеку, и последующей обработке LLM.

Не входит

  • Автоматическое включение диаризации без флага и --no-diarize.
  • Параллельный запуск ASR и диаризации.
  • Сопоставление Speaker N с именами участников.
  • Постоянный кеш разметки говорящих, голосовые эмбеддинги и диагностические файлы.
  • Отдельный синтаксис для перекрывающейся речи.
  • Изменение устройства ASR или диаризации ради восстановления функции.

Пользовательский интерфейс

  • --diarize включает диаризацию. По умолчанию она выключена; ключ в .transcriber.toml в первой версии не добавляется.
  • --speakers N, где N >= 1, задаёт известное число участников и сам включает диаризацию. Без него число кластеров определяется автоматически.
  • --threads N остаётся единым бюджетом активного CPU-прохода. Значение целиком получает сначала ASR, затем диаризация; 0 оставляет настройки библиотек.
  • --verbose показывает в консоли прогресс, число кластеров и интервалов, длительность прохода и предупреждения, но не создаёт дополнительные файлы.
  • --force пересчитывает и ASR, и диаризацию. Без него готовый транскрипт, как и сейчас, пропускается целиком.

sherpa-onnx входит в обычные runtime-зависимости. Модели sherpa-onnx-pyannote-segmentation-3-0 и wespeaker_en_voxceleb_resnet34_LM.onnx скачиваются и кешируются лениво при первом запросе диаризации. Отдельного installation extra нет.

Контракты данных

Результат ASR сохраняет существующие сегменты распознавания и дополнительно содержит упорядоченные канонические слова. Для каждого слова известны текст, начало и конец на временной шкале исходной записи. Backend-специфичные токены и слова нормализуются в адаптере бэкенда; их обратная сборка должна сохранять распознанный текст с точностью до нормализации пробелов.

Разметка говорящих хранится отдельно от результата ASR: это упорядоченные временные интервалы с анонимным идентификатором голосового кластера. ASR-бэкенд не знает о кластерах, а диаризатор не знает о распознанном тексте.

Операция сведения суммирует временное перекрытие слова с интервалами каждого кластера и назначает кластер с единственным наибольшим ненулевым перекрытием. Если пересечения нет либо несколько кластеров делят наибольшее значение, слово получает неизвестного говорящего: порядок кластеров не используется как искусственная развязка ничьей. Соседние слова одного говорящего объединяются в реплику; порядок слов и исходная временная шкала не меняются.

Все три ASR-пути обязаны предоставлять пословный контракт до включения диаризации:

Путь Источник временных привязок
FasterWhisper word timestamps CTranslate2
ONNX-ASR timestamped result модели
OpenVINO word-level timestamps WhisperPipeline

Грубая подстановка метки на весь сегмент распознавания запрещена.

Пайплайн и время жизни

После prescan и только при наличии файлов для обработки загружаются ASR-модель и один batch-owned диаризатор. До первого ASR проверяются доступность пословных таймкодов, модели диаризации и возможность создать диаризатор. Ошибка этого этапа останавливает весь запуск без частичных транскриптов.

Каждый файл обрабатывается последовательно:

  1. ASR;
  2. диаризация, если ASR нашёл речь;
  3. сведение слов с разметкой говорящих;
  4. форматирование и запись Markdown.

Один диаризатор последовательно переиспользуется для всех файлов батча. Данные конкретной записи не становятся состоянием следующей. Объект освобождается при завершении команды и не переносится через TranscribeFileResult.

Разметка говорящих хранится только до сведения. Единственный постоянный продуктовый артефакт — Markdown-транскрипт; локальный кеш файлов моделей живёт по существующим правилам загрузчиков.

Конфигурация диаризации

Автоматический режим использует:

  • Pyannote segmentation 3.0;
  • WeSpeaker ResNet34 LM;
  • порог кластеризации 0,89;
  • автоматическое число кластеров.

--speakers N передаёт явное число кластеров вместо автоматического. Для предупреждения используется диагностическая граница из калибровки: малым считается кластер с речью короче максимума из 5 секунд и 2% длительности записи. Граница влияет только на предупреждение — кластер не отбрасывается, получает обычный номер и не меняет статус команды.

Формат Markdown

При двух и более найденных кластерах тело состоит из линейных реплик:

[09:07] Speaker 1: Мы же у них не разворачиваемся…

[09:18] Speaker 2: Мне гораздо проще накатывать обновления…
  • Печатается только начало реплики; доли секунды отбрасываются, а не округляются (09:07.96[09:07]). Для записей длиннее часа используется [HH:MM:SS], иначе [MM:SS].
  • Метка Speaker N не получает Markdown-выделение.
  • Нумерация начинается заново для каждого файла; номера назначаются по порядку первого появления кластера в словах транскрипта.
  • Смена говорящего всегда начинает новую реплику.
  • Речь одного говорящего дополнительно разбивается по паузе не меньше 2 секунд и максимальной длительности реплики 60 секунд.
  • Слова без назначенного кластера группируются под Speaker ?.
  • Перекрывающаяся речь остаётся в хронологическом порядке без особого синтаксиса.
  • В шапку добавляется число голосовых кластеров и предупреждения. Таблица длительности по кластерам не выводится.

Без успешной разметки нескольких говорящих сохраняется нынешний формат обычного транскрипта с диапазонами времени.

Деградация и статус команды

Ситуация Артефакт Консоль и шапка Статус
Диаризация не запрошена Обычный транскрипт Без новых сообщений Текущий
ASR не нашёл речь Текущий пустой Markdown Речь не обнаружена; диаризация не запускалась 0
Найдено не меньше двух кластеров Транскрипт с Speaker N Число кластеров и предупреждения 0, если нет иной ошибки
Найден один кластер Обычный транскрипт без Speaker 1 Причина в консоли и шапке Ненулевой
Есть слова без пересечения Транскрипт с Speaker ? Число таких слов 0
Есть малый кластер Транскрипт со всеми кластерами Длительность малого кластера 0
Разметка пуста при непустом ASR Обычный транскрипт Явное предупреждение Ненулевой
Ошибка диаризации конкретного файла Обычный транскрипт Явное предупреждение Ненулевой
Нет пословных таймкодов или не инициализировался диаризатор Файлы не обрабатываются Понятная ошибка до ASR Ненулевой

В батче деградированный файл записывается, учитывается как неуспешная диаризация, а остальные файлы продолжают обрабатываться. Итоговый статус батча ненулевой, если хотя бы один файл деградировал или завершился ошибкой.

Критерии приёмки

Автоматические проверки

  • Адаптер каждого ASR-бэкенда возвращает монотонные слова с временной привязкой; сборка слов сохраняет текст сегментов с точностью до пробелов.
  • Сведение покрывает смену говорящего, отсутствие пересечения, равное наибольшее перекрытие с результатом Speaker ?, пунктуацию на границе реплик и хронологический порядок.
  • Форматтер проверяется для обычных, часовых, неизвестных и малых кластеров, Speaker ?, отбрасывания долей таймкода, паузы 2 секунды и предела 60 секунд.
  • CLI проверяет несовместимые и граничные значения, не запускает ASR при ошибке preflight и соблюдает всю матрицу деградации в single- и batch-режимах.
  • Батч создаёт диаризатор ровно один раз, пропускает его для пустого ASR, переиспользует между файлами и не пишет промежуточный кеш.
  • --threads, --verbose и --force сохраняют описанную семантику.

Все автоматические тесты мокают движки и не скачивают реальные модели.

Ручная проверка

Финальная сборка прогоняется на трёх записях из отчётов карты:

  • текст до и после сведения совпадает с точностью до переносов и пробелов;
  • на плотном диалоге вручную проверяются устойчивость «голос → кластер», смены говорящего, пропуски, малый остаточный кластер и перекрывающаяся речь;
  • автоматическая конфигурация воспроизводит наблюдённую форму результата: три основных и один малый остаточный кластер на Data Test, по два кластера на T2 BDMA и Yantar;
  • последовательный прогон на доступном Intel baseline остаётся быстрее реального времени; фактические wall time и peak RSS записываются рядом с результатом проверки.

Документация

README должен описать новые CLI-флаги, ленивую загрузку моделей, ожидаемую стоимость, формат Speaker N, предупреждения и batch-поведение. Направления «Диаризация» и «Ручка нарезки абзацев» удаляются из backlog: первое перешло в эту спецификацию, второе закрыто разрывом реплики на смене говорящего.

После стабилизации отдельно рассматриваются параллельный запуск и hardware-aware default.