Прямой ответ
Дисбаланс классов — ситуация, когда одного класса существенно больше, чем другого: 1 % мошеннических операций, 3 % оттока, 0.5 % дефектов. Главная проблема не в обучении, а в оценке: accuracy теряет смысл. При 1 % положительных модель, всегда отвечающая «нет», получает 99 % точности и нулевую пользу.
Метрики, которые работают при дисбалансе:
- Precision (точность). Доля верных среди предсказанных положительных. Отвечает на вопрос «сколько из flagged действительно проблемные».
- Recall (полнота). Доля найденных среди всех реальных положительных. Отвечает на вопрос «сколько проблем мы поймали».
- F1. Гармоническое среднее precision и recall; удобна как один ориентир, но скрывает компромисс.
- PR-AUC (average precision). Площадь под кривой precision-recall. При сильном дисбалансе информативнее ROC-AUC, потому что не «раздувается» большим числом истинно отрицательных.
- Confusion matrix. Основа для расчёта стоимости ошибок: пропуск мошенничества и ложная блокировка клиента стоят по-разному.
Компромисс precision/recall управляется порогом решения. Порог по умолчанию 0.5 почти никогда не оптимален: его выбирают по кривой PR и по бизнес-стоимости ошибок.
from sklearn.metrics import precision_recall_curve, average_precision_score
proba = model.predict_proba(X_test)[:, 1]
prec, rec, thr = precision_recall_curve(y_test, proba)
print("PR-AUC:", average_precision_score(y_test, proba))
# выбор порога под требуемую полноту
target_recall = 0.80
idx = next(i for i, r in enumerate(rec) if r >= target_recall)
print("порог:", thr[idx], "precision:", prec[idx])
Методы работы с дисбалансом:
- Взвешивание классов (
class_weight="balanced",scale_pos_weight). Самый простой и обычно достаточный приём. - Сэмплирование. Недо-сэмплирование большинства (риск потери информации) или пере-сэмплирование меньшинства (риск переобучения на дубликатах). Важно: сэмплирование применяется только к обучающим фолдам, никогда к валидации.
- Изменение порога. Часто даёт больший эффект, чем любые ухищрения с данными.
- Выбор правильной метрики для подбора. Оптимизация accuracy при дисбалансе ведёт к бесполезной модели.
Практика: корректный протокол оценки
- Stratified-разбиение. Доли классов сохраняются в каждом фолде — иначе оценка прыгает от запуска к запуску. Как это настроить — в статье про кросс-валидацию.
- Сэмплирование внутри пайплайна. Если используете пере-сэмплирование, оно должно быть шагом
Pipeline, иначе возникает утечка. - Оценивайте на несбалансированной валидации. Валидационный набор должен отражать реальное распределение классов, иначе метрики не перенесутся в прод.
- Смотрите на абсолютные числа. При 200 положительных случаях разница recall 0.78 и 0.82 — это 8 случаев; статистически она может быть незначима.
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.model_selection import StratifiedKFold, cross_validate
pipe = ImbPipeline([
("smote", SMOTE(random_state=42)),
("clf", HistGradientBoostingClassifier(random_state=42)),
])
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
res = cross_validate(pipe, X, y, cv=cv, scoring=["average_precision", "recall", "precision"])
Рекомендации:
- Начинайте со взвешивания, а не с SMOTE. Взвешивание проще, устойчивее и не создаёт синтетических объектов.
- Фиксируйте порог как часть модели. Смена порога меняет поведение сервиса так же сильно, как смена алгоритма.
- Согласуйте стоимость ошибок с бизнесом. «Что хуже: пропустить мошенничество или заблокировать добропорядочного клиента» — ответ определяет рабочую точку на кривой PR.
- Для задач без разметки используйте другой подход. Если редких событий нельзя разметить, применяют методы поиска аномалий — см. изолирующий лес.
Практический сценарий: модель с accuracy 99 % пропустила почти все случаи
Контекст: модель выявления проблемных поставок; положительный класс — 1.4 %. Отчёт показал accuracy 99.1 %, модель выкатили.
- Что произошло. Модель почти всегда предсказывала отрицательный класс. Recall оказался 0.09: из 140 проблемных поставок найдено 13.
- Диагностика. Подбор параметров шёл по accuracy, поэтому оптимизация вела к «всегда отрицательный» решению. Отчёт по precision/recall не смотрели.
- Исправление. (а) Метрика подбора —
average_precision; (б) включено взвешивание классов; (в) порог выбран под recall 0.75 при приемлемом precision; (г) отчётность переведена на confusion matrix и PR-кривую. - Результат. Recall 0.78, precision 0.31. При ручной проверке flagged-поставок нагрузка на команду выросла незначительно, а большинство проблемных случаев стало выявляться.
- Профилактика. В шаблон отчёта добавлены обязательные метрики precision, recall, PR-AUC и confusion matrix; accuracy исключена из критериев выбора модели.
Вывод: при дисбалансе метрика выбора определяет результат сильнее, чем алгоритм. Если оптимизировать не то, модель честно оптимизирует бесполезное поведение.
Как оценивать стабильность результата. При малом числе положительных случаев метрики прыгают между запусками. Полезно считать доверительный интервал через бутстреп по валидационным предсказаниям: если интервалы двух моделей пересекаются, выбирать стоит по простоте и стоимости эксплуатации, а не по третьей цифре после запятой.
FAQ
Почему ROC-AUC может выглядеть хорошо при бесполезной модели? ROC-AUC учитывает истинно отрицательные случаи, которых при дисбалансе очень много. Небольшое число ложных срабатываний почти не влияет на кривую ROC, но сильно влияет на precision. PR-AUC в таких задачах честнее.
Помогает ли просто удалить часть отрицательных примеров? Иногда да: при миллионах отрицательных и тысячах положительных сокращение большинства ускоряет обучение без потери качества. Но оценивать модель нужно на исходном распределении, иначе метрики будут завышены.
Что делать, если положительных случаев совсем мало (десятки)? Оценка становится ненадёжной: доверительные интервалы широкие. Практика — упростить модель, усилить регуляризацию и опираться на экспертные правила вместе с моделью, а не вместо них.
Как выбрать порог, если стоимость ошибок неизвестна? Ориентируйтесь на пропускную способность процесса: если команда может разобрать 100 случаев в день, порог выбирают так, чтобы flagged было не больше. Это превращает выбор порога в управляемое ограничение, а не в угадывание.
Источники: scikit-learn docs (model evaluation, average precision), Wikipedia (precision & recall) — ссылки в шапке.