Обучение модели классификации текста (BERT, RoBERTa, DeBERTa)

При дообучении BERT мы часто сталкиваемся с парадоксом: метрики на валидации отличные (F1 > 0.95), а в продакшене — провал. Причина — несоответствие распределений: train и real‑world данные различаются по длине, стилю, формулировкам. Мы решаем эту проблему системно: от выбора базовой модели до produ

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

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

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

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

При дообучении BERT мы часто сталкиваемся с парадоксом: метрики на валидации отличные (F1 > 0.95), а в продакшене — провал. Причина — несоответствие распределений: train и real‑world данные различаются по длине, стилю, формулировкам. Мы решаем эту проблему системно: от выбора базовой модели до production‑оптимизации. Недавно к нам обратился клиент с задачей классификации отзывов на маркетплейсе — на валидации F1 достигал 0.97, но в реальном потоке упал до 0.82. Анализ показал, что большинство ложных предсказаний приходилось на короткие отзывы (менее 10 токенов) — их почти не было в обучающей выборке. После добавления аугментации и настройки max_length до 128 метрика вернулась к 0.94.

Выбор базовой модели

BERT (bert-base-uncased, DeepPavlov/rubert-base-cased): классика, хорошо изучена, много туториалов. Для большинства задач достаточно.

RoBERTa (roberta-base, ai-forever/ruRoBERTa-large): улучшенное обучение без Next Sentence Prediction, на большем корпусе. Обычно на 1–3% лучше BERT.

DeBERTa (microsoft/deberta-v3-base): диcентанглированное внимание — лучшее качество на benchmarks. Рекомендуется если нужна максимальная точность и есть GPU-ресурсы.

Для русского языка: ai-forever/ruBert-base, DeepPavlov/rubert-base-cased, ai-forever/ruRoBERTa-large, ai-forever/sber-roberta-large.

Как выбрать базовую модель для русского языка?

Выбор зависит от трёх факторов: размера датасета, целевой метрики и доступных вычислительных ресурсов. Для старта мы рекомендуем DeepPavlov/rubert-base-cased — он сбалансирован по качеству и скорости обучения. Если датасет >10К примеров и есть V100/A100, используйте ai-forever/ruRoBERTa-large — прирост F1 до 3%. Для максимальной точности (и при наличии времени на тюнинг) — microsoft/deberta-v3-base.

Pipeline обучения

from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer ) from datasets import Dataset import evaluate import numpy as np # Подготовка данных tokenizer = AutoTokenizer.from_pretrained("DeepPavlov/rubert-base-cased") def tokenize_function(examples): return tokenizer( examples["text"], padding="max_length", truncation=True, max_length=256 # 256 достаточно для большинства задач ) dataset = Dataset.from_pandas(df) tokenized = dataset.map(tokenize_function, batched=True) tokenized = tokenized.train_test_split(test_size=0.2) # Инициализация модели model = AutoModelForSequenceClassification.from_pretrained( "DeepPavlov/rubert-base-cased", num_labels=num_classes, id2label=id2label, label2id=label2id ) # Метрики accuracy = evaluate.load("accuracy") f1 = evaluate.load("f1") def compute_metrics(eval_pred): logits, labels = eval_pred predictions = np.argmax(logits, axis=-1) return { "accuracy": accuracy.compute(predictions=predictions, references=labels)["accuracy"], "f1_macro": f1.compute(predictions=predictions, references=labels, average="macro")["f1"], } # Параметры обучения training_args = TrainingArguments( output_dir="./results", num_train_epochs=5, per_device_train_batch_size=16, per_device_eval_batch_size=32, learning_rate=2e-5, weight_decay=0.01, warmup_ratio=0.1, evaluation_strategy="epoch", save_strategy="epoch", load_best_model_at_end=True, metric_for_best_model="f1_macro", fp16=True, # mixed precision для GPU ) trainer = Trainer( model=model, args=training_args, train_dataset=tokenized["train"], eval_dataset=tokenized["test"], compute_metrics=compute_metrics, ) trainer.train() 

Гиперпараметры и их влияние

