Files
DenozordecandClaude Opus 5.5 89ffe4375b
docker / build (push) Successful in 39s
Документация: кодек-строки, аудит дорожек, текущие метрики
README: метрики модели 02.11.2026 (test 4 377 строк, e2e по трекерам), размер
датасета, kinozal-вкладки. dataset.md: размер снимка, колонки studio_source и
audio_raw. architecture.md: кодек-строки как позитивы сегментатора, E2E_MODEL.
collecting.md: tools/audit_tracks.py. training-windows.md: доразметка, сравнение
кандидата и постановка в models/best и Docker. plan.md: шаг «аудит дорожек».

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 21:55:43 +07:00

101 lines
7.6 KiB
Markdown
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.
# Архитектура
## Пайплайн
```
labels.csv (снимок датасета) HTML/текст страницы (инференс)
│ │
▼ ▼
src/prepare_data.py src/html_extract.py
• снять префикс «Перевод/Аудио N:» • HTML -> текст (BeautifulSoup:
• BIO-разметка автора (спан в тексте) .post_body / #tabs / весь текст)
• фильтры: мусорный studio, • сегментация на дорожки
«оригинал/субтитры», противоречия (строки «Перевод/Озвучка/Аудио N:»,
• 18+ флаг (age_18 / note / «18+» в raw) нумерация, «+ ; /», запятые-типы)
• сплиты: test 20% + 5-fold CV │
(группы = релиз ∪ одинаковый текст) ▼
│ src/predict.py (DubPredictor)
▼ • токенизация (max_len=128,
src/train.py offsets)
• multi-task обучение (CUDA, bf16) • модель -> тип, спан, 18+
• 5-fold CV + финальная модель • нормализация автора
• метрики, reports/, models/best • JSON
```
## Модель (src/model.py)
Один BERT-подобный энкодер и три головы (общий `[CLS]`/токенные состояния):
| Голова | Тип | Выход | Задача |
|---|---|---|---|
| `type_head` | Linear(hidden, 14) | `[CLS]` → класс | тип озвучки |
| `bio_head` | Linear(hidden, 3) | по токенам → O/B/I | позиция имени автора в тексте |
| `age_head` | Linear(hidden, 2) | `[CLS]` → 0/1 | дорожка 18+ |
Loss = CE(тип) + 1.0·CE(BIO, ignore_index=-100) + 0.5·CE(18+).
BIO-разметка строится по символьным спанам через `offsets_mapping`
токенизатора: токены, пересекающие спан, получают `B` (первый) и `I`
(остальные); все реальные токены вне спана — `O`; паддинг — `-100`.
Спецтокены ([CLS]/[SEP]) размечены как `O` — иначе их необученные логиты
при инференсе дают ложный «B» (эта ошибка была найдена при обучении и
исправлена; см. `decode_span` — он дополнительно пропускает токены с
пустыми offsets).
Декодирование спана: первый `B`-токен, затем цепочка `I` → символьный
диапазон через offsets → текст спана → каноническое имя автора через
словарь алиасов (`src/studio_norm.py`, порт из генератора датасета).
## Почему автор — извлечение, а не классификация
В датасете 253+ уникальных авторов, 177 из них встречаются ровно 1 раз.
Классификацией такие классы не выучить. При этом имя автора почти всегда
дословно присутствует в тексте дорожки (95.8% + варианты написания),
поэтому BIO-извлечение решает задачу и автоматически работает для авторов,
которых в обучении не было вовсе.
## Сравнение конфигураций
Обе модели учатся одинаково (5-fold CV + финальный запуск, ранний stopping
по среднему type-F1/span/age-F1):
* `cointegrated/rubert-tiny2` — 30M, lr 1e-4, batch 64, до 20 эпох;
* `ai-forever/ruBert-base` — 110M, lr 3e-5, batch 32, до 10 эпох.
Лучшая по joint exact на held-out test копируется в `models/best`.
Итог — в `reports/summary.md`.
## Сегментатор страниц (src/segmenter.py) — главный путь
Какие строки страницы описывают дорожки, решает обученная модель
(rubert-tiny2, бинарная классификация строки с контекстом «пред. || след.»),
а не регулярки. Правила (`html_extract.extract_segments`) — запасной путь:
используются, если `models/segmenter` нет или модель не нашла ни одной строки;
режим можно форсировать: `predict_release(..., segmenter="rules")`.
* Обучение: `python -m src.segmenter --train --out models/segmenter_v2` (или меню
`tools/collect.py` → «2»). Разметка строк — по `raw` и `audio_raw` из `labels.csv`
(страницы `data/pages`); синтетика: `raw` всех train-строк вставляются в контекст
реальных строк страниц. Страницы делятся по хешу `release_id` (≈20% в тест).
* Текст kinozal берётся из `data/raw/kinozal/<id>.json` (`h1` + «Техданные» +
«Релиз», `segmenter.page_text`): в `data/pages/kz-*.txt` нет ни заголовка, ни
«Техданных», откуда взята половина меток kinozal, а сервис (`fetch_html`)
видит обе вкладки.
* Кодек-строки со студией с 02.11.2026 размечены сами (`studio_source=codec`, см.
`docs/collecting.md`): их `raw`/`audio_raw` дают позитивы. Кодек-строка русской
дорожки («Аудио 3: AC3 … Русский (MVO, LostFilm)», «Аудио Rus: … | LostFilm»),
не попавшая в разметку, в обучение не идёт
(метка -1): у rutracker `raw` — строка «Перевод N:», и как негатив она учила
модель отбрасывать кодек-строки — а на kinozal студии только в них.
* В `predict_release` выбранные моделью кодек-строки — второстепенный источник
(`source: codec`): подключаются, только если у какой-то дорожки страницы нет
студии; если других строк нет (nnm), они и есть дорожки.
* e2e: `tests/e2e_segmenter.py [каталог]` — пары (тип, студия) на тестовых
страницах, не больше `E2E_MAX` (400) на трекер; `--train-pages` — ещё и train;
основная модель — `E2E_MODEL` (по умолчанию `models/best`), например
`set E2E_MODEL=models/runs/<запуск>` перед запуском, чтобы сравнить кандидата.
* Нарезка ВНУТРИ строки («Дублированный, многоголосый») в данных не размечена
(91 группа из 6735) — остаётся на правилах `_split_entries`, но только на
строках, выбранных моделью.
* ONNX-путь (Rust/TS) сегментатор пока не включает: там по-прежнему правила.