Как выбрать threshold для бинарной классификации?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как выбрать threshold для бинарной классификации?
Threshold - это критическое значение вероятности, при котором мы принимаем решение об отнесении объекта к положительному классу. По умолчанию используется 0.5, но это не всегда оптимально. Выбор threshold - ключевой шаг для балансировки метрик точности и полноты.
Основные метрики для выбора threshold
Precision и Recall определяют, как изменяется качество модели при разных значениях threshold:
from sklearn.metrics import precision_recall_curve, roc_curve, auc
import matplotlib.pyplot as plt
import numpy as np
# Получаем предсказания вероятностей
y_pred_proba = model.predict_proba(X_test)[:, 1]
y_true = y_test
# Вычисляем Precision и Recall для разных threshold
precision, recall, thresholds = precision_recall_curve(y_true, y_pred_proba)
roc_fpr, roc_tpr, roc_thresholds = roc_curve(y_true, y_pred_proba)
# Визуализируем кривые
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(recall, precision, marker=".")
plt.xlabel("Recall")
plt.ylabel("Precision")
plt.title("Precision-Recall Curve")
plt.subplot(1, 2, 2)
plt.plot(roc_fpr, roc_tpr, label=f"AUC = {auc(roc_fpr, roc_tpr):.3f}")
plt.plot([0, 1], [0, 1], "k--")
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.legend()
plt.title("ROC Curve")
plt.show()
Методы выбора оптимального threshold
1. F1-Score максимизация
Lучше всего использовать, когда классы примерно сбалансированы и нужен trade-off между Precision и Recall.
from sklearn.metrics import f1_score
f1_scores = []
for threshold in thresholds:
y_pred = (y_pred_proba >= threshold).astype(int)
f1_scores.append(f1_score(y_true, y_pred))
optimal_threshold = thresholds[np.argmax(f1_scores)]
print(f"Оптимальный threshold (F1): {optimal_threshold:.3f}")
2. ROC-AUC и Youden Index
Максимизирует (TPR - FPR), хороший выбор для сбалансированных задач.
youden_j = roc_tpr - roc_fpr
optimal_idx = np.argmax(youden_j)
optimal_threshold = roc_thresholds[optimal_idx]
print(f"Оптимальный threshold (Youden): {optimal_threshold:.3f}")
3. Матрица потерь (Cost Matrix)
Если ошибки имеют разную стоимость (False Positive vs False Negative), выбираем threshold, минимизирующий общие потери.
# Пример: FN дороже в 3 раза, чем FP
cost_fn = 3
cost_fp = 1
total_costs = []
for threshold in thresholds:
y_pred = (y_pred_proba >= threshold).astype(int)
tn = np.sum((y_pred == 0) & (y_true == 0))
fp = np.sum((y_pred == 1) & (y_true == 0))
fn = np.sum((y_pred == 0) & (y_true == 1))
tp = np.sum((y_pred == 1) & (y_true == 1))
cost = cost_fp * fp + cost_fn * fn
total_costs.append(cost)
optimal_threshold = thresholds[np.argmin(total_costs)]
print(f"Оптимальный threshold (Cost Matrix): {optimal_threshold:.3f}")
Практический выбор threshold
Для несбалансированных данных (например, fraud detection): выбирайте threshold, который максимизирует Recall для редкого класса при приемлемом Precision.
# Выбираем threshold с Recall >= 0.9 и максимальным Precision
min_recall = 0.9
for precision_val, recall_val, threshold in zip(precision, recall, thresholds):
if recall_val >= min_recall:
print(f"Threshold: {threshold:.3f}, Precision: {precision_val:.3f}, Recall: {recall_val:.3f}")
break
Рекомендации
- Всегда анализируйте бизнес-требования: что важнее - поймать все положительные (Recall) или быть уверенным в предсказании (Precision)?
- Используйте cross-validation для выбора threshold на отдельной валидационной выборке, не на test.
- Помните о дисбалансе классов: для редких классов (fraud, болезни) нужен более низкий threshold.
- Мониторьте дрейф распределения: threshold может стать неоптимальным со временем в production.
Выбор threshold - это балансировка между потерями типа I и II ошибок с учетом бизнес-контекста задачи.