Class imbalance на собеседовании Data Scientist

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.

Почему imbalance это проблема

Fraud detection: 99.9% транзакций — нормальные, 0.1% — мошеннические. Модель «всё нормально» имеет accuracy 99.9% — и нулевую бизнес-ценность. На собесе классический вопрос: «Дано 99% класса 0 и 1% класса 1. Модель показывает accuracy 99% — это хорошо или плохо?» Правильный ответ: «Бесполезно, потому что тривиальная модель predict(0) даст ту же метрику. Нужны precision/recall на меньшем классе».

Главная боль без понимания imbalance — DS приносит на ревью модель fraud-детектора с accuracy 99.5%. Через неделю тимлид смотрит — recall на fraud классе 5%, продакт спрашивает «почему мы пропускаем мошенничество». Кандидат, который не различает accuracy и recall, на этом и теряет оффер.

Imbalance бывает при fraud, churn, redkih medical conditions, отказах оборудования, cold start recsys. Основной приём — нет «универсальной серебряной пули», есть набор инструментов.

Метрики при дисбалансе

Accuracy не подходит. Модель predict(majority_class) даёт высокую accuracy, нулевую полезность.

Что использовать:

  • Precision — из тех, кого назвали fraud, сколько реально fraud
  • Recall — из всех реальных fraud, сколько поймали
  • F1 — гармоническое среднее P и R; равноправие precision и recall
  • F-beta (F2, F0.5) — если recall важнее precision (F2) или наоборот
  • PR-AUC — площадь под precision-recall кривой; лучше показывает качество на дисбалансе, чем ROC-AUC
  • ROC-AUC — устойчиво к дисбалансу, но на сильном дисбалансе может быть оптимистичным (большая часть FP не сказывается)
  • Cohen's kappa — accuracy с поправкой на случайное совпадение

Что использовать в зависимости от стоимости ошибки:

  • Цена FP высока (ложно списать деньги клиенту) → следить за precision
  • Цена FN высока (пропустить мошенничество) → следить за recall
  • Симметрично или неизвестно → F1

Confusion matrix на тестовой выборке — обязательно смотреть. Не просто число, а распределение.

Resampling: oversample vs undersample

Oversample (дублирование minority) — повторять примеры minority класса, пока классы не сравняются.

from sklearn.utils import resample
df_minority_upsampled = resample(df_minority, replace=True, n_samples=len(df_majority))
df_balanced = pd.concat([df_majority, df_minority_upsampled])

Минусы: модель видит одни и те же примеры много раз → переобучение на их особенностях.

Undersample (удаление majority) — выкинуть часть majority, пока классы не сравняются.

df_majority_downsampled = resample(df_majority, replace=False, n_samples=len(df_minority))
df_balanced = pd.concat([df_majority_downsampled, df_minority])

Минусы: теряем данные. На больших датасетах это ОК, на малых — критично.

Когда применять: на тренировочной выборке, после split. Никогда — на тесте/валидации, иначе метрики не отражают реальное распределение.

# правильный порядок
X_train, X_test, y_train, y_test = train_test_split(X, y)
X_train_res, y_train_res = oversample(X_train, y_train)  # только train
model.fit(X_train_res, y_train_res)
metrics = evaluate(model, X_test, y_test)  # тест в исходных пропорциях

SMOTE и его варианты

SMOTE (Synthetic Minority Oversampling Technique) — генерация синтетических примеров minority класса. Для каждого minority-примера ищет k ближайших соседей того же класса и делает интерполяцию между ними.

from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
X_res, y_res = smote.fit_resample(X_train, y_train)

Плюсы: не дублирует, добавляет «новые» точки в окрестности.

Минусы:

  • Линейная интерполяция в feature-space — для категориальных признаков ломается
  • На noisy minority-точках усиливает шум

Варианты:

  • Borderline-SMOTE — синтетика только для minority-точек у границы классов
  • SMOTE-NC — для смешанных категориальных и числовых признаков
  • ADASYN — адаптивная плотность, больше синтетики там, где minority «трудный»

Tomek links / Edited Nearest Neighbors — undersampling с удалением «спорных» majority точек около границы.

SMOTE + Tomek — комбинация: oversample minority + чистка majority около границы. Часто даёт лучший результат, чем чистый SMOTE.

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Class weights и cost-sensitive learning

Вместо изменения данных — изменить функцию потерь. Штраф за ошибку на minority-классе выше.

