Условие
В задаче антифрода positive класс — 0.2% наблюдений. Модель даёт accuracy 99.8%, predict_proba близко к 0. Опишите способы борьбы с дисбалансом и их компромиссы.
Решение
Почему стандартный train ломается
Loss function (cross-entropy) суммирует ошибки по всем примерам. При 0.2% positive вклад positive класса в gradient мал — модель быстро учится предсказывать почти всегда «не fraud».
Подходы
1. Изменить веса классов (class_weight).
Самое простое, работает в логрег, RF, XGBoost, нейронных сетях.
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(class_weight='balanced') # или {0: 1, 1: 500}class_weight='balanced' ≈ n / (n_classes · n_class_samples).
2. Undersampling (Random / Tomek / NearMiss). Уменьшить majority. Быстро, но теряем информацию.
from imblearn.under_sampling import RandomUnderSampler
X_res, y_res = RandomUnderSampler(random_state=42).fit_resample(X, y)3. Oversampling (Random / SMOTE / ADASYN). Дублировать или генерировать новых positives.
from imblearn.over_sampling import SMOTE
X_res, y_res = SMOTE(random_state=42).fit_resample(X, y)SMOTE генерирует синтетические positive интерполяцией ближайших соседей. Опасность: интерполяция бессмысленна для категориальных и для очень малых выборок positive.
4. Threshold tuning. Не менять данные, оставить модель — но сдвинуть decision threshold. У логрега или градиент бустинга порог 0.5 для дисбаланса бессмыслен — выбирайте threshold по F1, PR-AUC или business cost.
5. Anomaly detection / one-class. Для очень редких классов (< 0.1%) — Isolation Forest, One-class SVM, autoencoder reconstruction error.
6. Cost-sensitive learning.
Прямо встроить разные стоимости в loss: cost_FN · y · (1-p) + cost_FP · (1-y) · p.
Сравнение подходов
| Подход | Плюсы | Минусы |
|---|---|---|
| class_weight | дёшево, не меняет данные | не для всех алгоритмов |
| Undersampling | быстро | теряем данные, увеличиваем variance |
| Oversampling | сохраняем данные | риск overfitting на дубликатах |
| SMOTE | синтетическое разнообразие | плохо с категориями, не для маленького positive |
| Threshold | простое, прозрачное | не помогает, если модель не учится разделять |
| Anomaly det. | для экстремального дисбаланса | сложнее настроить |
Правильное применение
- Применяйте resampling ТОЛЬКО к train, не к validation/test. Иначе оценка качества врёт.
- Cross-validation должна быть стратифицированной (
StratifiedKFold), чтобы positive были в каждом fold. - Метрика — не accuracy. PR-AUC, F1, expected cost — релевантнее.
- Холдаут отражает прод-распределение. Если в проде 0.2% positive — на тестовом сете тоже 0.2%.
Тонкости SMOTE
- Generate
k=5синтетических из 5 ближайших соседей по умолчанию. Слишком высокое k → межклассовое размытие. - SMOTE-NC для категориальных + числовых.
- Borderline-SMOTE генерирует только у границы классов.
- ADASYN — больше синтетических там, где модель плохо классифицирует.
Подводные камни
- Resampling на test → завышенная оценка качества.
- «Использовали SMOTE и accuracy выросла». Accuracy теряет смысл при дисбалансе.
- Threshold 0.5 после resampling. Имеет смысл, только если class distribution в проде совпадает с train.
- SMOTE на one-hot encoded категориях — мусорные центры между категориями.
- Игнорировать data leakage. Resampling до train/test split → утечка.
- «Дисбаланс = всегда проблема». Иногда модель сама прекрасно справляется (особенно градиент бустинг с правильно подобранным loss и threshold).
Эталонный ответ
Подходы: class_weight (просто, не меняет данные), undersampling (быстро, теряем), oversampling/SMOTE (сохраняем, риск overfit), threshold tuning (часто достаточно), anomaly detection (для < 0.1%), cost-sensitive (формально). Применять только к train, оценивать на отдельной test с реальным распределением, метрика — PR-AUC / expected cost, не accuracy.