Дисбаланс классов: веса, порог и правильные метрики
Автор: Казачкин Даниил Михайлович · Обновлено
При дисбалансе один класс встречается намного реже другого: дефекты, мошеннические операции или аварии могут составлять доли процента. Модель с высокой accuracy способна полностью игнорировать редкое событие, поэтому проверку начинают с матрицы ошибок и цены решений.
Сначала определите положительный класс
Редкий класс не обязательно положительный в коде. Проверьте значения y, порядок classes_ и смысл каждого исхода. Ошибка в соответствии меток меняет интерпретацию precision, recall и столбца predict_proba.
Затем посчитайте baseline: что произойдёт, если всегда прогнозировать большинство? Его accuracy показывает, насколько мало сообщает общая доля правильных ответов.
Метрики для редкого события
Recall измеряет долю найденных положительных объектов, precision — долю настоящих среди срабатываний. PR-кривая показывает их компромисс при разных порогах. F1 объединяет две величины, но предполагает одинаковую значимость и скрывает конкретное число ошибок.
ROC-AUC полезна для ранжирования, однако большое число true negatives может сделать картину менее наглядной. Показывайте также average precision или PR-AUC, матрицу ошибок и абсолютные количества на рабочем пороге.
Веса классов
Многие алгоритмы принимают class_weight='balanced' и усиливают вклад редкого класса в функцию потерь. Это меняет обучение, но не гарантирует нужного рабочего компромисса.
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_recall_curve
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
X, y = make_classification(
n_samples=3_000,
n_features=12,
n_informative=6,
weights=[0.95, 0.05],
flip_y=0.01,
random_state=42,
)
X_train, X_valid, y_train, y_valid = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
model = make_pipeline(
StandardScaler(),
LogisticRegression(class_weight='balanced', max_iter=2_000),
)
model.fit(X_train, y_train)
positive_label = 1
positive_index = list(model.classes_).index(positive_label)
scores = model.predict_proba(X_valid)[:, positive_index]
precision, recall, thresholds = precision_recall_curve(
y_valid, scores, pos_label=positive_label
)
# Пример требования: найти вариант с recall не ниже 0.90.
candidates = [(p, r, t) for p, r, t in zip(precision, recall, thresholds) if r >= 0.90]
best_precision, best_recall, threshold = max(candidates, key=lambda item: item[0])
print(best_precision, best_recall, threshold)Порог выбирается на validation. После выбора правило фиксируют и один раз проверяют на test вместе с абсолютным числом FP и FN.
Повторная выборка
Oversampling увеличивает представительство редкого класса, undersampling уменьшает большинство. Эти операции выполняются только внутри train-фолда. Если сначала создать копии редких объектов, а затем разделить таблицу, один исходный пример попадёт в обучение и проверку.
Синтетические методы не создают новую реальность: они интерполируют доступные наблюдения и могут усиливать шум. Сравните их с весами, изменением порога и сбором настоящих примеров.
Дисбаланс может меняться после запуска
Доля событий влияет на precision. Модель с тем же recall и false positive rate будет давать другую чистоту срабатываний, если редкий класс стал встречаться реже. Поэтому отслеживайте base rate, объём ручной проверки и качество на размеченных данных во времени.
Для очень редких событий случайный test может содержать слишком мало положительных случаев. Нужны достаточно длинное окно, интервальная оценка и предметно важные стресс-сценарии.
Практика: переведите метрики в очередь
Пусть сервис обрабатывает 100 000 заявок в день, положительный класс составляет 0,5%, recall равен 0,8, а precision — 0,2. Посчитайте найденные события, пропуски и примерный размер очереди проверок. Решите, выдерживает ли её команда, и сформулируйте ограничение для выбора порога.
Что важно запомнить
- Accuracy большинства не измеряет обнаружение редкого события.
- Порог выбирают по цене FP и FN на validation.
- Sampling выполняется только внутри обучающих фолдов.
- Отчёт должен переводить метрики в реальные количества и нагрузку.
Частые вопросы
Нужно ли всегда балансировать классы до 50/50?
Нет. Искусственная доля меняет учебную задачу и оценки вероятности. Веса или sampling выбирают по валидации, а тест и отчёт должны отражать реальное распределение.
Достаточно ли F1 для несбалансированной задачи?
Не всегда. F1 не учитывает TN и не показывает абсолютную нагрузку. Дополните его precision, recall, PR-кривой, матрицей ошибок и продуктовой стоимостью на выбранном пороге.
Почему precision упала без изменения модели?
Она зависит от распространённости положительного класса. Если событие стало реже или изменился поток данных, доля истинных срабатываний среди всех тревог может снизиться при похожих условных характеристиках.
Связанные исследования
- Оцифрованная муха: от карты нейронов к виртуальному поведению — Как карты FlyWire и MaleCNS связаны с моделями мозга и виртуальным телом мухи: результаты работ 2024–2026 годов, ограничения демонстраций и проверяемый пример на Python.
- Загрязнение бенчмарков LLM: как отличать знакомые задания от обобщения — Как проверяют попадание бенчмарка в обучение LLM: исследование ICLR 2024, предпосылки статистического теста и собственный пример с перестановками на Python.
- Кот Шрёдингера против оптических потерь: разбор исследования Physical Review A — Что исследовали Gorshenin, Dyadkin и Chikalkin: обнаружение заданного фазового сдвига, антисжатие, потери и границы преимущества перед гауссовским светом.