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

Что делал при сильной дисбалансе классов?

2.3 Middle🔥 171 комментариев
#Машинное обучение

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

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

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

Проблема дисбаланса

Дисбаланс классов — когда количество примеров одного класса намного больше другого. Модель предсказывает всегда класс большинства и получает высокую accuracy, но не работает на меньшинстве.

Решения (от простых к сложным)

1. Выбор правильной метрики (Самое важное!)

from sklearn.metrics import precision, recall, f1_score, roc_auc_score

# НЕПРАВИЛЬНО для дисбаланса
accuracy = (TP + TN) / (TP + TN + FP + FN)  # 99% даже если не работает!

# ПРАВИЛЬНО для дисбаланса
precision = TP / (TP + FP)
recall = TP / (TP + FN)
f1 = 2 * (precision * recall) / (precision + recall)
roc_auc = roc_auc_score(y_true, y_pred_proba)

scores = cross_validate(
    model, X, y,
    scoring=['precision', 'recall', 'f1', 'roc_auc'],
    cv=5
)

2. Переуменьшение/передискретизация (SMOTE)

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

# SMOTE: синтетическое увеличение меньшинства
smote = SMOTE(sampling_strategy=0.5)
X_smote, y_smote = smote.fit_resample(X_train, y_train)

# ВАЖНО: Применяй только на train, не на test!
pipeline = Pipeline([
    ('smote', SMOTE(sampling_strategy=0.5)),
    ('classifier', LogisticRegression())
])

pipeline.fit(X_train, y_train)  # SMOTE на train
y_pred = pipeline.predict(X_test)  # На test не применяется

3. Регулировка весов классов

from sklearn.linear_model import LogisticRegression

# Способ 1: Вручную
weights = {0: 1, 1: 10}  # Класс 1 в 10 раз важнее
model = LogisticRegression(class_weight=weights)

# Способ 2: Автоматически (balanced)
model = LogisticRegression(class_weight='balanced')

# Способ 3: XGBoost с scale_pos_weight
ratio = (y == 0).sum() / (y == 1).sum()
model = xgb.XGBClassifier(scale_pos_weight=ratio)

4. Пороговая оптимизация

from sklearn.metrics import precision_recall_curve

y_pred_proba = model.predict_proba(X_test)[:, 1]
precision, recall, thresholds = precision_recall_curve(y_test, y_pred_proba)

f1_scores = 2 * (precision * recall) / (precision + recall + 1e-10)
optimal_idx = np.argmax(f1_scores)
optimal_threshold = thresholds[optimal_idx]

y_pred_optimized = (y_pred_proba >= optimal_threshold).astype(int)

5. Ensemble методы

from sklearn.ensemble import BaggingClassifier
import xgboost as xgb

# Bagging
bagging = BaggingClassifier(n_estimators=100)

# XGBoost (хорошо с дисбалансом)
xgb_model = xgb.XGBClassifier(
    scale_pos_weight=10,
    eval_metric='logloss'
)

6. Для Deep Learning

import tensorflow as tf
from sklearn.utils.class_weight import compute_class_weight

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

model.fit(
    X_train, y_train,
    class_weight=class_weight_dict,
    epochs=50
)

Моя стратегия на практике

  1. Выбираю правильные метрики первым делом — F1, ROC-AUC, PR-AUC
  2. Пробую SMOTE + класс-веса — обычно хорошо работает
  3. Если не помогает → пробую ансамбли (XGBoost, Random Forest)
  4. Проверяю recall vs precision — выбираю threshold по business требованиям
    • Высокий recall: найти максимум positives (fraud, болезни)
    • Высокий precision: быть уверенным в positives (рекомендации)
from imblearn.pipeline import Pipeline
from imblearn.over_sampling import SMOTE
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('smote', SMOTE(sampling_strategy=0.5)),
    ('classifier', LogisticRegression(class_weight='balanced'))
])

scores = cross_validate(
    pipeline, X, y,
    scoring=['f1', 'roc_auc', 'precision', 'recall'],
    cv=5
)

print(f"F1: {scores['test_f1'].mean():.3f}")
print(f"ROC-AUC: {scores['test_roc_auc'].mean():.3f}")