При донавчанні 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-ресурси.
Для української мови: ukr-models/bert-base-uk (адаптована версія BERT), DeepPavlov/rubert-base-cased (показує добрі результати на україномовних текстах), ai-forever/ruRoBERTa-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: чи є патерн у помилках? (певні слова, довжина тексту, авторський стиль)
Детальний чек-лист посттренувальної валідації
- Перевірити розподіл передбачень на реальних даних (відмінних від train/test).
- Побудувати ROC-криву для кожного класу, переконатись в AUC >0.95.
- Виконати A/B-тест: порівняти нову модель з поточною на випадковій вибірці.
- Заміряти latency p99 та throughput на цільовій конфігурації.
- Підготувати карту моделі з обмеженнями та відомими 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. Всі результати фіксуються у звіті та підтверджуються на відкладеній вибірці.







