Качество классификации и второй проект

Precision, recall и F1

Содержание курса

Вызов precision_score, recall_score, f1_score с явным pos_label

Используем данные из задания: y_true = [0, 0, 1, 1, 0, 1, 0, 1, 1, 0], y_pred = [0, 1, 1, 0, 0, 1, 0, 1, 0, 0].

Сначала построим матрицу и вручную извлечём TP, FP, FN — именно так, как делали в уроке 20:

from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score

y_true = [0, 0, 1, 1, 0, 1, 0, 1, 1, 0]
y_pred = [0, 1, 1, 0, 0, 1, 0, 1, 0, 0]

cm = confusion_matrix(y_true, y_pred, labels=[0, 1])
tn, fp, fn, tp = cm.ravel()  # строки: реальный 0, реальный 1
print(f"TN={tn}, FP={fp}, FN={fn}, TP={tp}")
# TN=4, FP=1, FN=2, TP=3

# Ручной расчёт
prec_manual = tp / (tp + fp)   # 3 / 4 = 0.75
rec_manual  = tp / (tp + fn)   # 3 / 5 = 0.60
f1_manual   = 2 * prec_manual * rec_manual / (prec_manual + rec_manual)  # 0.6667

print(f"Ручной: precision={prec_manual:.4f}, recall={rec_manual:.4f}, F1={f1_manual:.4f}")

# Через sklearn
p  = precision_score(y_true, y_pred, pos_label=1)
r  = recall_score(y_true, y_pred, pos_label=1)
f1 = f1_score(y_true, y_pred, pos_label=1)

print(f"sklearn: precision={p:.4f}, recall={r:.4f}, F1={f1:.4f}")

Обе строки вывода дадут одинаковые числа: precision ≈ 0.75, recall = 0.60, F1 ≈ 0.667. Это подтверждает, что sklearn считает ровно по тем формулам, которые мы разобрали.

Теперь о параметре pos_label. Его нужно задавать равным реальной метке положительного класса в вашей задаче. В текущем примере метки — 0 и 1, и положительный класс — 1, поэтому pos_label=1. Если бы метки были 'spam'/'ham', нужно было бы писать pos_label='spam'; если 2/3 — pos_label=3. Явная передача pos_label фиксирует смысл и делает код читаемым независимо от контекста.

Что произойдёт с pos_label=0? Тогда «положительным» становится класс 0, и функции пересчитывают метрики относительно него. Положительные объекты класса 0 — это те, кого матрица хранит как TN (теперь они «TP»), а «пропуски» и «ложные тревоги» меняют роли. На наших данных (TN=4, FP=1, FN=2, TP=3):

  • precision_score(y_true, y_pred, pos_label=0) = 4 / (4 + 2) ≈ 0.667
  • recall_score(y_true, y_pred, pos_label=0) = 4 / (4 + 1) = 0.800

Это не просто «числа поменялись местами» — это другие значения с другим смыслом. При несбалансированных данных разница между pos_label=0 и pos_label=1 может быть значительной и легко остаться незамеченной, если не указывать pos_label явно.

Порядок аргументов — отдельный источник ошибки: первым всегда идёт y_true, вторым y_pred. Если перепутать, sklearn не выдаст исключения. При этом бинарный F1 формально не изменится — он симметричен к перестановке меток и считается как 2·TP / (2·TP + FP + FN), где роли FP и FN просто поменяются местами. Именно из-за этой симметрии ошибка особенно коварна: F1 остаётся прежним, тогда как precision и recall меняются местами, и ориентироваться только на F1-цифру как на индикатор правильности вызова недостаточно.

Чтобы убедиться в корректном порядке, сверяйте значения precision и recall с ручным расчётом по формулам precision и recall, разобранным выше.