MLметрикиклассификацияprecisionrecall

Precision, Recall и F1: метрики классификации без магии

2026-07-11 12 мин

Precision отвечает на вопрос «из тех, кого модель назвала положительными, сколько действительно положительные», а recall — «из всех реально положительных, скольких модель поймала». Это две разные ошибки, и они почти всегда тянут модель в противоположные стороны. F1 сводит их в одно число через гармоническое среднее, а что именно оптимизировать в конкретной задаче, решает не формула, а цена ошибки: пропустить мошеннический платёж дороже, чем лишний раз перепроверить честный. Ниже разберу матрицу ошибок, trade-off через порог, F-beta и связь с ROC-AUC/PR-AUC на живых примерах с платежами и письмами.

Что показывает матрица ошибок?

Матрица ошибок (confusion matrix) — это таблица 2×2, которая раскладывает предсказания бинарного классификатора по четырём корзинам. Возьмём антифрод: модель для каждого платежа выдаёт метку «фрод» (1) или «честный» (0), и мы сравниваем её с реальностью.

Вся дальнейшая арифметика строится на этих четырёх числах. Их удобно посчитать прямо в SQL, если у вас есть таблица с истинными метками и предсказаниями:

select
  sum(case when y_true = 1 and y_pred = 1 then 1 else 0 end) as tp,
  sum(case when y_true = 0 and y_pred = 1 then 1 else 0 end) as fp,
  sum(case when y_true = 1 and y_pred = 0 then 1 else 0 end) as fn,
  sum(case when y_true = 0 and y_pred = 0 then 1 else 0 end) as tn
from payment_predictions;

Обратите внимание: сама по себе доля правильных ответов (accuracy = (TP + TN) / всё) в антифроде почти бесполезна. Если фрод составляет 1% платежей, модель, которая всех считает честными, даст 99% accuracy и ноль пойманных мошенников. Именно поэтому смотрят на precision и recall по отдельности. Потренироваться собирать такие агрегаты можно в SQL-тренажёре.

Чем precision отличается от recall?

Формулы короткие. precision = TP / (TP + FP) — доля правильных срабатываний среди всех, кого модель пометила положительными. recall = TP / (TP + FN) — доля пойманных среди всех, кто реально положительный.

Разберём на числах. Пусть за день прошло 10 000 платежей, из них 100 действительно мошеннические. Модель пометила «фродом» 150 транзакций, и из этих 150 реально фродовыми оказались 80. Тогда TP = 80, FP = 70 (заблокировали честных), FN = 20 (пропустили фрод).

Ключевая интуиция: precision наказывает вас за ложные срабатывания (FP), recall — за пропуски (FN). Знаменатели разные, поэтому одну метрику можно улучшать за счёт другой почти бесконечно. Модель, которая блокирует вообще все платежи, имеет recall = 1.0 (не пропустила ни одного фрода), но precision у неё стремится к доле фрода в потоке, то есть к 1%. И наоборот: если блокировать только те платежи, где модель уверена на 99.9%, precision будет высокой, а recall провалится, потому что большая часть фрода пройдёт мимо.

Почему нельзя одновременно максимизировать precision и recall?

Потому что обе метрики зависят от одного и того же порога, а он двигает их в разные стороны. Модель классификации почти никогда не выдаёт сразу 0 или 1 — она выдаёт вероятность (score) от 0 до 1. Метка «положительный» получается сравнением с порогом: y_pred = score >= threshold.

Понизили порог (например, с 0.5 до 0.3) — модель начинает помечать положительными больше объектов. Ловит больше настоящего фрода (recall растёт), но и ложных тревог становится больше (precision падает). Подняли порог до 0.7 — срабатываний меньше, но каждое надёжнее: precision вверх, recall вниз. Это фундаментальный компромисс, а не дефект конкретной модели.

import numpy as np

