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

Модель в 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, а не до слайда — оставьте заявку.