Поделиться
Поделиться

Модель в Jupyter выглядела убедительно: F1 выше 0.9, демо на трёх PDF прошло без ошибок. Через две недели в production точность упала, пользователи жаловались на «галлюцинации» полей, а команда не могла ответить, какая версия модели сейчас на сервере. Это классический разрыв между ML-прототипом и ML-продуктом.

В OtherCode мы строим Python-контуры для OCR и NLP в URAP AI, для разбора документов в Content Factory и для вспомогательных классификаторов в промышленных и SaaS-продуктах. Ниже — как мы доводим модели до production: версионирование, eval-наборы, CI, мониторинг дрифта и жёсткое разделение «эксперимент» и «релиз».

Прототип ≠ продукт: где ломается цепочка

| Аспект | Прототип / notebook | Production-продукт | |--------|---------------------|--------------------| | Данные | 50–200 размеченных примеров | Версионированный датасет + hold-out | | Метрика | «В целом хорошо» | SLA: precision/recall по классам, latency p95 | | Деплой | Ручной скрипт на ноутбуке | Docker-образ, GitLab CI, откат | | Мониторинг | Нет | Drift, error rate, human feedback | | Ответственность | Исследователь | Команда продукта + on-call |

Прототип отвечает на вопрос «можно ли». Продукт отвечает на вопросы «стабильно ли», «сколько стоит», «как откатить» и «кому звонить ночью».

В URAP AI распознавание и извлечение полей из документов — не разовое демо, а ежедневный поток: разные сканеры, качество печати, шаблоны актов и счетов. Без MLOps-контура любая «улучшенная модель» — лотерея.

Архитектура ML-контура в OtherCode

Типовой путь от сырых документов до ответа API:

[Загрузка документа]

[Предобработка / OCR]  ← Python-сервис (URAP)

[Извлечение / NLP / классификация]

[Валидация + rule engine]

[API / очередь Redis] → [PostgreSQL]

[Feedback / разметка] → [Eval set] → [Retrain]

Python здесь — основной язык пайплайна: предобработка изображений, OCR, постобработка текста, лёгкие классификаторы и оркестрация вызовов LLM. Тяжёлые модели упаковываем в отдельные контейнеры, лёгкие — в worker рядом с API.

Для асинхронных задач (длинные PDF, пакетная обработка) используем Redis-очереди — тот же подход, что и в остальных backend-сервисах студии. Подробнее про очереди и кэш мы писали в статье про Redis.

Как это стыкуется с остальными продуктами

  • URAP AI — ядро OCR/NLP: модели и пайплайны версионируются, eval прогоняется в CI перед релизом.
  • Content Factory (Telegram) — генерация и разбор контента: правила + модели, без «магического» промпта в проде без метрик.
  • Synapse Stream / Game API — геоданные и realtime на PostGIS; ML здесь точечный (классификация событий), но тот же принцип: модель = артефакт с версией.
  • Typhoon (STM32/Modbus) и Galvatec — телеметрия и промышленные контуры; ML не «вместо» протокола, а поверх нормализованных сигналов.
  • Diom (Flutter) и клиенты на Next.js — UI показывает confidence и даёт human-in-the-loop, а не слепо доверяет модели.

Версионирование моделей и данных

Без версий нельзя воспроизвести баг и нельзя честно сравнить две модели.

Что мы версионируем

| Артефакт | Как храним | Зачем | |----------|------------|-------| | Датасет (train/val/test) | Object storage + manifest (хеш, дата, источник) | Воспроизводимость | | Код пайплайна | Git (монорепо или отдельный ML-репо) | Аудит изменений | | Веса / ONNX / pickled pipeline | Object storage, тег model@version | Деплой и откат | | Конфиг препроцессинга | Рядом с моделью в образе | Один «снимок» поведения | | Eval-отчёт | CI artifact + запись в БД | История качества |

Правило студии: один релиз = один неизменяемый артефакт (Docker-образ с зафиксированными весами и конфигом). Не «подтянули свежие веса на VPS ночью».

Семантические версии для моделей

Используем схему, понятную продукту:

  • MAJOR — ломающее изменение выхода (другая схема JSON, другие классы).
  • MINOR — улучшение качества при совместимом API.
  • PATCH — hotfix препроцессинга, баг в постобработке.

В API URAP клиент видит model_version в ответе — это упрощает разбор жалоб: «в v2.3.1 поле ИНН ломалось на сканах с поворотом».

