8.0 KiB
8.0 KiB
План доработки: модель озвучки для grabber
Цель
По ссылке на раздачу (rutracker / kinozal / nnm-club) или её HTML определить на уровне релиза: какие дорожки озвучки есть, кто их сделал (студия / переводчик), какого они типа (дубляж, многоголосый, …) и есть ли 18+. Модель должна быть маленькой и встраиваемой в grabber.
Что уже есть (анализ)
- Multi-task RuBERT (тип 14 классов + BIO-спан автора + 18+), 3742 строки, joint exact 0.946 (base) / 0.915 (tiny2); ONNX-экспорт с parity 100%; FastAPI-сервис; клиенты Rust/TS/PowerShell; тесты и документация.
- grabber (
C:\Users\shats\Dev\grabber) — PHP 8.1 + Docker, интеграции Radarr/Sonarr/qBittorrent/Prowlarr; студии с kinozal разбирает регулярками (KinozalReleaseParser.php,ProwlarrReleaseEnricher.php), идёт Rust-порт. Естественный путь интеграции — HTTP-сервис (PHP ходит curl-ом), ONNX нужен для Rust-порта.
Пробелы относительно задачи
| # | Пробел | Решение |
|---|---|---|
| 1 | Нет входа «по ссылке» (только text/html) |
url в CLI, сервисе, API: fetch (таймаут, UA, cp1251), определение трекера по домену |
| 2 | Ответ — плоский список дорожек | Агрегат релиза: дедуп дорожек, уникальные студии с типами, is_18plus релиза; поле tracks не ломаем |
| 3 | Заголовок раздачи (/ ПМ (LostFilm) /) не используется |
fallback-парсинг title, если дорожек 0 или студии нет в дорожках |
| 4 | kinozal: авторы грузятся JS | при работе по URL запросить вкладку озвучки напрямую (эндпоинт — из data/samples/kinozal*.html, не угадывать) |
| 5 | «Маленькая» модель: best = base (~440 МБ) | tiny2 (30M) + ONNX + int8-квантизация; решение по цифрам parity/размера |
| 6 | Precision 0.83 на rutracker | разобрать ложные срабатывания e2e — скорее всего регулярки сегментации |
| 7 | В git закоммичен examples/rust/*/target/ |
.gitignore + удалить из индекса |
Порядок работ
- Baseline: tests + e2e на текущих моделях (цифры в конец документа).
- URL + агрегат релиза (
src/fetch.py,src/release.py, эндпоинтPOST /predictсurl, CLI--url). - Title-fallback в
html_extract.py. - kinozal-вкладка (если эндпоинт найден в сэмплах).
- Разбор false-positives сегментации.
- tiny2 + int8 ONNX, замер размера/parity.
- Тесты на каждый пункт, обновление docs/README.
.gitignore+ чисткаtarget/.
Критерии готовности
python -m src.predict --url <ссылка>печатает агрегат релиза;- e2e precision/recall не хуже baseline;
- малая модель < ~40 МБ (int8), parity ≥ 99%;
- тесты зелёные, docs обновлены.
Baseline
Модель models/best (ruBert-base), tests/e2e_check.py:
- rutracker: precision 0.826 / recall 0.984 (n=290);
- kinozal: 0.107 / 0.107 (n=28) — в текстовых дампах нет авторов (JS), потолок данных;
- smoke вне датасета: тип 9/10, автор 5/10, 18+ 10/10;
tests/run_tests.py: 34 ok.
Статус
| Шаг | Состояние |
|---|---|
| 1 baseline | сделано |
| 2 URL + агрегат релиза | сделано: src/fetch.py, src/release.py, --url, POST /predict {"url"} |
| 3 title-fallback | сделано (ПМ (LostFilm) → дорожка с source: "title"); заголовок берётся из HTML (<h1>/<title>), из текстового дампа (первая строка вида «… / год / …»), либо передаётся явно (title). Если на странице есть дорожка того же типа без студии, студия вписывается в неё (source: page+title), а не дублируется. Эффект на e2e мал: kinozal P/R 0.107 → 0.143, rutracker без изменений (0.826 / 0.984) — в разметке датасета студия из заголовка почти не нужна |
| 4 kinozal-вкладка | не сделано в сервисе: страница релиза требует авторизации, а get_srv_details.php?id=…&pagesd=N уже вызывает KinozalClient.php в grabber. Рекомендация: grabber скачивает вкладку сам и шлёт {"html": …, "title": <заголовок раздачи>} (во фрагменте вкладки заголовка нет) |
| 5 false-positives сегментации | сделано: починена резка «Авторский, одноголосый» по запятой; постобработка спана (refine_span: до границ слова, мусор вроде L,C,R,l,r/кодеков → null). Остаток — промахи модели на kbps | DUB Автор и особенности разметки |
| 9 переобучение | датасет вырос 3286 → 6864 строк; обе модели переобучены (см. ниже) |
| 6 tiny2 + int8 | сделано: 29.3 МБ, parity 99.8% тип / 99.2% спан / 100% 18+ (reports/onnx_parity_tiny2_int8.md) |
| 7 тесты | run_tests.py: 57 ok (URL allowlist/SSRF, декодирование, заглушки Cloudflare/логина, title, слияние, агрегат) |
8 target/ |
сделано |
Открытые вопросы
- Подтвердить, что grabber (PHP) интегрируется по HTTP (рекомендуется), а
Rust-порт — через ONNX int8 (
models/tiny2-onnx/model.int8.onnx). - tiny2 vs base: held-out joint exact 0.915 против 0.946 (
reports/summary.md); int8 сверху теряет до ~0.8% по спану (parity). e2e на страницах для tiny2 не гонялся. --urlживьём не проверен: rutracker отдал 403 (бот-защита/гео), nnm — 404 на тестовый id. Проверено только на allowlist, декодировании и заглушках.- Шаг 5 (false positives сегментации) и kinozal без авторизации остаются открытыми.
- Веса (
models/,tiny2-onnx) в gitignore и лежат только в этом worktree/основном репозитории.
Итоги после переобучения (датасет 6864 строк, 7780 после подготовки)
| было | стало | |
|---|---|---|
| held-out joint exact, base | 0.946 | 0.950 (тест теперь 516 строк — сравнение условное) |
| held-out joint exact, tiny2 | 0.915 | 0.924 |
| e2e rutracker precision / recall | 0.826 / 0.984 | 0.841 / 0.973 |
| smoke: автор вне датасета | 5/10 | 7/10 |
| tiny2 int8: размер, parity тип / спан / 18+ | 29.3 МБ, 99.8 / 99.2 / 100 | 29.3 МБ, 100 / 98.8 / 100 (ниже порога 99%) |
Для e2e есть страницы только старых релизов (data/pages), для новых строк их нет.
Если порог 99% по спану критичен — для встраивания брать fp32 tiny2 (116 МБ).