Files
grabber-ai/docs/architecture.md
T
DenozordecandClaude Opus 5.5 8511744d3a
docker / build (push) Successful in 1m21s
Несколько студий в одной строке, разбор противоречий, правки парсеров студий
Парсеры (PARSE_VERSION 9): «т/к» и единицы битрейта не режутся по слэшу, техпараметры
(kb/s, Кбит/сек, 16 бит, channels, CBR, Atmos) и пол голоса (муж./жен.) не становятся студией,
«от X» -> X; «N-й канал», «5 канал», «Лексикон» больше не выбрасываются как мусор; строка без типа
со списком из 3+ имён (актёры, титры сабберов) не даёт дорожек; в кодек-строках имя рядом со
скобкой пола становится кандидатом, а строка только с мусором даёт дорожку с типом без студии.

Несколько авторов строки («Многоголосый (HDRezka Studio, LostFilm)» — верны все): prepare_data
склеивает строки релиза в одну со списком spans, обучение размечает все спаны BIO, span_exact
сравнивает наборы, decode_spans возвращает все прогоны; сервис отдаёт studio_spans / studios /
studio_confidences, в ответе релиза — дорожка на каждого автора. Противоречий в датасете
20 % -> 1.2 %; проверочная tiny2 находит весь набор студий в 71 % строк с 2+ авторами
(прежняя модель — 1 %).

Разбор противоречий (collect.py, пункт 8): тексты, которые релизы разметили по-разному;
решения в data/text_fixes.csv применяются в prepare_data поверх labels.csv.

Метки и модель не пересобраны: нужны collect.py build --relabel, prepare_data, переобучение.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 16:24:52 +07:00

108 lines
8.2 KiB
Markdown
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.
# Архитектура
## Пайплайн
```
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).
Декодирование (`decode_spans`): каждая цепочка `B`/`I` → символьный
диапазон через offsets → текст спана → каноническое имя автора через
словарь алиасов (`src/studio_norm.py`, порт из генератора датасета).
У строки может быть несколько авторов: «Многоголосый (HDRezka Studio,
LostFilm, TVShows)» значит, что верны все три. `prepare_data` склеивает
строки одного релиза с одинаковым текстом и типом в одну строку
(`spans` — все спаны, `span` — первый), BIO размечает все, `span_exact`
сравнивает наборы. Сервис отдаёт `studio_spans`/`studios`; в ответе
релиза такая строка раскладывается на дорожку на каждого автора.
## Почему автор — извлечение, а не классификация
В датасете 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) сегментатор пока не включает: там по-прежнему правила.