docs(diarization): подтверждено смешение в ASR-сегментах
- Зачем: - требовалось проверить долю смешанных ASR-сегментов на трёх записях, включая разговоры на двоих. - Что: - добавлен отчёт с долями сегментов и времени для трёх рабочих созвонов. - обвязка замеров переведена на откалиброванный порог 0,89. - исследовательский скрипт приведён к формату ruff. - Проверка: - выполнены три полных прогона bench_conflict.py с WeSpeaker и порогом 0,89. - uv run ruff check и ruff format --check прошли успешно.
This commit is contained in:
@@ -41,17 +41,18 @@ curl -sSL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/speaker-rec
|
|||||||
export PYTHONIOENCODING=utf-8
|
export PYTHONIOENCODING=utf-8
|
||||||
|
|
||||||
uv run python .scratch/diarization/bench_asr.py "<путь к записи>"
|
uv run python .scratch/diarization/bench_asr.py "<путь к записи>"
|
||||||
uv run --with sherpa-onnx python .scratch/diarization/bench_diar.py "<путь>" 8 0.9
|
uv run --with sherpa-onnx python .scratch/diarization/bench_diar.py "<путь>" 8 0.89
|
||||||
uv run --with sherpa-onnx python .scratch/diarization/bench_sweep.py "<путь>"
|
uv run --with sherpa-onnx python .scratch/diarization/bench_sweep.py "<путь>"
|
||||||
uv run --with sherpa-onnx python .scratch/diarization/bench_conflict.py "<путь>"
|
uv run --with sherpa-onnx python .scratch/diarization/bench_conflict.py "<путь>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Что стоит знать до запуска
|
## Что стоит знать до запуска
|
||||||
|
|
||||||
- **Порог кластеризации не откалиброван.** По умолчанию стоит 0,9 — значение из
|
- **Порог кластеризации откалиброван.** По умолчанию стоит 0,89 — единственное
|
||||||
разведки, подобранное на одной записи и на ней же проверенное. На пороге 0,5
|
проверенное значение, которое без знания числа участников дало правильные
|
||||||
из примеров sherpa-onnx получалось 29 говорящих вместо трёх. Калибровка — это
|
3 / 2 / 2 кластера на трёх калибровочных фрагментах. Решение и ограничения
|
||||||
тикет #10, до его закрытия любое значение считается временным.
|
описаны в
|
||||||
|
[отчёте о калибровке](../../docs/benchmarks/2026-08-14-diarization-calibration.md).
|
||||||
- **Свип дорогой.** Каждая конфигурация — полный прогон сегментации и
|
- **Свип дорогой.** Каждая конфигурация — полный прогон сегментации и
|
||||||
эмбеддингов, около 2,5 минут на 26-минутную запись, и время от настроек
|
эмбеддингов, около 2,5 минут на 26-минутную запись, и время от настроек
|
||||||
кластеризации практически не зависит. Свип вести на коротком фрагменте.
|
кластеризации практически не зависит. Свип вести на коротком фрагменте.
|
||||||
|
|||||||
@@ -74,7 +74,13 @@ def main(audio_path: str, threshold: float, threads: int) -> None:
|
|||||||
continue
|
continue
|
||||||
major = max(per_speaker, key=lambda k: per_speaker[k])
|
major = max(per_speaker, key=lambda k: per_speaker[k])
|
||||||
rows.append(
|
rows.append(
|
||||||
(seg, major, per_speaker[major] / total, total - per_speaker[major], dict(per_speaker))
|
(
|
||||||
|
seg,
|
||||||
|
major,
|
||||||
|
per_speaker[major] / total,
|
||||||
|
total - per_speaker[major],
|
||||||
|
dict(per_speaker),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
n = len(rows)
|
n = len(rows)
|
||||||
@@ -95,7 +101,9 @@ def main(audio_path: str, threshold: float, threads: int) -> None:
|
|||||||
(f"с чужой репликой (>={INTERJECTION_S:.0f} с)", lost),
|
(f"с чужой репликой (>={INTERJECTION_S:.0f} с)", lost),
|
||||||
("без говорящего вообще", unattributed),
|
("без говорящего вообще", unattributed),
|
||||||
):
|
):
|
||||||
print(f" {label:<34} {len(group):4d} {len(group) / n * 100:5.1f}% {minutes(group):5.1f} мин")
|
print(
|
||||||
|
f" {label:<34} {len(group):4d} {len(group) / n * 100:5.1f}% {minutes(group):5.1f} мин"
|
||||||
|
)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
for level in PURITY_LEVELS:
|
for level in PURITY_LEVELS:
|
||||||
@@ -107,9 +115,12 @@ def main(audio_path: str, threshold: float, threads: int) -> None:
|
|||||||
|
|
||||||
print("\n" + "=" * 64)
|
print("\n" + "=" * 64)
|
||||||
print("ХУДШИЕ 12 СЕГМЕНТОВ (больше всего чужой речи внутри):")
|
print("ХУДШИЕ 12 СЕГМЕНТОВ (больше всего чужой речи внутри):")
|
||||||
for seg, major, purity, others, per_speaker in sorted(attributed, key=lambda r: -r[3])[:12]:
|
for seg, major, purity, others, per_speaker in sorted(
|
||||||
|
attributed, key=lambda r: -r[3]
|
||||||
|
)[:12]:
|
||||||
share = ", ".join(
|
share = ", ".join(
|
||||||
f"spk{k}={v:.1f}с" for k, v in sorted(per_speaker.items(), key=lambda x: -x[1])
|
f"spk{k}={v:.1f}с"
|
||||||
|
for k, v in sorted(per_speaker.items(), key=lambda x: -x[1])
|
||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
f"\n [{seg.start:7.1f}-{seg.end:7.1f}] ({seg.end - seg.start:4.1f} с) "
|
f"\n [{seg.start:7.1f}-{seg.end:7.1f}] ({seg.end - seg.start:4.1f} с) "
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ EMBEDDING = MODELS / "wespeaker_en_voxceleb_resnet34_LM.onnx"
|
|||||||
|
|
||||||
SAMPLE_RATE = 16_000
|
SAMPLE_RATE = 16_000
|
||||||
|
|
||||||
# Настройки разведки 2026-08-12. Порог 0.9 дал верное число говорящих на
|
# Конфигурация, выбранная калибровкой 2026-08-14 на трёх записях.
|
||||||
# контрольной записи; на 0.5 из примеров sherpa-onnx получалось 29 вместо трёх.
|
# На 0.5 из примеров sherpa-onnx получалось 29 говорящих вместо трёх.
|
||||||
DISCOVERY_THRESHOLD = 0.9
|
DISCOVERY_THRESHOLD = 0.89
|
||||||
DEFAULT_THREADS = 8
|
DEFAULT_THREADS = 8
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# Смешение говорящих внутри ASR-сегментов
|
||||||
|
|
||||||
|
**Дата:** 2026-08-14
|
||||||
|
|
||||||
|
**Статус:** проверка на трёх русскоязычных рабочих созвонах. Не оценка DER и
|
||||||
|
не решение о единице привязки спикера к тексту.
|
||||||
|
|
||||||
|
## Вопрос
|
||||||
|
|
||||||
|
Воспроизводится ли смешение говорящих внутри ASR-сегментов на других записях,
|
||||||
|
или результат разведки — особенность одной встречи на троих?
|
||||||
|
|
||||||
|
В [разведочном замере](2026-08-12-diarization-feasibility.md) 27% сегментов
|
||||||
|
контрольной записи содержали не меньше секунды чужой речи. На них приходилось
|
||||||
|
больше половины времени ASR-сегментов. Проверка повторена на тех же трёх
|
||||||
|
записях, на которых калибровалась диаризация, включая два разговора на двоих.
|
||||||
|
|
||||||
|
## Метод
|
||||||
|
|
||||||
|
ASR выполнялся через модель по умолчанию ONNX-пути
|
||||||
|
`gigaam-v3-e2e-rnnt` INT8 с языком `ru`. Диаризация выполнялась через
|
||||||
|
`sherpa-onnx` 1.13.5 с выбранной в
|
||||||
|
[калибровке](2026-08-14-diarization-calibration.md) конфигурацией:
|
||||||
|
|
||||||
|
- сегментация `sherpa-onnx-pyannote-segmentation-3-0`;
|
||||||
|
- эмбеддинги `wespeaker_en_voxceleb_resnet34_LM.onnx`;
|
||||||
|
- `FastClusteringConfig.threshold=0.89`;
|
||||||
|
- автоматическое определение числа кластеров (`num_clusters=-1`).
|
||||||
|
|
||||||
|
Для каждого ASR-сегмента считалось перекрытие с интервалами каждого кластера.
|
||||||
|
Кластер с максимальным перекрытием считался мажоритарным, а сумма перекрытий
|
||||||
|
остальных кластеров — чужой речью. Сегменты разделены на четыре категории:
|
||||||
|
|
||||||
|
- **чистый** — перекрытие только с одним кластером;
|
||||||
|
- **с поддакиванием** — меньше 1 секунды чужой речи;
|
||||||
|
- **с чужой репликой** — не меньше 1 секунды чужой речи;
|
||||||
|
- **без спикера** — нет перекрытия с интервалами диаризации.
|
||||||
|
|
||||||
|
Время категории — сумма длительностей попавших в неё ASR-сегментов. Это не
|
||||||
|
сумма времени речи: интервалы разных кластеров могут перекрываться при
|
||||||
|
наложенной речи. Метрика отвечает на узкий вопрос, насколько огрубляет текст
|
||||||
|
одна метка спикера на весь ASR-сегмент. Она не измеряет точность диаризации.
|
||||||
|
|
||||||
|
## Результаты
|
||||||
|
|
||||||
|
| Запись | Участников | ASR-сегментов | Чистые | Поддакивание <1 с | Чужая реплика ≥1 с | Без спикера |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
| Data Test | 3 | 274 | 164 (59,9%) | 30 (10,9%) | **74 (27,0%)** | 6 (2,2%) |
|
||||||
|
| T2 BDMA | 2 | 223 | 167 (74,9%) | 34 (15,2%) | **14 (6,3%)** | 8 (3,6%) |
|
||||||
|
| Yantar | 2 | 339 | 275 (81,1%) | 20 (5,9%) | **24 (7,1%)** | 20 (5,9%) |
|
||||||
|
|
||||||
|
| Запись | Время всех ASR-сегментов | Время сегментов с чужой репликой ≥1 с | Доля времени |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| Data Test | 21,89 мин | **11,20 мин** | **51,2%** |
|
||||||
|
| T2 BDMA | 12,56 мин | **1,53 мин** | **12,2%** |
|
||||||
|
| Yantar | 15,93 мин | **2,67 мин** | **16,8%** |
|
||||||
|
|
||||||
|
На двух разговорах на двоих вместе чужая реплика не меньше секунды встречается
|
||||||
|
в 38 из 562 сегментов (6,8%) и затрагивает 4,20 из 28,49 минуты (14,7%). По
|
||||||
|
сравнению со встречей на троих это в четыре раза меньше по доле сегментов и
|
||||||
|
примерно в 3,5 раза меньше по доле времени.
|
||||||
|
|
||||||
|
## Вывод
|
||||||
|
|
||||||
|
Смешение говорящих внутри ASR-сегмента — общее свойство проверенного материала,
|
||||||
|
а не аномалия одной записи: оно воспроизвелось на обоих разговорах на двоих.
|
||||||
|
Однако тяжесть сильно зависит от характера разговора. Значение 27% сегментов и
|
||||||
|
51% времени не переносится на двухсторонние созвоны: там получено 6–7%
|
||||||
|
сегментов и 12–17% времени.
|
||||||
|
|
||||||
|
Мажоритарная метка на весь ASR-сегмент поэтому остаётся заметным огрублением
|
||||||
|
даже на разговорах на двоих, а на плотной встрече втроём теряет реплики в
|
||||||
|
массовом масштабе. Эти данные не выбирают единицу привязки сами по себе, но
|
||||||
|
исключают предположение, что сегментная привязка безопасна для всех обычных
|
||||||
|
созвонов без пословной привязки или явной пометки качества.
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
- Все три записи — русскоязычные рабочие созвоны одного пользователя; другие
|
||||||
|
микрофоны, шумы и стили разговора не представлены.
|
||||||
|
- Диаризация служит опорной разметкой и сама содержит ошибки. На контрольной
|
||||||
|
записи есть ложный остаточный кластер, редкие пропуски и неполная разметка
|
||||||
|
перекрывающейся речи.
|
||||||
|
- На двухсторонних записях один голос заметно доминирует по времени. Баланс
|
||||||
|
реплик может влиять на долю смешанных сегментов.
|
||||||
|
- Порог 1 секунда разделяет короткие вставки и потенциально потерянные реплики,
|
||||||
|
но не доказывает смысловую важность каждого фрагмента.
|
||||||
|
|
||||||
|
## Воспроизводимость
|
||||||
|
|
||||||
|
Расчёт выполняется скриптом
|
||||||
|
[`bench_conflict.py`](../../.scratch/diarization/bench_conflict.py):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:PYTHONIOENCODING = "utf-8"
|
||||||
|
uv run --with sherpa-onnx python .scratch/diarization/bench_conflict.py `
|
||||||
|
"<путь к записи>" 0.89 8
|
||||||
|
```
|
||||||
|
|
||||||
|
Медиа и сырые JSON не добавлены в репозиторий: они содержат локальные пути и
|
||||||
|
относятся к рабочим созвонам. SHA-256 всех трёх исходных файлов зафиксированы в
|
||||||
|
[отчёте о калибровке](2026-08-14-diarization-calibration.md).
|
||||||
Reference in New Issue
Block a user