from sklearn.linear_model import LogisticRegression
model = LogisticRegression(class_weight='balanced')
# 'balanced' = w_c = n_samples / (n_classes * count(c))

balanced — автоматически: класс с 1% весит в 99 раз больше класса с 99%.

Кастомные веса: если стоимость FN в 10 раз выше FP — задать class_weight={0: 1, 1: 10}.

В XGBoost / LightGBM:

model = XGBClassifier(scale_pos_weight=ratio_neg_to_pos)

В нейросетях:

loss = nn.CrossEntropyLoss(weight=torch.tensor([1.0, 10.0]))

Преимущество: не меняем выборку, не теряем данные, не дублируем. Часто эффективнее SMOTE на табличных данных.

Focal loss и threshold tuning

Focal loss (Lin et al., RetinaNet) — модификация cross-entropy:

FL = -α · (1 - p_t)^γ · log(p_t)

(1 - p_t)^γ — фокусирующий множитель: уменьшает loss для уверенно правильных предсказаний, усиливает для трудных. Используется в object detection и сильном дисбалансе.

γ=2 — типичное значение. γ=0 — обычный cross-entropy.

Threshold tuning. Бинарный классификатор обычно использует порог 0.5 для отнесения к классу 1. На дисбалансе оптимальный порог редко 0.5.

from sklearn.metrics import precision_recall_curve, f1_score
proba = model.predict_proba(X_val)[:, 1]
prec, rec, thr = precision_recall_curve(y_val, proba)
f1 = 2 * prec * rec / (prec + rec + 1e-9)
best_thr = thr[np.argmax(f1)]

Тюнить порог отдельно от тренировки модели — почти всегда дешёвый и эффективный приём. Особенно если бизнес-метрика — не accuracy, а tradeoff precision/recall с конкретными весами.

Частые ошибки

Resampling до train/test split. Информация из теста утечёт в трейн через интерполяцию SMOTE. Resampling — только на train.

Только accuracy. На дисбалансе бесполезна. Минимум — precision, recall, confusion matrix, PR-AUC.

SMOTE на категориальных признаках без SMOTE-NC. Линейная интерполяция категорий даёт нонсенс.

Балансировать к 1:1, когда не нужно. Если в проде дисбаланс 99:1, а на трейне 50:50 — модель будет выдавать неправильные вероятности. Калибровать через CalibratedClassifierCV или предпочесть class weights.

Не смотреть на калибровку. Модель может разделять классы (хороший AUC), но вероятности «всё или ничего». Для бизнес-применения часто нужны калиброванные вероятности.

Не учитывать стоимость ошибки. Метрика без бизнес-веса — оптимизация впустую. Спросить продакта: «сколько стоит FP? сколько FN?»

Думать, что SMOTE «всегда лучше». На современных бустингах с scale_pos_weight или class_weight часто результат не хуже, без раздувания выборки.

Связанные темы

FAQ

Когда нужен resampling, а когда class weights?

Class weights — первая попытка. Дешёво, не меняет данные. Если результат недостаточен — пробовать SMOTE/undersample. На больших датасетах undersample эффективен (модель не видит дубли). На малых — SMOTE.

Какой дисбаланс считается серьёзным?

Условно: 1:10 — лёгкий, 1:100 — серьёзный, 1:1000+ — экстремальный. Точная граница зависит от размера выборки и сложности задачи.

SMOTE для нейросети работает?

Хуже, чем для линейных моделей и деревьев. Нейросеть и сама может научиться представлению, но при сильном дисбалансе синтетика часто помогает или используют focal loss / class weights в loss.

Как балансировать на multi-class?

class_weight='balanced' или явные веса, SMOTE с sampling_strategy='auto'. Метрики — macro-F1 (среднее F1 по классам, не взвешенное на размер класса) или weighted-F1.

Threshold вместо resampling — вариант?

Да, часто достаточный. Тренируем модель как есть, потом на val подбираем порог под бизнес-метрику (max F1, или max recall при precision > X). Не меняем выборку — вероятности откалиброваны.

Это официальная информация?

Нет. Статья основана на работах по imbalanced learning (Chawla et al., 2002 для SMOTE, Lin et al. 2017 для focal loss) и документации imbalanced-learn / sklearn.


Тренируйте Data Science — откройте тренажёр с 1500+ вопросами для собесов.