# scores — вероятности от модели, y_true — истинные метки
for threshold in [0.3, 0.5, 0.7, 0.9]:
    y_pred = (scores >= threshold).astype(int)
    tp = np.sum((y_true == 1) & (y_pred == 1))
    fp = np.sum((y_true == 0) & (y_pred == 1))
    fn = np.sum((y_true == 1) & (y_pred == 0))
    precision = tp / (tp + fp) if (tp + fp) else 0
    recall = tp / (tp + fn) if (tp + fn) else 0
    print(f"thr={threshold}: precision={precision:.2f}, recall={recall:.2f}")

Прогнав такой цикл, вы увидите, как с ростом порога precision ползёт вверх, а recall — вниз. Поэтому порог — это не «настройка по умолчанию 0.5», а бизнес-решение. Его выбирают под задачу: сколько ложных блокировок готова терпеть поддержка и сколько пропущенного фрода готов принять финдиректор. Поиграть с порогом на реальном датасете удобно в Python-тренажёре.

Что такое F1 и зачем нужно гармоническое среднее?

F1 — это способ выразить precision и recall одним числом, чтобы сравнивать модели и подбирать порог без ручного жонглирования двумя метриками. Формула: F1 = 2 * precision * recall / (precision + recall). Это гармоническое среднее двух величин.

Почему именно гармоническое, а не обычное арифметическое? Потому что арифметическое легко обмануть. Возьмём модель с precision = 0.9 и recall = 0.1 (уверенно блокирует пару очевидных мошенников, но пропускает почти всех). Арифметическое среднее даст (0.9 + 0.1) / 2 = 0.5 — вроде бы приличная цифра. А F1 = 2 * 0.9 * 0.1 / (0.9 + 0.1) = 0.18. Гармоническое среднее тянется к меньшему из чисел и жёстко наказывает перекос. Высокий F1 достижим только тогда, когда и precision, и recall одновременно неплохие.

Считать вручную ничего не нужно, всё есть в готовом виде:

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

print(confusion_matrix(y_true, y_pred))     # [[TN, FP], [FN, TP]]
print(precision_score(y_true, y_pred))
print(recall_score(y_true, y_pred))
print(f1_score(y_true, y_pred))

Важная оговорка про многоклассовую задачу: там F1 усредняют, и способ усреднения меняет смысл. macro считает F1 по каждому классу и берёт простое среднее (все классы равны, редкие важны так же, как частые). micro собирает общие TP/FP/FN по всем классам сразу (доминируют частые классы). weighted взвешивает по размеру класса. На несбалансированных данных macro и micro могут отличаться в разы, и молча брать дефолт — распространённая ошибка на собеседовании. Разобрать такие развилки помогают вопросы с интервью.

Когда брать F-beta вместо F1?

F1 неявно считает precision и recall одинаково важными. Но в жизни это редко так, и тогда берут обобщение — F-beta: F_beta = (1 + beta^2) * precision * recall / (beta^2 * precision + recall).

Параметр beta задаёт, во сколько раз recall важнее precision. При beta = 1 получается обычный F1. При beta = 2 recall весит примерно вдвое больше — берут, когда пропуск дороже ложной тревоги (медицина, антифрод). При beta = 0.5 больше веса у precision — когда ложное срабатывание раздражает пользователя сильнее, чем пропуск (рекомендации, спам-фильтр).

from sklearn.metrics import fbeta_score

fbeta_score(y_true, y_pred, beta=2.0)   # recall в приоритете
fbeta_score(y_true, y_pred, beta=0.5)   # precision в приоритете

Практический совет: не выбирайте beta «на глаз». Сформулируйте её через деньги. Если пропущенный фрод стоит в среднем 5000 рублей, а ручной разбор ложной блокировки — 50 рублей на оператора поддержки, то ошибки различаются на два порядка, и beta должна это отражать. F-beta — это способ зашить бизнес-стоимость ошибок прямо в метрику.

Что важнее — precision или recall?

Зависит от того, какая ошибка дороже. Правило простое: дорогой FP — оптимизируйте precision, дорогой FN — оптимизируйте recall.

