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 <noreply@anthropic.com>
7.6 KiB
Архитектура
Пайплайн
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): у rutrackerraw— строка «Перевод 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) сегментатор пока не включает: там по-прежнему правила.