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.667recall_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, разобранным выше.