Спам-фильтр — классика precision. Здесь FP означает, что важное письмо от клиента улетело в спам и человек его не увидел. Это очень дорого: потерянная сделка, пропущенный дедлайн. А FN (одно спам-письмо просочилось во «Входящие») — мелкое раздражение, пользователь удалит его за секунду. Поэтому спам-фильтр настраивают на высокий precision: лучше пропустить немного спама, чем похоронить настоящее письмо.

Диагностика болезни и антифрод — территория recall. Здесь FN (не заметили заболевание, пропустили мошенническую операцию) катастрофичен, а FP (перепроверили здорового человека, попросили клиента подтвердить платёж по SMS) — терпимая цена. Модель настраивают так, чтобы поймать почти всех положительных, соглашаясь на поток ложных тревог, который потом отфильтрует человек или второй, более точный этап проверки.

Отсюда типичный продуктовый паттерн — каскад: первый этап с высоким recall грубо отсеивает подозрительное (лучше перебдеть), второй этап с высоким precision аккуратно принимает финальное решение. Такие компромиссы постоянно всплывают в продуктовых кейсах, и на собеседовании ждут не заученную формулу, а рассуждение про цену ошибки.

Как precision и recall связаны с ROC-AUC и PR-AUC?

Precision и recall считаются при одном фиксированном пороге. Но модель — это не один порог, а целое семейство: для каждого threshold от 0 до 1 получается своя пара метрик. ROC- и PR-кривые как раз описывают всё это семейство сразу, а AUC (площадь под кривой) сворачивает его в одно число, не зависящее от выбора порога.

ROC-кривая строится в координатах TPR против FPR. TPR (true positive rate) — это ровно recall, FPR = FP / (FP + TN) — доля ложных тревог среди всех отрицательных. ROC-AUC отвечает на вопрос: если взять случайный положительный и случайный отрицательный объект, какова вероятность, что модель даст положительному более высокий score. 0.5 — случайное угадывание, 1.0 — идеал.

У ROC-AUC есть ловушка на несбалансированных данных. Знаменатель FPR — это FP + TN, а на сильном дисбалансе TN огромен (честных платежей в сотни раз больше, чем фрода). Поэтому даже большое число ложных тревог почти не двигает FPR, и ROC-AUC выглядит обманчиво красиво. PR-кривая (precision против recall) этой болезнью не страдает, потому что в её метриках TN вообще не участвует. Практическое правило: на редком положительном классе (фрод, отклик на рассылку, дефолт по кредиту) ориентируйтесь в первую очередь на PR-AUC, а ROC-AUC держите как второй, менее чувствительный ориентир.

from sklearn.metrics import roc_auc_score, average_precision_score

roc_auc_score(y_true, scores)            # ROC-AUC
average_precision_score(y_true, scores)  # PR-AUC (усреднённый precision)

И порог, и AUC — это про разные вопросы. AUC говорит, насколько хорошо модель в принципе разделяет классы (сравнение моделей между собой). Порог и посчитанные при нём precision/recall/F1 — про то, как вы будете принимать решения в продакшене. Хорошая модель по AUC ещё не значит удачно выбранный порог, и наоборот.

С чего начать на практике?

Короткий чеклист, который я держу в голове перед тем, как назвать модель «хорошей»:

Метрики классификации — это не про заучивание четырёх формул, а про умение связать TP/FP/FN/TN с ценой ошибки в конкретном продукте. Если хотите довести навык до автоматизма — собирайте confusion matrix руками в Python-тренажёре, решайте практические задания и разбирайте формулировки на вопросах с реальных собеседований. А теорию по SQL, без которого не собрать данные для обучения модели, удобно подтянуть в справочнике.

Полный доступ ко всем 402 Python-задачам, 412 кейсам и безлимитным AI мок-собеседованиям открывается в Pro — это заметно быстрее, чем собирать подготовку по кускам. Но начать точно можно бесплатно: первые задачи и кейсы доступны без подписки, попробуйте прямо сейчас.

Закрепи Python на задачах
402 Python-задачи через pandas/numpy/scipy — попробуй бесплатно.
Python-тренажёр →