Files
local-transcriber/.scratch/diarization/bench_sweep.py
T
Dmitriy DementievandClaude Opus 5 1cb6a36c92 chore(diarization): добавлена обвязка замеров в .scratch
- Зачем:
  - тикеты карты #10-#13 опираются на измерительную обвязку, которая до сих пор
    жила во временном каталоге сессии и исчезла бы вместе с ним.
- Что:
  - перенесены четыре скрипта разведки: ASR, один прогон диаризации, свип порога
    кластеризации и подсчёт чистоты ASR-сегментов.
  - общая часть вынесена в common.py: пути от корня репозитория вместо
    захардкоженных, конфигурация диаризатора, проверка наличия моделей.
  - починен замер пиковой памяти: нужен экспорт K32GetProcessMemoryInfo из
    kernel32 и явные argtypes, иначе дескриптор процесса уезжает 32-битным.
  - модели и выход замеров исключены из истории локальным .gitignore.
- Проверка:
  - export PYTHONIOENCODING=utf-8
  - uv run --with sherpa-onnx python .scratch/diarization/bench_diar.py "<запись>" 8 0.9

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:35:20 +03:00

63 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Свип порога кластеризации и явного числа говорящих.
uv run --with sherpa-onnx python .scratch/diarization/bench_sweep.py <файл> [потоки]
Каждая конфигурация — полный прогон сегментации и эмбеддингов (около 2,5 минут
на 26-минутную запись), поэтому свип имеет смысл вести на коротком фрагменте, а
полные записи оставить для проверки финального кандидата.
"""
from __future__ import annotations
import sys
import time
from common import DEFAULT_THREADS, SAMPLE_RATE, load_audio, make_diarizer
# подпись, num_clusters, threshold
CONFIGS = [
("авто, порог 0.5", -1, 0.5),
("авто, порог 0.7", -1, 0.7),
("авто, порог 0.9", -1, 0.9),
("явно k=5", 5, 0.5),
]
# говорящий с речью короче порога считается остаточным кластером, не участником
MIN_SPEAKER_S = 30.0
def main(audio_path: str, threads: int) -> None:
samples = load_audio(audio_path)
duration = len(samples) / SAMPLE_RATE
print(f"файл: {audio_path}")
print(f"длительность: {duration / 60:.1f} мин, потоков: {threads}\n")
for label, num_clusters, threshold in CONFIGS:
diarizer = make_diarizer(
threshold=threshold, num_clusters=num_clusters, threads=threads
)
t0 = time.perf_counter()
segments = diarizer.process(samples).sort_by_start_time()
elapsed = time.perf_counter() - t0
totals: dict[int, float] = {}
for seg in segments:
totals[seg.speaker] = totals.get(seg.speaker, 0.0) + (seg.end - seg.start)
real = [spk for spk, t in totals.items() if t >= MIN_SPEAKER_S]
top = sorted(totals.values(), reverse=True)[:8]
print(f"--- {label}")
print(
f" {elapsed:.0f} с ({duration / elapsed:.1f}x RTF), "
f"говорящих: {len(totals)}, из них >= {MIN_SPEAKER_S:.0f} с речи: {len(real)}, "
f"интервалов: {len(segments)}"
)
print(" топ по времени (мин): " + ", ".join(f"{t / 60:.1f}" for t in top))
print()
if __name__ == "__main__":
if len(sys.argv) < 2:
raise SystemExit(__doc__)
main(sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_THREADS)