← Назад к вопросам

Как работать с несбалансированными данными?

2.0 Middle🔥 211 комментариев
#Машинное обучение#Метрики и оценка моделей

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Работа с несбалансированными данными (Imbalanced Data)

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

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

Пример проблемы

import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, precision_recall_fscore_support

# Создать несбалансированный датасет (99-1)
X, y = make_classification(n_samples=10000, n_features=20, 
                           weights=[0.99, 0.01], random_state=42)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

model = RandomForestClassifier()
model.fit(X_train, y_train)

predictions = model.predict(X_test)

# Accuracy показывает 99%, но на самом деле модель просто предсказывает класс 0
print(f'Accuracy: {accuracy_score(y_test, predictions):.2%}')
print(f'Precision: {precision_recall_fscore_support(y_test, predictions)[0][1]:.2%}')
print(f'Recall: {precision_recall_fscore_support(y_test, predictions)[1][1]:.2%}')
# Recall может быть очень низким для редкого класса

Методы решения

1. Изменение метрик оценки

Используй правильные метрики вместо accuracy:

from sklearn.metrics import confusion_matrix, classification_report, roc_auc_score, f1_score

# ПЛОХО: использовать accuracy
# print(f'Accuracy: {accuracy_score(y_test, predictions)}')

# ХОРОШО: использовать F1-score, precision-recall, ROC-AUC
print(f'F1-score: {f1_score(y_test, predictions)}')
print(f'ROC-AUC: {roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])}')
print(f'\nClassification Report:')
print(classification_report(y_test, predictions))

# Матрица ошибок показывает, что происходит с каждым классом
print(f'\nConfusion Matrix:')
print(confusion_matrix(y_test, predictions))

2. Переsampling: Over-sampling редкого класса

Добавить больше примеров редкого класса:

from imblearn.over_sampling import RandomOverSampler, SMOTE

# Вариант 1: Простое дублирование (Random Over-sampling)
ros = RandomOverSampler(random_state=42)
X_train_ros, y_train_ros = ros.fit_resample(X_train, y_train)

print(f'Исходное распределение: {np.bincount(y_train)}')
print(f'После ROS: {np.bincount(y_train_ros)}')

# Вариант 2: SMOTE (Synthetic Minority Over-sampling Technique) — более умный
# Создаёт синтетические примеры редкого класса
smote = SMOTE(random_state=42)
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)

model = RandomForestClassifier()
model.fit(X_train_smote, y_train_smote)
predictions = model.predict(X_test)
print(f'F1-score с SMOTE: {f1_score(y_test, predictions)}')

3. Переsampling: Under-sampling большинства класса

Уменьшить количество примеров большинства:

from imblearn.under_sampling import RandomUnderSampler, TomekLinks

# Вариант 1: Случайное удаление
rus = RandomUnderSampler(random_state=42)
X_train_rus, y_train_rus = rus.fit_resample(X_train, y_train)

print(f'После RUS: {np.bincount(y_train_rus)}')

# Вариант 2: TomekLinks — удаляет пары из большинства, близкие к меньшинству
tkl = TomekLinks()
X_train_tkl, y_train_tkl = tkl.fit_resample(X_train, y_train)

4. Комбинированный подход: SMOTE + Tomek

from imblearn.pipeline import Pipeline as ImbPipeline
from imblearn.combine import SMOTETomek

# SMOTETomek = SMOTE + TomekLinks
smt = SMOTETomek(random_state=42)
X_train_smt, y_train_smt = smt.fit_resample(X_train, y_train)

model = RandomForestClassifier()
model.fit(X_train_smt, y_train_smt)

5. Взвешивание классов в модели

Некоторые модели поддерживают параметр class_weight:

# Автоматическое расчёт весов на основе частоты класса
model = RandomForestClassifier(class_weight='balanced', random_state=42)
model.fit(X_train, y_train)  # Без переsampling!
predictions = model.predict(X_test)

print(f'F1-score с class_weight: {f1_score(y_test, predictions)}')

# Или задать веса вручную
from sklearn.utils.class_weight import compute_class_weight

weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)
class_weights = {0: weights[0], 1: weights[1]}

model = RandomForestClassifier(
    class_weight=class_weights,
    random_state=42
)
model.fit(X_train, y_train)

6. Коррекция threshold (Decision Threshold)

По умолчанию модель предсказывает класс 1, если probability > 0.5. Можно изменить:

from sklearn.metrics import precision_recall_curve

# Получить вероятности
y_proba = model.predict_proba(X_test)[:, 1]

# Найти оптимальный threshold
precision, recall, thresholds = precision_recall_curve(y_test, y_proba)

# Выбрать threshold, который максимизирует F1
f1_scores = 2 * (precision * recall) / (precision + recall + 1e-10)
optimal_idx = np.argmax(f1_scores)
optimal_threshold = thresholds[optimal_idx]

print(f'Оптимальный threshold: {optimal_threshold:.3f}')

# Применить новый threshold
predictions_tuned = (y_proba >= optimal_threshold).astype(int)
print(f'F1-score с новым threshold: {f1_score(y_test, predictions_tuned)}')

Полный пример с Pipeline

from imblearn.pipeline import Pipeline as ImbPipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from imblearn.over_sampling import SMOTE

# Создать pipeline с балансировкой
pipeline = ImbPipeline([
    ('scaler', StandardScaler()),
    ('smote', SMOTE(random_state=42)),
    ('model', RandomForestClassifier(class_weight='balanced', random_state=42))
])

# Обучить
pipeline.fit(X_train, y_train)

# Предсказать (SMOTE применяется только на обучающих данных!)
predictions = pipeline.predict(X_test)

# Оценить
from sklearn.metrics import classification_report
print(classification_report(y_test, predictions))

Когда использовать какой метод

МетодКогда использоватьПлюсыМинусы
SMOTEНебольшой редкий классСоздаёт реалистичные примерыМожет привести к переобучению
Over-samplingОчень мало данныхПростойДублирование может вызвать переобучение
Under-samplingМного данныхБыстрое обучениеПотеря информации
class_weightЛюбой дисбалансПросто, встроено в модельМожет не помочь с экстремальным дисбалансом
Threshold tuningПостклассификационная коррекцияНе требует переsamplingМожет быть нестабильно

Выводы

  • Никогда не используй accuracy для несбалансированных данных
  • SMOTE — обычно лучший выбор для начала
  • Всегда проверяй различные метрики (precision, recall, F1, ROC-AUC)
  • Комбинируй методы — SMOTE + class_weight часто работает хорошо
  • Не переусложняй — иногда simple class_weight решает проблему
Как работать с несбалансированными данными? | PrepBro