← Назад к вопросам
Как работать с несбалансированными данными?
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 решает проблему