Files
DenozordecandClaude Opus 5.5 e3af778068 Студии из кодек-строк: codec_line_rows, доразметка, аудит дорожек
tools/audit_tracks.py и reports/tracks_audit.md — аудит всех дорожек
техданных по data/raw с планом. Пункт 1 плана: harvest_core.codec_line_rows
добавляет студии из строк «Аудио 2: AC3 … (MVO, LostFilm)», «Аудио Rus: … |
TVShows» (studio_source=codec), которых нет среди строк релиза; тот же шаг
в tools/relabel_studios.py (повторный запуск ничего не добавляет).
labels.csv: +6 428 строк в 2 862 релизах (kinozal 3 444, nnm 1 609,
rutracker 1 375), со студией 29 331 -> 35 759. tests/e2e_segmenter.py:
модель через E2E_MODEL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:30:52 +07:00

83 lines
3.9 KiB
Python
Raw Permalink 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.
"""Сравнение сегментации: правила vs обученный сегментатор (predict_release).
Метрика — пары (тип, автор) на уровне релиза против меток датасета, только по
страницам, которых сегментатор не видел при обучении (is_test_release).
Текст kinozal — из data/raw/kinozal/*.json (как у сервиса), см. segmenter.page_text.
На трекер берётся не больше E2E_MAX страниц (по умолчанию 400, детерминированно);
train-страницы считаются только с флагом --train-pages. Основная модель — E2E_MODEL
(по умолчанию models/best).
Запуск: .venv/Scripts/python tests/e2e_segmenter.py [каталог_сегментатора] [--train-pages]
"""
from __future__ import annotations
import hashlib
import json
import os
import sys
from collections import defaultdict
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from src.predict import DubPredictor # noqa: E402
from src.release import predict_release # noqa: E402
from src.segmenter import is_test_release, page_text # noqa: E402
ROOT = Path(__file__).resolve().parents[1]
MAX_PER_SOURCE = int(os.environ.get("E2E_MAX", "400"))
MODEL_DIR = os.environ.get("E2E_MODEL", "models/best")
def run(predictor, rows, mode: str, test_only: bool) -> dict:
by_rel = defaultdict(list)
for r in rows:
by_rel[r["release_id"]].append(r)
per_src = defaultdict(list)
pages = [p for p in (ROOT / "data" / "pages").glob("*.txt")
if is_test_release(p.stem) == test_only and p.stem in by_rel]
pages.sort(key=lambda p: hashlib.md5(p.stem.encode()).hexdigest())
for page in pages:
rid = page.stem
src = by_rel[rid][0]["source"]
if len(per_src[src]) >= MAX_PER_SOURCE:
continue
gold = {(r["translation_type"], r["studio"] or None) for r in by_rel[rid]}
# как aggregate: «тип без студии» не отдельная дорожка, если у типа есть студия
# (kinozal: «Перевод: многоголосый» + студии из заголовка)
named = {t for t, s in gold if s}
gold = {(t, s) for t, s in gold if s or t not in named}
res = predict_release(predictor, text=page_text(page), segmenter=mode)
pred = {(t["translation_type"], t["studio"]) for t in res["tracks"]}
tp = len(gold & pred)
p = tp / len(pred) if pred else 0.0
r = tp / len(gold) if gold else 0.0
per_src[src].append((p, r))
out = {}
for s, v in per_src.items():
out[s] = (len(v), sum(x[0] for x in v) / len(v), sum(x[1] for x in v) / len(v))
return out
def main() -> None:
rows = json.loads((ROOT / "data" / "processed" / "dataset.json").read_text(encoding="utf-8"))
args = [a for a in sys.argv[1:] if not a.startswith("--")]
seg_dir = args[0] if args else "models/segmenter"
predictor = DubPredictor(MODEL_DIR, segmenter_dir=seg_dir)
print(f"модель: {MODEL_DIR}; сегментатор: {seg_dir}")
if predictor.segmenter is None:
sys.exit("нет models/segmenter: python -m src.segmenter --train")
blocks = [(True, "тестовые страницы (не виделись сегментатору)")]
if "--train-pages" in sys.argv:
blocks.append((False, "train-страницы (для справки, оптимистично)"))
for test_only, name in blocks:
print(f"== {name} ==")
for mode in ("rules", "model"):
res = run(predictor, rows, mode, test_only)
print(f" {mode:5s}: " + "; ".join(
f"{s} n={n} P={p:.3f} R={r:.3f}" for s, (n, p, r) in res.items()))
if __name__ == "__main__":
main()