Eval-наборы: метрика важнее ощущения

Демо на «красивых» документах обманывает. Eval-набор — это контракт качества.

Как собираем eval в URAP

  1. Стратификация: типы документов (счёт, акт, паспорт, произвольный скан), качество (хороший/шум/повёрт), источники (мобильное фото / МФУ).
  2. Hold-out: тестовый набор не участвует в обучении и в подборе порогов.
  3. Золотые поля: не только «текст в целом», а точность по ключевым полям (ИНН, сумма, дата, номер договора).
  4. Human baseline: сколько ошибок делает оператор — чтобы не гнаться за нереалистичным 100%.

| Метрика | Для чего | Типичный порог релиза | |---------|----------|------------------------| | Field-level accuracy | Ключевые поля | ≥ целевого SLA по типу дока | | Exact match JSON | Строгие интеграции | По договорённости с заказчиком | | Latency p95 | UX и нагрузка | В пределах бюджета инстанса | | Share of «needs review» | Нагрузка на людей | Не растёт после релиза |

Релиз блокируется, если новый кандидат хуже предыдущего на критичных полях или сильно медленнее при той же инфраструктуре.

Практика OCR-пайплайнов (предобработка, движки, таблицы) разобрана отдельно: OCR и распознавание документов на Python.

CI для ML: не только «собрать образ»

GitLab CI у нас — общие docker runners. Для ML-пайплайна типичные стадии:

  1. lint / unit — чистый Python: парсеры, валидаторы схемы, препроцессинг без GPU.
  2. smoke inference — прогон 5–20 эталонных документов на CPU-образе (быстро ловит сломанный импорт и несовместимость зависимостей).
  3. eval job — полный eval на runner с нужными ресурсами; артефакт — JSON/HTML отчёт.
  4. build & push — образ в registry только если eval прошёл пороги.
  5. deploy — staging → production (PM2/compose на VPS или сервис в Yandex Cloud), с возможностью отката на предыдущий тег.

Параллельные runners ускоряют «обычный» backend и ML-ветки, но GPU-eval планируем отдельно: дорогой ресурс не должен блокировать каждый merge мелкого фикса в UI Next.js.

Важно: notebook не является артефактом релиза. Код эксперимента переносится в пакет (src/urap_ocr/...), покрывается тестами и только потом попадает в образ.

Мониторинг дрифта и деградации

Модель стареет не потому что «веса портятся», а потому что меняется мир: новые бланки, другой сканер в филиале, смена шрифта в ERP заказчика.

Что мониторим в production

| Сигнал | Как считаем | Реакция | |--------|-------------|---------| | Input drift | Распределение размеров, DPI, языка, доли пустых OCR | Алерт + выборка на разметку | | Prediction drift | Доли классов / средний confidence | Сверка с eval | | Quality proxy | Доля ручных правок, отказов API, тикетов | Приоритет retrain | | Latency / errors | p95, OOM, timeout очереди | Масштабирование / откат |

В URAP feedback от оператора (исправленное поле) — золотой источник для следующего eval и дообучения. Без замкнутого цикла «ошибка → датасет → релиз» MLOps превращается в театр.

Rule engine рядом с моделью

Модель предлагает, правила страхуют. Для документов это критично:

  • форматные проверки (ИНН, даты, суммы);
  • согласованность полей («сумма прописью» vs число);
  • маршрутизация: низкий confidence → очередь человека;
  • запрет автозаписи в ERP без подтверждения на критичных полях.

Тот же принцип гибрида «ИИ + правила» мы используем, когда встраиваем LLM в продукты: модель не заменяет доменную логику. Общий взгляд на AI в продуктах — в материале Разработка с AI.

Инфраструктура: где крутятся модели

  • Разработка: Docker Compose локально, те же образы, что уедут в CI.
  • CI: GitLab docker runners; тяжёлый eval — по расписанию или по тегу ml-release.
  • Production: для большинства сервисов othercode.ru и клиентских VPS RU — Docker + PM2 для Node-слоя, Python-workers в compose; при необходимости — Yandex Cloud (в т.ч. GPU под обучение/batch, не обязательно под каждый online-запрос).
  • Стоимость: online-инференс стараемся держать на CPU/оптимизированных форматах (ONNX), GPU — для обучения и пакетных джобов.

Kubernetes включаем, когда реально нужны автоскейл и много сервисов с жёстким изоляционным контуром. Для многих продуктов студии compose + PM2 на выделенном VPS проще и дешевле — без потери дисциплины артефактов.

