Сегментатор: текст kinozal из raw JSON, кодек-строки не как негатив; e2e по 400 страниц
Обучение: * page_text/kinozal_text: текст kinozal = h1 + «Техданные» + «Релиз» из data/raw/kinozal/*.json (в pages/kz-*.txt нет заголовка и «Техданных», откуда половина меток kinozal; сервис видит обе вкладки). * label_page: позитив — raw или audio_raw; неразмеченная кодек-строка русской дорожки («Аудио 3: … Русский (MVO, X)», «Аудио Rus: … | X») получает -1 и в обучение не идёт: как негатив она учила отбрасывать кодек-строки. Сводка «Аудио: Русский, английский (…)» остаётся негативом. Инференс (release): * model_segments -> (основные, кодек); выбранные моделью кодек-строки идут второстепенным источником вместе с правилом codec_segments; иноязычные («Казахский (Dub, …)», «English Dubbing», «Original») отбрасываются. * merge_extra_tracks(adopt_type): кодек-дорожка с типом «неизвестно» получает тип единственной дорожки страницы без студии. e2e_segmenter: тот же текст kinozal, не больше E2E_MAX (400) страниц на трекер, train-страницы только с --train-pages (меню парсит только тест). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
099d33bdaa
commit
6470f882f1
5 files changed
+191
-47
No files matched your search
+22
-10
@@ -2,24 +2,29 @@
|
||||
|
||||
Метрика — пары (тип, автор) на уровне релиза против меток датасета, только по
|
||||
страницам, которых сегментатор не видел при обучении (is_test_release).
|
||||
Запуск: .venv/Scripts/python tests/e2e_segmenter.py
|
||||
Текст kinozal — из data/raw/kinozal/*.json (как у сервиса), см. segmenter.page_text.
|
||||
На трекер берётся не больше E2E_MAX страниц (по умолчанию 400, детерминированно);
|
||||
train-страницы считаются только с флагом --train-pages.
|
||||
Запуск: .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.html_extract import read_input # noqa: E402
|
||||
from src.predict import DubPredictor # noqa: E402
|
||||
from src.release import predict_release # noqa: E402
|
||||
from src.segmenter import is_test_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"))
|
||||
|
||||
|
||||
def run(predictor, rows, mode: str, test_only: bool) -> dict:
|
||||
@@ -27,17 +32,21 @@ def run(predictor, rows, mode: str, test_only: bool) -> dict:
|
||||
for r in rows:
|
||||
by_rel[r["release_id"]].append(r)
|
||||
per_src = defaultdict(list)
|
||||
for page in sorted((ROOT / "data" / "pages").glob("*.txt")):
|
||||
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
|
||||
if is_test_release(rid) != test_only or rid not in by_rel:
|
||||
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]}
|
||||
res = predict_release(predictor, text=read_input(page), segmenter=mode)
|
||||
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[by_rel[rid][0]["source"]].append((p, r))
|
||||
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))
|
||||
@@ -46,13 +55,16 @@ def run(predictor, rows, mode: str, test_only: bool) -> dict:
|
||||
|
||||
def main() -> None:
|
||||
rows = json.loads((ROOT / "data" / "processed" / "dataset.json").read_text(encoding="utf-8"))
|
||||
seg_dir = sys.argv[1] if len(sys.argv) > 1 else "models/segmenter"
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||
seg_dir = args[0] if args else "models/segmenter"
|
||||
predictor = DubPredictor(segmenter_dir=seg_dir)
|
||||
print(f"сегментатор: {seg_dir}")
|
||||
if predictor.segmenter is None:
|
||||
sys.exit("нет models/segmenter: python -m src.segmenter --train")
|
||||
for test_only, name in ((True, "тестовые страницы (не виделись сегментатору)"),
|
||||
(False, "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)
|
||||
|
||||
@@ -246,6 +246,46 @@ merge_extra_tracks(_page2, [
|
||||
check("codec: студия вписана, новая добавлена, без студии — нет",
|
||||
[(x["studio"], x["source"]) for x in _page2]
|
||||
== [("HDrezka", "page+codec"), ("LostFilm", "codec")])
|
||||
_page3 = [dict(_t("проф. многоголосый", None), studio_span=None, studio_confidence=None)]
|
||||
merge_extra_tracks(_page3, [
|
||||
dict(_t("неизвестно", "LostFilm"), studio_span="LostFilm", studio_confidence=1),
|
||||
dict(_t("неизвестно", "TVShows"), studio_span="TVShows", studio_confidence=1)],
|
||||
"codec", match=same_studio, adopt_type=True)
|
||||
check("codec: «Аудио Rus: … | LostFilm» получает тип дорожки страницы",
|
||||
[(x["translation_type"], x["studio"]) for x in _page3]
|
||||
== [("проф. многоголосый", "LostFilm"), ("проф. многоголосый", "TVShows")])
|
||||
|
||||
from src.release import codec_ok, model_segments # noqa: E402
|
||||
from src.segmenter import kinozal_text, label_page # noqa: E402
|
||||
|
||||
|
||||
class _FakeSeg:
|
||||
def select_lines(self, text):
|
||||
return text.splitlines()
|
||||
|
||||
|
||||
_main, _codec = model_segments(_FakeSeg(), "Перевод: Профессиональный многоголосый\n"
|
||||
"Аудио 3: AC3 - Русский (MVO, LostFilm)\n"
|
||||
"Аудио 6: AC3 - Казахский (Dub, TK Qazaqstan)")
|
||||
check("model_segments: основные и кодек-строки раздельно, иноязычная отброшена",
|
||||
_main == ["Профессиональный многоголосый"] and _codec == ["AC3 - Русский (MVO, LostFilm)"])
|
||||
check("codec_ok: «English Dubbing» и «Original» — не русская дорожка",
|
||||
not codec_ok("Аудио 6: E-AC3 - English Dubbing") and not codec_ok("Аудио 5: E-AC3 - Original (французский)")
|
||||
and codec_ok("Аудио Rus: AC3, 2 ch | LostFilm"))
|
||||
check("label_page: raw — 1, неразмеченная кодек-строка — -1, сводка «Аудио:» — 0",
|
||||
label_page(["Перевод: Дублированный", "Аудио 2: AC3 - Русский (MVO, X)",
|
||||
"Аудио: Русский, английский (AC3)", "Размер: 6 ГБ"], ["Перевод: Дублированный"])
|
||||
== [1, -1, 0, 0])
|
||||
import json # noqa: E402
|
||||
import tempfile as _tmp # noqa: E402
|
||||
with _tmp.TemporaryDirectory() as _d:
|
||||
Path(_d, "123.json").write_text(json.dumps({"h1": "X / Y / 2026 / ПМ (LostFilm)",
|
||||
"tech_tab": "Перевод: Профессиональный многоголосый",
|
||||
"release_tab": "Аудио Rus: AC3 | LostFilm"}), encoding="utf-8")
|
||||
check("kinozal_text: заголовок + Техданные + Релиз из raw JSON",
|
||||
kinozal_text("kz-123", Path(_d)).splitlines()
|
||||
== ["X / Y / 2026 / ПМ (LostFilm)", "Перевод: Профессиональный многоголосый", "Аудио Rus: AC3 | LostFilm"]
|
||||
and kinozal_text("kz-404", Path(_d)) is None)
|
||||
|
||||
_dump = ("Меню\nГангстерленд / MobLand / 2025 / ПМ (LostFilm) / WEB\n"
|
||||
"Гангстерленд / 2026 / ДБ (X) / WEB-DL")
|
||||
@@ -381,6 +421,7 @@ check("series_info: сезон, серии, total и «студия -> сери
|
||||
print("== сборщик датасета (tools/collect.py) ==")
|
||||
import csv as _csv # noqa: E402
|
||||
import json as _json # noqa: E402
|
||||
import json # noqa: E402
|
||||
import tempfile as _tmp # noqa: E402
|
||||
from pathlib import Path as _P # noqa: E402
|
||||
|
||||
|
||||
Reference in new issue
Block a user