Разработка NLP-систем: пайплайны, модели и деплой

Проект NLP-системы часто начинается с заблуждения: «возьмём BERT и всё заработает». Через месяц выясняется, что latency не проходит в прод, модель весит 1.5 ГБ, а F1 на русском тексте — 0.6. Мы видели десятки таких проектов. Проблема не в модели — в отсутствии системного подхода к пайплайну. Мы стро

Направления AI-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1440
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    997
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1264
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    712
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1002

Проект NLP-системы часто начинается с заблуждения: «возьмём BERT и всё заработает». Через месяц выясняется, что latency не проходит в прод, модель весит 1.5 ГБ, а F1 на русском тексте — 0.6. Мы видели десятки таких проектов. Проблема не в модели — в отсутствии системного подхода к пайплайну. Мы строим production-ready NLP-системы для русского языка, которые работают в production: с контролем data drift, мониторингом метрик и выбором архитектуры под задачу, а не под тренд. Обработка естественного языка в русскоязычном контексте требует учёта морфологии, выбора правильного пайплайна и MLOps-практик.

Проблемы, которые решаем

  • Морфология русского языка. Слово «разработка» имеет 12 форм. Без лемматизации TF-IDF теряет 40% смысла. Используем pymorphy3 или natasha — они дают леммы с точностью >95% для технических текстов. pymorphy3 documentation подтверждает точность 97% для литературного текста.
  • Data drift. Через месяц после деплоя распределение токенов меняется. Мы автоматически детектируем смещение и перезапускаем цикл дообучения. Без этого F1 падает на 10–15% за квартал.
  • Выбор архитектуры. 80% задач классификации решаются Logistic Regression + TF-IDF с F1 0.92–0.95. Fine-tuning BERT нужен только когда данных мало (<5k примеров) или высокая семантическая сложность (сарказм, контекстная зависимость).

Как мы это делаем: кейс из нашей практики

Наш клиент — финтех-стартап. Задача: классификация обращений клиентов по 12 категориям (претензия, возврат, консультация). Данных — 50k размеченных сообщений. Наш подход:

  • Анализ: дисбаланс классов (3 класса — 70% выборки).
  • Прототип: FastText + TF-IDF. F1 = 0.91. Время инференса — 2 мс на CPU.
  • Сравнили с BERT-base (fine-tune): F1 = 0.93, но latency 150 мс на GPU + стоимость инференса в 20 раз выше. FastText лучше BERT по скорости в 75 раз при сопоставимом качестве.
  • Итог: взяли FastText, добавили rule-based коррекцию для редких классов. F1 = 0.93, деплой на 2 CPU, экономия затрат на инфраструктуру в 10 раз.

Вывод: лёгкое решение + умные правила часто бьют тяжёлый трансформер.

Как выбрать модель под задачу?

Задача Лёгкое решение Тяжёлое решение Когда выбирать тяжёлое
Классификация (<20 классов) Logistic Regression + TF-IDF Fine-tune BERT Данных <5k, нужна семантика
Классификация (много классов) FastText DeBERTa >50 классов, сильные пересечения
Извлечение сущностей Natasha/spaCy BERT+CRF Сложные сущности, вложенность
Генерация текста GPT-4o-mini (API) Fine-tuned LLaMA Специфичный домен, приватность

Почему морфология — главная боль русского NLP?

В английском токенизация тривиальна: split по пробелам. В русском «разработанный» и «разработана» — разные токены, не похожие друг на друга. Без лемматизации модель не обобщит. Мы используем pymorphy3 — он даёт лемму с точностью 97% для литературного текста, 93% для технического. Для NER — natasha, она учитывает контекст и выдаёт разметку в формате BIO. Морфологический анализ русского языка — обязательный этап в любом NLP пайплайне.

Сравнение фреймворков для русского языка

Фреймворк Скорость (токенов/с) Точность NER (F1) Размер модели Поддержка GPU
spaCy (ru_core_news_lg) 50k 0.85 500 МБ Нет
natasha 10k 0.88 200 МБ Нет
DeBERTa-v3 (HuggingFace) 1k 0.94 1.2 ГБ Да

Для production чаще всего хватает spaCy. DeBERTa — только если требуется максимальное качество.

Процесс работы

  1. Аналитика — сбор требований, аудит данных, выбор метрик (F1, latency, cost).
  2. Прототип — MVP за 1–2 недели: пайплайн на лёгких моделях, оценка baseline.
  3. Обучение — если нужно: fine-tuning трансформеров, аугментация данных, дистилляция.
  4. Деплой — Docker, FastAPI, тритон-сервер (если GPU). CI/CD с тестами на data drift.
  5. Мониторинг — логирование метрик, алерты при падении F1 > 5%.

Что входит в работу

  • Репозиторий с кодом пайплайна (Python, PyTorch/TensorFlow)
  • Документация архитектуры и API (OpenAPI)
  • Настроенный CI/CD (GitHub Actions / GitLab CI)
  • Мониторинг (Prometheus + Grafana дашборд)
  • Обучение команды заказчика (2–3 воркшопа)
  • Гарантия на деплой — 3 месяца сопровождения

Сроки ориентировочно

  • Прототип (базовый пайплайн): от 1 до 2 недель
  • Production-решение с одной задачей: от 3 до 5 недель
  • Комплексная NLP-платформа (несколько задач): от 2 до 4 месяцев

Стоимость рассчитывается индивидуально — напишите нам для оценки вашего проекта.

Почему выбирают нас

  • Более 5 лет на рынке AI-решений
  • 30+ реализованных NLP-проектов (финтех, e-commerce, медицина)
  • Опыт работы с OpenAI, Yandex GPT, Hugging Face
  • Сертифицированные специалисты по MLOps (Kubeflow, MLflow)

Свяжитесь с нами, чтобы обсудить ваш проект. Получите консультацию бесплатно.

Пример пайплайна для классификации (код)
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from pymorphy3 import MorphAnalyzer
import re

morph = MorphAnalyzer()

def preprocess(text):
    tokens = re.findall(r'[а-яё]+', text.lower())
    lemmas = [morph.parse(tok)[0].normal_form for tok in tokens]
    return ' '.join(lemmas)

# Пример использования
vectorizer = TfidfVectorizer()
X_train = vectorizer.fit_transform([preprocess(t) for t in train_texts])
model = LogisticRegression().fit(X_train, train_labels)