Что такое дисбаланс классов в машинном обучении?

Ответ

Дисбаланс классов — это ситуация в задачах классификации, когда распределение экземпляров по классам существенно неравномерно. Один класс (мажоритарный, negative) содержит подавляющее большинство примеров, а другой (миноритарный, positive) — значительно меньше. Например, 99% транзакций легальны и 1% — мошеннические.

Проблемы, которые вызывает дисбаланс:

  1. Смещение модели: Алгоритм, оптимизирующий общую точность (accuracy), быстро научится всегда предсказывать мажоритарный класс, достигая высокой, но бессмысленной метрики (99% accuracy в примере выше).
  2. Плохое обучение на миноритарном классе: Модель редко видит примеры редкого класса и не может выучить его характерные признаки.
  3. Неадекватные метрики оценки: Точность (Accuracy) становится бесполезной. Необходимо использовать метрики, чувствительные к дисбалансу.

Методы борьбы с дисбалансом:

1. На уровне данных (Resampling):

from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline

# Передискретизация (увеличение числа примеров миноритарного класса)
# SMOTE создаёт синтетические примеры на основе существующих
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# Недостаточная выборка (уменьшение числа примеров мажоритарного класса)
rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(X_train, y_train)

# Комбинация (часто лучший подход)
pipeline = Pipeline([
    ('oversample', SMOTE(sampling_strategy=0.5)),  # Увеличим миноритарный до 50% от мажоритарного
    ('undersample', RandomUnderSampler(sampling_strategy=0.8)) # Уменьшим мажоритарный
])
X_res, y_res = pipeline.fit_resample(X_train, y_train)

2. На уровне алгоритма:

  • Взвешивание классов (Class Weight): Присвоение большей «стоимости» ошибке на миноритарном классе в функции потерь.
    from sklearn.linear_model import LogisticRegression
    # 'balanced' автоматически присваивает веса, обратно пропорциональные частоте классов
    model = LogisticRegression(class_weight='balanced')
    # Или вручную:
    model = LogisticRegression(class_weight={0: 1, 1: 10}) # Ошибка для класса 1 в 10 раз «дороже»

3. На уровне оценки:

  • Использование правильных метрик: F1-score (гармоническое среднее precision и recall), Precision-Recall AUC, ROC-AUC, Cohen's Kappa.
  • Анализ матрицы ошибок (Confusion Matrix) вместо одной сводной цифры.

Выбор стратегии зависит от данных и задачи. Часто комбинация взвешивания классов и использования SMOTE даёт наилучшие результаты.

Ответ 18+ 🔞

Слушай, а вот представь себе такую пизданутую ситуацию. Ты учишь модель отличать котиков от собачек, но у тебя в датасете овердохуища котиков, а собачек — штук пять, и те все на одно лицо. Это и есть дисбаланс классов, ёпта. Один класс (мажоритарный) забивает всё, как толпа гопников в маршрутке, а второй (миноритарный) скромно стоит в углу. Например, 99% транзакций нормальные, а 1% — это мошенничество, на котором всем похуй, пока не накроют твой счёт.

И к чему это приводит, блядь:

  1. Модель становится конченой. Она быстро смекает, что проще всего тупо всегда говорить «да это же котик!» и будет права в 99% случаев. Точность (accuracy) — огонь, а толку — нихуя. Модель-то ни хрена не научилась, она просто жмот.
  2. Редкий класс она вообще не учит. Ну как выучить породу собаки, если ты видел только её хвост мельком? Никак. Признаки не уловит, будет всех собак в котики записывать.
  3. Все привычные метрики летят в пизду. Смотреть на accuracy в такой ситуации — это как мерить температуру улицы градусником в жопе. Полная хуйня. Нужны другие штуки.

Как с этим безобразием бороться?

1. Ковыряем сами данные (Resampling): Тут два пути: либо надуть щёки редкому классу, либо постричь мажоритарный. А можно и так, и эдак.

from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline

# SMOTE — это типа волшебник. Берёт редкие примеры и додумывает новые, похожие.
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# А это — грубый садовник. Берёт и просто выкидывает лишние примеры из большого класса.
rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(X_train, y_train)

# Самый адекватный способ — гибрид. Немного надули, немного подстригли.
pipeline = Pipeline([
    ('oversample', SMOTE(sampling_strategy=0.5)),  # Делаем редкий класс покрупнее
    ('undersample', RandomUnderSampler(sampling_strategy=0.8)) # И подрезаем большой
])
X_res, y_res = pipeline.fit_resample(X_train, y_train)

2. Ковыряем алгоритм:

  • Взвешивание классов (Class Weight): Говорим модели: «Слушай, чувак, если ошибёшься на этой редкой собаке — тебе будет в десять раз больнее, чем если перепутаешь тысячного котика». Это заставляет её быть внимательнее к мелочи.
from sklearn.linear_model import LogisticRegression
# Скажи 'balanced', и библиотека сама всё посчитает.
model = LogisticRegression(class_weight='balanced')
# Или сам стань богом и назначь веса вручную.
model = LogisticRegression(class_weight={0: 1, 1: 10}) # Ошибка на классе 1 (собака) будет в 10 раз дороже

3. Меняем взгляд на результат:

  • Забей на accuracy, блядь. Смотри на F1-score (он как среднее арифметическое, только для умных), Precision-Recall AUC, ROC-AUC. Распечатай матрицу ошибок (Confusion Matrix) и смотри, где конкретно модель лажает.

Что в итоге делать? Э, сабака сука, универсального рецепта нет. Чаще всего работает комбинация: накрутил весов в алгоритме и немного поколдовал над данными через SMOTE. Главное — не игнорируй проблему, а то твоя крутая модель окажется просто хитрой жопой, которая всех кидает в один класс, и доверия к ней — ноль ебать.