Параметр Рекомендуемый диапазон Влияние
learning_rate 1e-5 – 5e-5 Самый критичный. 2e-5 — хороший старт
num_epochs 3–10 Переобучение при > 10
batch_size 8–32 Больше = стабильнее, но нужно больше VRAM
max_length 64–512 Зависит от длины текстов
warmup_ratio 0.06–0.1 Предотвращает нестабильное начало

Сравнение времени инференса моделей

Модель FP32 latency (CPU) INT8 latency (CPU) F1 drop (vs FP32)
ruBERT-base 120ms 18ms -0.3%
ruRoBERTa-large 340ms 52ms -0.5%
DeBERTa-v3-base 190ms 30ms -0.4%

Почему важно использовать weighted loss при дисбалансе классов?

Если классы распределены неравномерно (например, 95% нормальных обращений и 5% жалоб), стандартный CrossEntropyLoss «проигнорирует» редкий класс. Мы добавляем class_weight в функцию потерь:

from torch import nn import torch # Вычисляем веса классов class_weights = compute_class_weight("balanced", classes=np.unique(labels), y=labels) weights_tensor = torch.FloatTensor(class_weights).to(device) class WeightedTrainer(Trainer): def compute_loss(self, model, inputs, return_outputs=False): labels = inputs.pop("labels") outputs = model(**inputs) logits = outputs.get("logits") loss_fn = nn.CrossEntropyLoss(weight=weights_tensor) loss = loss_fn(logits, labels) return (loss, outputs) if return_outputs else loss 

Этот приём поднимает F1 по редкому классу на 10–15% без потери качества на частых.

Оценка и анализ ошибок

После обучения обязательно:

  • Confusion matrix по всем классам
  • Примеры ошибок для каждой пары (истинный класс, предсказанный класс)
  • Calibration plot: насколько достоверны вероятности модели
  • Error analysis: есть ли паттерн в ошибках? (определённые слова, длина текста, авторский стиль)
Детальный чек-лист посттренировочной валидации
  1. Проверить распределение предсказаний на реальных данных (отличных от train/test).
  2. Построить ROC-кривую для каждого класса, убедиться в AUC >0.95.
  3. Выполнить A/B-тест: сравнить новую модель с текущей на случайной выборке.
  4. Замерить latency p99 и throughput на целевой конфигурации.
  5. Подготовить модель карту с ограничениями и известными edge-case.

Оптимизация для продакшена

После fine-tuning экспортировать в ONNX:

from optimum.onnxruntime import ORTModelForSequenceClassification ort_model = ORTModelForSequenceClassification.from_pretrained("./results", export=True) ort_model.save_pretrained("./onnx_model") 

Benchmark: ruBERT fine-tuned → ONNX INT8: 120ms → 18ms на CPU при точности -0.3%. Благодаря квантизации стоимость инференса снижается на 40% по сравнению с FP32. На одном из проектов мы сократили затраты на GPU с $2000 до $1200 в месяц.

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

Мы предоставляем не просто модель, а готовое решение под ключ:

  • Анализ датасета и определение минимального достаточного размера выборки
  • Выбор оптимальной архитектуры (BERT/RoBERTa/DeBERTa) под ваши данные
  • Дообучение с подбором гиперпараметров (learning rate, batch size, epochs)
  • Пост-тренировочный анализ: confusion matrix, calibration curve, error analysis
  • Экспорт в ONNX/TensorRT для инференса на CPU
  • Интеграция в ваш пайплайн (REST API, gRPC, бэтч)
  • Документация модели и метрик, отчёт по качеству
  • Поддержка после внедрения — гарантируем стабильную работу

С нами работают компании с 7+ летним опытом в NLP, мы выполнили более 30 проектов по классификации текстов. Свяжитесь с нами для оценки вашей задачи — мы подберём оптимальный стэк и режим обучения. Закажите консультацию, и мы проанализируем ваш датасет.

Типичные результаты

На задачах классификации новостей: 92–96% F1 macro. Классификация обращений клиентов: 88–94%. Мультиметочная классификация: 78–86% Micro F1. Все результаты фиксируются в отчёте и подтверждаются на отложенной выборке.