От эксперимента к API: рабочий процесс команды

Внутри студии ML-задача почти никогда не живёт «в голове одного data scientist». Типичный цикл для URAP или похожего контура:

  1. Продукт формулирует SLA — какие поля критичны, какая доля ручной проверки допустима, какой p95 ответа API.
  2. Собираем или расширяем датасет — с разметкой и источником (клиент, синтетика, публичные бланки только как черновик).
  3. Базовый пайплайн без «тяжёлой» модели — правила + OCR + эвристики; это нижняя планка качества и стоимости.
  4. Модель как улучшение — сравниваем с baseline на том же eval, а не с ощущением «стало умнее».
  5. Упаковка — сервис с ясным контрактом JSON, таймаутами, идемпотентностью для очередей.
  6. Канареечный выкат — часть трафика на новую версию, сравнение error rate и правок операторов.
  7. Ретро после двух недель — что деградировало, какие типы документов добавить в train/eval.

Для Content Factory цикл короче: шаблоны и стоп-слова часто важнее fine-tune. Для Synapse/Game API классификаторы событий появляются точечно — только если правило не закрывает кейс. В Typhoon и Galvatec сначала нормализуем регистры и единицы измерения; «нейросеть на сыром Modbus» без словаря сигналов мы не делаем.

Роли и ответственность

| Роль | Зона | |------|------| | Product / заказчик | SLA, цена ошибки, приоритет типов документов | | Backend | API, очереди Redis, деплой, наблюдаемость | | ML / Python | Пайплайн, eval, версия модели | | Frontend (Next.js / Flutter Diom) | UX confidence, очередь review, понятные ошибки | | Ops | GPU/CPU бюджет, runners, бэкапы артефактов |

Без владельца метрики модель «сиротеет» после первого релиза — это частая причина тихого дрифта.

Экономика инференса и очередей

Production ML упирается в деньги так же часто, как в F1.

  • Синхронный путь — только для коротких документов и ясного UX-ожидания.
  • Длинные PDF и пакеты филиалов — в Redis-очередь, статус задачи в PostgreSQL, webhook или polling для клиента.
  • Кэш результатов по хешу файла экономит повторный OCR одного и того же скана.
  • Батчинг на GPU имеет смысл, когда очередь стабильно непустая; иначе платим за простой карты.

На VPS RU и в Yandex Cloud мы явно разделяем бюджет online API и budget batch/retrain. Иначе «ночной эксперимент» внезапно становится строкой в счёте клиента.

Документация, которую реально читают

Минимум рядом с сервисом:

  • как воспроизвести eval локально (Compose + фикстуры);
  • таблица breaking changes API и model_version;
  • runbook: «точность упала» → какие дашборды, как откатить тег, кого звать;
  • описание полей feedback из UI оператора.

Это скучно по сравнению с новым fine-tune, но именно это отличает продукт студии от прототипа подрядчика.

Чеклист: готовность модели к production

  • [ ] Есть версионированный train/val/test и описание источников данных
  • [ ] Eval-метрики согласованы с бизнесом (не только «accuracy»)
  • [ ] Модель упакована в immutable Docker-образ с конфигом
  • [ ] CI прогоняет smoke + eval до деплоя
  • [ ] В ответе API есть model_version
  • [ ] Есть мониторинг latency, ошибок и proxy качества
  • [ ] Есть план отката на предыдущий тег
  • [ ] Human-in-the-loop на низком confidence
  • [ ] Документирован retrain-процесс (кто, как часто, на каких данных)

Типичные ошибки, которых мы избегаем

  1. Релиз «с ноутбука» — невоспроизводимо и небезопасно.
  2. Один общий test set на все эксперименты — незаметный data leakage.
  3. Оптимизация только average accuracy — просадка на редком, но дорогом классе документов.
  4. Игнор latency — модель точнее на 2%, но p95 вырос втрое.
  5. Нет владельца метрики — «ML-инженер ушёл, F1 никто не смотрит».

Итог

Python в OtherCode — не про красивые графики в Colab, а про управляемый путь: данные → eval → образ → CI → production → feedback. URAP AI живёт в этом контуре; соседние продукты (Synapse, промышленные интеграции, Flutter/Next.js-клиенты, Telegram Content Factory) забирают тот же инженерный стандарт: версия, метрика, откат.

Если нужна команда, которая доведёт OCR/NLP или классификаторы до стабильного API, а не до слайда — оставьте заявку.