docs(research): выбрана схема оценки числа говорящих #38
@@ -0,0 +1,402 @@
|
||||
# Оценка числа говорящих для быстрых эмбеддингов
|
||||
|
||||
**Дата:** 2026-08-30
|
||||
|
||||
**Статус:** исследовательская записка. Production-код и конфигурация не
|
||||
менялись.
|
||||
|
||||
## Вопрос
|
||||
|
||||
Как отделить оценку числа говорящих от порога `FastClustering`, чтобы
|
||||
использовать быстрые CAMPPlus или TitaNet и не запускать сегментацию и
|
||||
извлечение эмбеддингов второй раз?
|
||||
|
||||
## Краткий вывод
|
||||
|
||||
Нужна двухшаговая схема: один раз получить ровно те эмбеддинги, которые
|
||||
подготовил конвейер sherpa-onnx, отдельно оценить по ним число голосовых
|
||||
кластеров `N`, затем передать `N` штатному `FastClustering`. Для первого
|
||||
прототипа рекомендован **счетчик NME**, без полной спектральной кластеризации:
|
||||
ядро алгоритма из статьи реализовать на NumPy, а итоговые метки по-прежнему
|
||||
получить через `FastClustering(num_clusters=N)`.
|
||||
Счетчик eigengap с фиксированным прореживанием нужен как дешевый контрольный
|
||||
вариант. Пока это гипотеза для прототипа, а не выбор production-алгоритма.
|
||||
|
||||
Публичного промежуточного интерфейса для этого в sherpa-onnx 1.13.5 нет.
|
||||
`SpeakerEmbeddingExtractor` и `FastClustering` публичны, но
|
||||
`OfflineSpeakerDiarization.process()` возвращает только готовые интервалы.
|
||||
Матрица эмбеддингов и соответствие строк локальным говорящим остаются внутри
|
||||
реализации. После `set_config()` повторный `process()` снова выполнит модельный
|
||||
инференс. Python-адаптер проекта сам по себе не может дать дешевую повторную
|
||||
кластеризацию; будущему прототипу нужен узкий исследовательский интерфейс на
|
||||
уровне sherpa-onnx.
|
||||
|
||||
## Что подтверждено про sherpa-onnx
|
||||
|
||||
### `FastClustering` уже умеет второй шаг
|
||||
|
||||
В версии 1.13.5 алгоритм нормирует строки, строит все попарные косинусные
|
||||
расстояния, выполняет AHC методом полной связи и затем выбирает один из двух
|
||||
способов разрезать одно и то же дерево:
|
||||
|
||||
- при `num_clusters > 0`: ровно на заданное число кластеров;
|
||||
- иначе: по высоте `threshold`.
|
||||
|
||||
Так работает
|
||||
[`fast-clustering.cc`](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/fast-clustering.cc)
|
||||
и закреплено в комментарии к
|
||||
[`FastClusteringConfig`](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/fast-clustering-config.h):
|
||||
при известном числе кластеров порог игнорируется. Python-модуль публично
|
||||
принимает C-contiguous матрицу `float32` размера `M x D` и возвращает метку для
|
||||
каждой строки
|
||||
([исходник Python-модуля](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/python/csrc/fast-clustering.cc)).
|
||||
Значит, передавать оцененное `N` в существующий кластеризатор не требует новой
|
||||
реализации распределения эмбеддингов по кластерам.
|
||||
|
||||
Порог `FastClustering` не принадлежит модели эмбеддингов: он задает высоту
|
||||
дерева полной связи в шкале косинусного расстояния. Официальный пример CAMPPlus
|
||||
для проверки говорящего лишь вычисляет косинусное сходство и не задает
|
||||
универсальной границы решения
|
||||
([`infer_sv.py`](https://github.com/modelscope/3D-Speaker/blob/065629c313eaf1a01c65c640c46d77e61e9607b4/speakerlab/bin/infer_sv.py)).
|
||||
Официальный рецепт диаризации 3D-Speaker отдельно задает `pval=0.012` для
|
||||
спектральной кластеризации и `mer_cos=0.8` для последующего слияния центров
|
||||
([`infer_diarization.py`](https://github.com/modelscope/3D-Speaker/blob/065629c313eaf1a01c65c640c46d77e61e9607b4/speakerlab/bin/infer_diarization.py)).
|
||||
Эти величины имеют другой смысл и не могут быть перенесены в sherpa как
|
||||
`порог модели`.
|
||||
|
||||
### Публичных деталей недостаточно для повтора всего конвейера
|
||||
|
||||
Публичный `SpeakerEmbeddingExtractor` позволяет создать поток, подать в него
|
||||
аудио и получить один эмбеддинг
|
||||
([Python-модуль](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/python/csrc/speaker-embedding-extractor.cc),
|
||||
[C/C++ API](https://k2-fsa.github.io/sherpa/onnx/c-api/html/speaker_embedding.html)).
|
||||
Интерфейс пригоден для самостоятельно выбранного фрагмента, но не выдает
|
||||
материал, который выбрал конвейер диаризации.
|
||||
|
||||
Внутри `OfflineSpeakerDiarization` после сегментации:
|
||||
|
||||
1. из перекрывающихся окон строятся пары `окно x локальный говорящий`;
|
||||
2. речь с одновременными голосами исключается;
|
||||
3. для каждой пары собирается свой поток и вычисляется эмбеддинг;
|
||||
4. вся матрица передается в `FastClustering`;
|
||||
5. метки строк переводятся обратно в разметку говорящих.
|
||||
|
||||
Последовательность и временная матрица видны в
|
||||
[`OfflineSpeakerDiarizationPyannoteImpl::Process`](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/offline-speaker-diarization-pyannote-impl.h#L87-L204),
|
||||
а сборка строк описана в
|
||||
[`GetChunkSpeakerSampleIndexes` и `ComputeEmbeddings`](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/offline-speaker-diarization-pyannote-impl.h#L376-L528).
|
||||
Оба метода закрыты внутри реализации.
|
||||
|
||||
Python-класс `OfflineSpeakerDiarization` экспортирует только `sample_rate`,
|
||||
`set_config` и `process`; `process` возвращает готовый результат, а не признаки
|
||||
([Python-модуль 1.13.5](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/python/csrc/offline-speaker-diarization.cc#L73-L128)).
|
||||
Внутренний `SetConfig` заменяет только объект кластеризации, тогда как каждый
|
||||
`Process` заново вызывает сегментацию и `ComputeEmbeddings`
|
||||
([реализация](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/sherpa-onnx/csrc/offline-speaker-diarization-pyannote-impl.h#L79-L175)).
|
||||
Публичная C API также документирует только создание, настройку и получение
|
||||
итоговых сегментов
|
||||
([официальная страница](https://k2-fsa.github.io/sherpa/onnx/c-api/html/speaker_diarization.html)).
|
||||
|
||||
Итог: публичный API позволяет **кластеризовать имеющуюся матрицу повторно**.
|
||||
Извлечь или повторно обработать промежуточную матрицу штатного конвейера
|
||||
диаризации через него нельзя.
|
||||
|
||||
## Варианты оценки `N`
|
||||
|
||||
### 1. Отдельный порог для каждой модели эмбеддингов
|
||||
|
||||
Автоматический режим `FastClustering` дешев, не добавляет зависимостей и за один
|
||||
проход оценивает число кластеров и назначает метки. На порог влияют архитектура
|
||||
модели эмбеддингов, длительность и состав выбранных окон, акустика и
|
||||
распределение голосов. Локальная калибровка уже не нашла общего рабочего порога
|
||||
для CAMPPlus, а TitaNet не прошел автоматическую сетку
|
||||
([исходное исследование](2026-08-14-diarization-cpu-cost.md),
|
||||
[повторный CPU-тест](../benchmarks/2026-08-30-lightweight-diarization-cpu.md)).
|
||||
|
||||
Вводить еще один `официальный порог модели` оснований нет: исходные рецепты
|
||||
публикуют параметры конкретного конвейера кластеризации, а не переносимую
|
||||
границу для метода полной связи в sherpa. Вариант остается контрольным, но не
|
||||
решает тикет.
|
||||
|
||||
### 2. Счетчик eigengap
|
||||
|
||||
Официальный CAMPPlus-конвейер 3D-Speaker строит матрицу косинусного сходства.
|
||||
В каждой строке он оставляет фиксированную долю ближайших соседей,
|
||||
симметризует матрицу, затем вычисляет ненормированный лапласиан и несколько
|
||||
наименьших собственных пар.
|
||||
Число говорящих задает позиция максимального разрыва собственных значений
|
||||
(`eigengap`) в заданных пределах, после чего собственные векторы
|
||||
кластеризуются k-means
|
||||
([`SpectralCluster`](https://github.com/modelscope/3D-Speaker/blob/065629c313eaf1a01c65c640c46d77e61e9607b4/speakerlab/process/cluster.py#L21-L106)).
|
||||
|
||||
Для нашей двухшаговой схемы нужны только собственные значения и оценка `N`;
|
||||
k-means можно не выполнять. Одного спектрального разложения достаточно для
|
||||
минимального контрольного варианта, но фиксированный `pval` остается новым
|
||||
порогом. Официальная реализация задает его для своего способа нарезки; пары
|
||||
`окно x локальный говорящий` sherpa устроены иначе. Вариант отделяет `N` от
|
||||
порога AHC, но сохраняет калибруемый параметр.
|
||||
|
||||
Если брать реализацию 3D-Speaker как зависимость, она импортирует NumPy, SciPy,
|
||||
scikit-learn, fastcluster, UMAP и HDBSCAN, причем отсутствие UMAP или HDBSCAN
|
||||
роняет уже импорт модуля. Корневой `requirements.txt` при этом не перечисляет
|
||||
fastcluster, UMAP и HDBSCAN
|
||||
([исходник](https://github.com/modelscope/3D-Speaker/blob/065629c313eaf1a01c65c640c46d77e61e9607b4/speakerlab/process/cluster.py#L3-L18),
|
||||
[requirements](https://github.com/modelscope/3D-Speaker/blob/065629c313eaf1a01c65c640c46d77e61e9607b4/requirements.txt)).
|
||||
Для production это несоразмерно счетчику.
|
||||
|
||||
### 3. NME-SC
|
||||
|
||||
NME-SC создан именно для совместной оценки числа кластеров и параметра
|
||||
прореживания без отдельной выборки для настройки. Для каждого числа соседей
|
||||
`p` алгоритм бинаризует матрицу косинусного сходства, симметризует ее, строит
|
||||
ненормированный лапласиан и считает разрывы собственных значений. Нормированный
|
||||
разрыв равен максимальному разрыву, деленному на наибольшее собственное
|
||||
значение; выбирается `p`, минимизирующий отношение доли соседей `p/M` к
|
||||
нормированному разрыву, а `N` задает позиция максимального разрыва
|
||||
([статья авторов, раздел III](https://arxiv.org/abs/2003.02405)).
|
||||
Авторы ограничивали поиск `p` диапазоном до четверти числа строк, а число
|
||||
говорящих числом восемь. Результаты статьи получены на x-vector и англоязычных
|
||||
корпусах, поэтому перенос на наши эмбеддинги еще предстоит проверить.
|
||||
|
||||
Поддерживаемая реализация NeMo разделяет оценку и назначение меток. `NMESC`
|
||||
принимает готовую матрицу сходства и возвращает `est_num_of_spk` вместе с
|
||||
выбранным `p`; следующий слой отдельно выбирает оцененное или известное число
|
||||
говорящих перед спектральной кластеризацией
|
||||
([`NMESC` и выбор числа кластеров](https://github.com/NVIDIA-NeMo/Speech/blob/d47d7a3c306790637aff39c2d3f9eceb83ca8879/nemo/collections/asr/parts/utils/offline_clustering.py#L909-L1041),
|
||||
[`SpeakerClustering.forward_unit_infer`](https://github.com/NVIDIA-NeMo/Speech/blob/d47d7a3c306790637aff39c2d3f9eceb83ca8879/nemo/collections/asr/parts/utils/offline_clustering.py#L1181-L1259)).
|
||||
Более высокий интерфейс NeMo получает многомасштабные эмбеддинги вместе с
|
||||
временной разметкой и сам строит матрицу сходства
|
||||
([`forward_infer`](https://github.com/NVIDIA-NeMo/Speech/blob/d47d7a3c306790637aff39c2d3f9eceb83ca8879/nemo/collections/asr/parts/utils/offline_clustering.py#L1301-L1403)).
|
||||
|
||||
Для ускорения NME NeMo задает целевой размер матрицы 512 строк, перебирает
|
||||
разреженную сетку из 30 значений `p` и предупреждает, что меньше 20 может
|
||||
ухудшить оценку
|
||||
([класс `NMESC`](https://github.com/NVIDIA-NeMo/Speech/blob/d47d7a3c306790637aff39c2d3f9eceb83ca8879/nemo/collections/asr/parts/utils/offline_clustering.py#L872-L977)).
|
||||
Предел не жесткий: регулярный шаг вычисляется целочисленно, поэтому для
|
||||
некоторых размеров после прореживания остается больше 512 строк. Исходник также
|
||||
предупреждает о риске пропустить говорящего с малой долей речи
|
||||
([`subsampleAffinityMat`](https://github.com/NVIDIA-NeMo/Speech/blob/d47d7a3c306790637aff39c2d3f9eceb83ca8879/nemo/collections/asr/parts/utils/offline_clustering.py#L1043-L1066)).
|
||||
Для коротких записей NeMo добавляет случайные якорные эмбеддинги и повторяет
|
||||
оценку. Такое расширение выходит за пределы минимального ядра статьи
|
||||
([исходник](https://github.com/NVIDIA-NeMo/Speech/blob/d47d7a3c306790637aff39c2d3f9eceb83ca8879/nemo/collections/asr/parts/utils/offline_clustering.py#L536-L595)).
|
||||
|
||||
Полная NME-SC после оценки `N` еще раз раскладывает лапласиан, строит
|
||||
спектральные эмбеддинги и запускает k-means. Для этого тикета такая замена
|
||||
итогового кластеризатора не нужна: локальные тесты уже показывают полезный путь
|
||||
`FastClustering` с известным числом говорящих. Нужна только функция
|
||||
`embeddings -> N`.
|
||||
|
||||
## Рекомендуемая граница ответственности
|
||||
|
||||
Здесь речь не об отдельном счетчике, а о способе соединить оценку `N` со
|
||||
штатным кластеризатором:
|
||||
|
||||
```text
|
||||
segmentation -> embeddings +-> счетчик NME -> N
|
||||
+-> FastClustering(features, num_clusters=N)
|
||||
-> штатная финализация sherpa
|
||||
```
|
||||
|
||||
Плюсы:
|
||||
|
||||
- порог метода полной связи больше не определяет число голосовых кластеров;
|
||||
- CAMPPlus и TitaNet используют один модельный проход;
|
||||
- итоговое назначение меток остается в уже принятом `FastClustering`;
|
||||
- на одной матрице можно независимо сравнить несколько счетчиков.
|
||||
|
||||
Минус один, но архитектурный: без нового промежуточного интерфейса текущий
|
||||
`process()` не позволяет вставить счетчик между вычислением матрицы и
|
||||
кластеризацией.
|
||||
|
||||
## Зависимости, лицензии и цена
|
||||
|
||||
Штатная установка NeMo требует среди прочего NumPy, scikit-learn и PyTorch
|
||||
([`pyproject.toml`](https://github.com/NVIDIA-NeMo/Speech/blob/d47d7a3c306790637aff39c2d3f9eceb83ca8879/pyproject.toml)).
|
||||
|
||||
| Вариант | Зависимости прототипа | Работа после эмбеддингов | Ограничение |
|
||||
|---|---|---|---|
|
||||
| Порог `FastClustering` | новых нет | существующие попарные расстояния и AHC | порог зависит от модели и материала |
|
||||
| Счетчик eigengap | NumPy; официальный модуль 3D-Speaker целиком не импортируется | матрица `M x M`, одно собственное разложение | фиксированное прореживание остается параметром |
|
||||
| Полная NME-SC NeMo | `nemo-toolkit`, PyTorch, scikit-learn и другие зависимости NeMo | несколько разложений, затем спектральные эмбеддинги и k-means | меняет итоговый кластеризатор и слишком тяжела для счетчика |
|
||||
| Счетчик NME + `FastClustering` | NumPy | несколько разложений, затем штатный AHC по известному `N` | нужен внутренний интерфейс sherpa и проверка переноса |
|
||||
|
||||
`FastClustering` уже хранит `M(M-1)/2` расстояний и строит AHC. Счетчик eigengap
|
||||
добавляет плотную матрицу сходства и одно собственное разложение. NME
|
||||
повторяет анализ для набора `p`. Полная спектральная кластеризация также хранит
|
||||
собственные векторы и запускает k-means, поэтому для одной оценки `N` избыточна.
|
||||
В проектных замерах основной расход пока приходится на извлечение эмбеддингов,
|
||||
а не на кластеризацию
|
||||
([профиль стоимости](2026-08-14-diarization-cpu-cost.md),
|
||||
[CPU-тест](../benchmarks/2026-08-30-lightweight-diarization-cpu.md)); это не
|
||||
заменяет отдельного замера NME на фактическом числе строк.
|
||||
|
||||
NumPy сейчас есть только в [lock-файле проекта](../../uv.lock), а не в прямых
|
||||
зависимостях `local-transcriber`. Для стенда версию 2.4.3 нужно закрепить явно.
|
||||
Если счетчик дойдет до поставляемого кода, NumPy потребуется объявить прямой
|
||||
зависимостью времени выполнения.
|
||||
|
||||
sherpa-onnx, 3D-Speaker и NeMo Speech распространяют код под Apache-2.0
|
||||
([sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx/blob/v1.13.5/LICENSE),
|
||||
[3D-Speaker](https://github.com/modelscope/3D-Speaker/blob/065629c313eaf1a01c65c640c46d77e61e9607b4/LICENSE),
|
||||
[NeMo Speech](https://github.com/NVIDIA-NeMo/Speech/blob/d47d7a3c306790637aff39c2d3f9eceb83ca8879/LICENSE)).
|
||||
Авторская реализация NME-SC опубликована под MIT и направляет пользователей к
|
||||
поддерживаемой версии NeMo
|
||||
([репозиторий авторов](https://github.com/tango4j/Auto-Tuning-Spectral-Clustering/tree/ae97f9be9c33b554205c7192c5137ce8f456c1b7)).
|
||||
Файл кластеризации NeMo дополнительно сохраняет BSD-уведомление scikit-learn,
|
||||
NumPy 2.4.3 использует BSD-3-Clause
|
||||
([лицензия](https://github.com/numpy/numpy/blob/v2.4.3/LICENSE.txt)).
|
||||
Лицензионного запрета на прототип нет. Прямое заимствование кода потребует
|
||||
сохранить его уведомления; реализация по формулам статьи должна явно назвать
|
||||
происхождение алгоритма.
|
||||
|
||||
## Ограничения вывода
|
||||
|
||||
- NME-SC проверялась авторами на другой нарезке и эмбеддингах x-vector. Строки
|
||||
sherpa представляют локальных говорящих в перекрывающихся окнах Pyannote после
|
||||
исключения пересечений речи;
|
||||
переносимость нельзя считать доказанной.
|
||||
- Правильное `N` не гарантирует качественную разметку говорящих. Для TitaNet в
|
||||
режиме известного числа кластеров уже наблюдался малый кластер, поэтому нужны
|
||||
прежние прокси-метрики и слуховая проверка.
|
||||
- У sherpa есть отдельный путь для единственного окна, который обходится без
|
||||
общей матрицы эмбеддингов. Стенд должен сохранить этот частный случай, а также
|
||||
текущую обработку пустого результата и нечисловых эмбеддингов.
|
||||
- Python-модуль `FastClustering` нормирует переданную матрицу на месте;
|
||||
сравниваемым вариантам нужны отдельные C-contiguous копии.
|
||||
- Оцененное `N` нужно проверять до вызова `FastClustering`:
|
||||
`1 <= N <= min(8, M)`, где `M` обозначает число валидных строк. Некорректное
|
||||
значение считается ошибкой ячейки, а не молча ограничивается.
|
||||
- Прореживание строк может потерять голосовой кластер с малой долей речи.
|
||||
Поэтому полный набор строк задает основной результат, а сокращенная матрица
|
||||
годится только для отдельной проверки чувствительности.
|
||||
- `numpy.linalg.eigvalsh` использует BLAS/LAPACK. Без закрепленного числа
|
||||
потоков его процессорное и фактическое время нельзя сравнивать между
|
||||
машинами.
|
||||
|
||||
## Точная граница будущего прототипа
|
||||
|
||||
Production-код, CLI, модели по умолчанию и ADR не менять. Сделать отдельный
|
||||
исследовательский стенд на sherpa-onnx 1.13.5 и сохранить точный хеш коммита
|
||||
экспериментальной сборки рядом с результатом.
|
||||
|
||||
### Внутренний интерфейс sherpa
|
||||
|
||||
В экспериментальной ветке sherpa разделить внутренний `Process` в одном месте:
|
||||
после удаления нечисловых строк и синхронной фильтрации пар
|
||||
`окно x локальный говорящий`, до `clustering_->Cluster`.
|
||||
|
||||
- `Prepare(audio)` либо возвращает готовый результат существующего частного
|
||||
пути, либо владеющий объект `PreparedDiarization`. Объект хранит матрицу
|
||||
`float32`, выровненные пары, локальные покадровые метки, число говорящих по
|
||||
кадрам и длину аудио, необходимые текущей финализации.
|
||||
- Экспериментальный Python-модуль отдает C-contiguous копию матрицы и
|
||||
непрозрачный дескриптор подготовленного состояния. Дескриптор живет только в
|
||||
памяти процесса.
|
||||
- `Finalize(handle, row_labels)` проверяет число меток и выполняет существующие
|
||||
`ConvertChunkSpeakerToCluster`, `ReLabel`, `FinalizeLabels` и `ComputeResult`.
|
||||
Так все счетчики используют одну подготовку и одну штатную финализацию.
|
||||
- Пустая речь, одно окно, отсутствие валидных эмбеддингов и одна валидная строка
|
||||
сохраняют нынешнее поведение. Аудио, текст, матрица и дескриптор не
|
||||
сериализуются.
|
||||
|
||||
### Закрепленные входы и счетчики
|
||||
|
||||
Использовать только модель Pyannote Segmentation 3.0 FP32 `model.onnx`
|
||||
(`220ad67ca923bef2fa91f2390c786097bf305bceb5e261d4af67b38e938e1079`) и две
|
||||
модели эмбеддингов из предыдущих измерений:
|
||||
|
||||
- CAMPPlus zh/en
|
||||
(`aa3cfc16963a10586a9393f5035d6d6b57e98d358b347f80c2a30bf4f00ceba2`);
|
||||
- TitaNet small
|
||||
(`ad4a1802485d8b34c722d2a9d04249662f2ece5d28a7a039063ca22f515a789e`).
|
||||
|
||||
Полные SHA-256 и происхождение уже записаны в
|
||||
[калибровке CAMPPlus](../benchmarks/2026-08-14-diarization-calibration.md#модели)
|
||||
и [исследовании TitaNet](2026-08-29-lightweight-diarization-models.md#проверенные-sha-256).
|
||||
INT8-сегментация в эту матрицу не входит: она меняет локальные покадровые метки
|
||||
и уже отклонена по результатам отдельного CPU-теста.
|
||||
|
||||
На каждой исходной матрице сравнить:
|
||||
|
||||
1. диагностический контроль `FastClustering`: для CAMPPlus пороги `1,05` и
|
||||
`1,10`; для TitaNet использовать наименее плохую, но отклоненную точку
|
||||
`1,10`;
|
||||
2. счетчик eigengap по схеме 3D-Speaker с `pval=0.012`, `min_pnum=6`, без
|
||||
спектральных эмбеддингов и k-means;
|
||||
3. счетчик NME по контракту ниже.
|
||||
|
||||
Контрольные пороги не становятся допустимыми настройками. Они только
|
||||
воспроизводят уже наблюдавшийся переход между лишними кластерами и склейкой.
|
||||
|
||||
### Контракт счетчика NME
|
||||
|
||||
Контракт следует формулам статьи и варианту Sparse-Search-20 из
|
||||
[кода авторов](https://github.com/tango4j/Auto-Tuning-Spectral-Clustering/blob/ae97f9be9c33b554205c7192c5137ce8f456c1b7/spectral_opt.py#L463-L495):
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| Вход | все `M` валидных строк; строки конечны и имеют ненулевую норму |
|
||||
| Матрица сходства | нормировать строки на копии, затем `A = E @ E.T` |
|
||||
| Диапазон `p` | `1..floor(M/4)`; при `M < 6` ячейка счетчика помечается пропуском, а диагностический `FastClustering` выполняется независимо |
|
||||
| Разреженный поиск | все значения, если верхняя граница не больше 20; иначе 20 целых точек `linspace` с обоими концами и удалением дублей |
|
||||
| Бинаризация | сохранить top-`p` в каждой строке, включая диагональ; равные значения разрешать по меньшему индексу столбца |
|
||||
| Граф | `(A_p + A_p.T) / 2`, затем обнулить диагональ |
|
||||
| Лапласиан | ненормированный `L = D - A`, `D_ii = sum_j(abs(A_ij))` |
|
||||
| Собственные значения | `numpy.linalg.eigvalsh`, по возрастанию; `epsilon = 1e-10` |
|
||||
| Оценка `N` | `N = 1 + argmax(gaps[:min(8, M - 1)])`; при равенстве брать наименьший индекс |
|
||||
| Выбор `p` | `argmin` по `(p/M) / (max_gap / (lambda_max + epsilon) + epsilon)`; при равенстве брать наименьшее `p` |
|
||||
| Связность | если выбранный граф несвязен, взять первое большее `p` из той же сетки, которое делает его связным; отсутствие такого `p` считать ошибкой ячейки |
|
||||
|
||||
После коррекции связности использовать `N`, рассчитанное для итогового `p`.
|
||||
Статья и код авторов не задают стабильное разрешение равных значений или
|
||||
поведение при слишком малой матрице; таблица фиксирует эти два локальных
|
||||
правила воспроизводимости. Основной расчет идет по всем строкам, как в
|
||||
авторской реализации.
|
||||
|
||||
До прогона на рабочих записях закрепить две синтетические фикстуры с
|
||||
блочными матрицами сходства и ожидаемыми `N=2` и `N=3`. Они проверяют
|
||||
сортировку и разрешение равных значений в `argmax` и `argmin`.
|
||||
Якорные эмбеддинги, Torch, спектральные эмбеддинги и k-means в этот счетчик не
|
||||
входят.
|
||||
|
||||
Основной результат считать на всех строках. Если `M > 512`, дополнительно
|
||||
повторить NME с регулярным прореживанием NeMo, записать `M` до и после и
|
||||
сравнить `N`. Сокращенная матрица не заменяет полный результат даже при
|
||||
совпадении.
|
||||
|
||||
### Проверка и граница решения
|
||||
|
||||
Для каждой модели один раз подготовить матрицу на тех же трех пятиминутных
|
||||
[контрольных записях](../benchmarks/2026-08-30-lightweight-diarization-cpu.md)
|
||||
и на ее независимых копиях выполнить все счетчики. Критерии качества:
|
||||
|
||||
- оцененное число голосовых кластеров точно равно `3 / 2 / 2` на трех
|
||||
контрольных записях для каждой модели отдельно; содержательные кластеры не
|
||||
компенсируют ошибку счета;
|
||||
- метки строк и финальный результат для оцененного `N` совпадают с отдельным
|
||||
вызовом известного `N` на той же матрице;
|
||||
- повторный счет на той же матрице дает те же `p`, `N` и метки;
|
||||
- сохранены прежняя метрика `mapped speaker purity`, число слов без говорящего и
|
||||
диагностика малых кластеров; прогон CAMPPlus и TitaNet заканчивается слуховой
|
||||
проверкой.
|
||||
|
||||
Для каждого счетчика отдельно записать фактическое и процессорное время,
|
||||
`peak RSS`, форму матрицы, конфигурацию NumPy/BLAS и эффективное число потоков.
|
||||
Процесс запускать с одним потоком BLAS/LAPACK; способ ограничения и вывод
|
||||
`numpy.show_config()` входят в результат.
|
||||
|
||||
Числовой гейт времени и памяти эта записка не задает: его определит
|
||||
[Зафиксировать аппаратную матрицу и бюджет памяти](https://git.dementev.space/ddmitry/local-transcriber/issues/35),
|
||||
а общую матрицу соберет
|
||||
[Собрать минимальную матрицу следующего CPU-эксперимента](https://git.dementev.space/ddmitry/local-transcriber/issues/34).
|
||||
До этих решений время и память служат измерениями, но не основанием объявить
|
||||
счетчик принятым.
|
||||
|
||||
Если счетчики eigengap и NME проходят критерии качества, в следующую матрицу
|
||||
идет более дешевый. Если проходит только NME, отдельно решается перенос чистой
|
||||
NumPy-реализации в production. Если оба ошибаются, CAMPPlus и TitaNet остаются
|
||||
кандидатами только для явно заданного числа говорящих.
|
||||
|
||||
Прототип проверяет переносимость оценки `N` на внутренние строки sherpa и цену
|
||||
счетчика. Он не выбирает новую модель по умолчанию, не заменяет
|
||||
`FastClustering` и не расширяет продуктовый контракт.
|
||||
Reference in new issue
Block a user