Какую метрику выбрать для оценки работы бинарного классификатора с распределением 95% положительных и 5% отрицательных классов?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Метрики для дисбалансированной классификации (95%-5%)
Это классическая проблема при работе с редкими событиями: fraud detection, редкие заболевания, техническая неисправность оборудования. Accuracy здесь абсолютно неподходящая метрика, и вот почему.
Почему Accuracy неправильна
from sklearn.metrics import accuracy_score
# Наивный классификатор: всегда предсказывает 1 (положительный класс)
y_true = [1]*95 + [0]*5 # 95% положительных, 5% отрицательных
y_pred = [1]*100 # Всегда 1
accuracy = accuracy_score(y_true, y_pred) # 95%!
print(f'Accuracy наивного классификатора: {accuracy:.1%}')
# Но на самом деле модель бесполезна!
Accuracy оценивает долю правильных предсказаний, но не показывает, что модель совсем не определяет редкий класс. При дисбалансе это критично.
Лучшие метрики для дисбалансированных данных
1. AUC-ROC (Receiver Operating Characteristic) — ОСНОВНАЯ метрика
Это мой первый выбор для дисбалансированных данных. AUC показывает, насколько хорошо модель разделяет классы, независимо от порога решения.
from sklearn.metrics import roc_auc_score, roc_curve
import matplotlib.pyplot as plt
y_proba = model.predict_proba(X_test)[:, 1] # Вероятности положительного класса
auc = roc_auc_score(y_true, y_proba)
print(f'AUC-ROC: {auc:.4f}') # 0.5 = случайный классификатор, 1.0 = идеальный
# ROC кривая показывает trade-off между TPR и FPR
fpr, tpr, thresholds = roc_curve(y_true, y_proba)
plt.plot(fpr, tpr, label=f'AUC = {auc:.3f}')
plt.plot([0, 1], [0, 1], 'k--', label='Random')
plt.xlabel('False Positive Rate (1 - Specificity)')
plt.ylabel('True Positive Rate (Recall)')
plt.legend()
plt.show()
Плюсы:
- Не зависит от порога решения
- Инвариантна к дисбалансу классов
- Интерпретируемая: вероятность того, что модель правильно рангирует случайно выбранный положительный пример выше, чем отрицательный
Минусы:
- Не показывает абсолютные значения ошибок
- Менее интуитивна, чем точность
2. Precision-Recall кривая и F1-score — когда фокус на редком классе
Если мы особенно заботимся о положительном (редком) классе:
from sklearn.metrics import precision_recall_curve, f1_score
# Precision: из предсказанных положительных, сколько правильных
# Recall (Sensitivity): из всех положительных, сколько мы поймали
precision, recall, thresholds = precision_recall_curve(y_true, y_proba)
plt.plot(recall, precision, label='PR curve')
plt.xlabel('Recall (Sensitivity)')
plt.ylabel('Precision')
plt.legend()
plt.show()
# F1-score: гармоническое среднее Precision и Recall
optimal_threshold = 0.3 # Выбираем порог максимизирующий F1
y_pred = (y_proba >= optimal_threshold).astype(int)
f1 = f1_score(y_true, y_pred)
print(f'F1-score: {f1:.4f}')
Когда использовать:
- Fraud detection: неправильный положительный (False Positive) дорогой — берём высокую Precision
- Medical diagnosis: пропустить больного (False Negative) опасно — берём высокий Recall
- Обычно ищем баланс через F1-score
3. PR-AUC (Area Under Precision-Recall Curve) — для экстремального дисбаланса
Если дисбаланс очень сильный (как в вашем случае 95%-5%), PR-AUC предпочтительнее ROC-AUC.
from sklearn.metrics import auc
pr_auc = auc(recall, precision)
print(f'PR-AUC: {pr_auc:.4f}')
# PR-AUC более чувствительна к производительности на меньшинстве
4. Confusion Matrix и производные метрики
Для полного понимания::
from sklearn.metrics import confusion_matrix, classification_report
y_pred = (y_proba >= 0.5).astype(int)
cm = confusion_matrix(y_true, y_pred)
print(cm)
# [[TN, FP],
# [FN, TP]]
TN, FP, FN, TP = cm.ravel()
print(f'True Positive Rate (Recall, Sensitivity): {TP / (TP + FN):.4f}')
print(f'False Positive Rate: {FP / (FP + TN):.4f}')
print(f'Precision (PPV): {TP / (TP + FP):.4f}')
print(f'Specificity: {TN / (TN + FP):.4f}')
print(classification_report(y_true, y_pred))
5. Matthews Correlation Coefficient (MCC) — игнорирует дисбаланс
Одна из лучших метрик для несбалансированных данных:
from sklearn.metrics import matthews_corrcoef
mcc = matthews_corrcoef(y_true, y_pred)
print(f'MCC: {mcc:.4f}') # От -1 до 1, 0 = случайный
# MCC = (TP*TN - FP*FN) / sqrt((TP+FP)(TP+FN)(TN+FP)(TN+FN))
MCC учитывает все четыре компонента матрицы ошибок и не зависит от дисбаланса. Очень хорошая метрика!
6. Cohen's Kappa — согласованность с учётом случайности
from sklearn.metrics import cohen_kappa_score
kappa = cohen_kappa_score(y_true, y_pred)
print(f'Cohen Kappa: {kappa:.4f}')
Практический пример для вашего случая (95%-5%)
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 (
roc_auc_score, pr_auc, matthews_corrcoef, f1_score,
precision_score, recall_score, confusion_matrix
)
# Создаём дисбалансированный датасет
X, y = make_classification(
n_samples=10000,
n_features=20,
n_informative=10,
weights=[0.95, 0.05], # 95%-5% дисбаланс
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# Обучаем модель с балансировкой
model = RandomForestClassifier(
n_estimators=100,
class_weight='balanced', # ВАЖНО для дисбаланса!
random_state=42
)
model.fit(X_train, y_train)
y_proba = model.predict_proba(X_test)[:, 1]
y_pred = model.predict(X_test)
# МЕТРИКИ ДЛЯ ОТЧЁТА
print('=' * 60)
print('МЕТРИКИ ДИСБАЛАНСИРОВАННОЙ КЛАССИФИКАЦИИ')
print('=' * 60)
print(f'\n1. AUC-ROC: {roc_auc_score(y_test, y_proba):.4f}') # ОСНОВНАЯ
print(f'2. PR-AUC: {auc(recall, precision):.4f}') # ДЛЯ ЭКСТРЕМАЛЬНОГО ДИСБАЛАНСА
print(f'3. F1-score: {f1_score(y_test, y_pred):.4f}') # БАЛАНС PRECISION-RECALL
print(f'4. MCC (Matthews): {matthews_corrcoef(y_test, y_pred):.4f}') # УНИВЕРСАЛЬНАЯ')
print(f'5. Precision: {precision_score(y_test, y_pred):.4f}') # ИЗ ПРЕДСК. ПОЛОЖИТ. СКОЛЬКО ПРАВИЛЬНО')
print(f'6. Recall: {recall_score(y_test, y_pred):.4f}') # ИЗ ИСТИННО ПОЛОЖИТ. СКОЛЬКО ПОЙМАЛИ')
print(f'\nMatrix ошибок:')
print(confusion_matrix(y_test, y_pred))
print(f'\nAccuracy (НЕ ИСПОЛЬЗУЙ!): {(y_pred == y_test).mean():.4f}')
Рекомендация для вашей задачи
При распределении 95%-5% я бы использовал:
- AUC-ROC ≥ 0.85 — основная метрика (базовая, хорошая)
- PR-AUC ≥ 0.60 — вторичная (более чувствительна к редкому классу)
- F1-score ≥ 0.60 — для практического применения (выбирайте порог, максимизирующий F1)
- MCC ≥ 0.50 — как дополнительная проверка
Чего НЕ делать:
- Accuracy — совершенно неинформативна при таком дисбалансе
- Полагаться только на одну метрику
- Забывать про порог решения (по умолчанию 0.5 редко оптимален)
Оптимизация порога решения
from sklearn.metrics import f1_score
# Находим оптимальный порог
thresholds = np.arange(0.1, 0.9, 0.01)
f1_scores = []
for threshold in thresholds:
y_pred_thresh = (y_proba >= threshold).astype(int)
f1 = f1_score(y_test, y_pred_thresh)
f1_scores.append(f1)
optimal_threshold = thresholds[np.argmax(f1_scores)]
print(f'Оптимальный порог: {optimal_threshold:.2f}')
y_pred_optimal = (y_proba >= optimal_threshold).astype(int)
print(f'F1-score с оптимальным порогом: {f1_score(y_test, y_pred_optimal):.4f}')
Итоговый совет
Для задачи с дисбалансом 95%-5% думайте так: модель оценивается тем, насколько хорошо она различает редкий класс, а не тем, что она говорит "положительный" в 95% случаев. AUC-ROC + PR-AUC + F1-score — магическая тройка для дисбалансированных задач.