Precision отвечает на вопрос «из тех, кого модель назвала положительными, сколько действительно положительные», а recall — «из всех реально положительных, скольких модель поймала». Это две разные ошибки, и они почти всегда тянут модель в противоположные стороны. F1 сводит их в одно число через гармоническое среднее, а что именно оптимизировать в конкретной задаче, решает не формула, а цена ошибки: пропустить мошеннический платёж дороже, чем лишний раз перепроверить честный. Ниже разберу матрицу ошибок, trade-off через порог, F-beta и связь с ROC-AUC/PR-AUC на живых примерах с платежами и письмами.
Что показывает матрица ошибок?
Матрица ошибок (confusion matrix) — это таблица 2×2, которая раскладывает предсказания бинарного классификатора по четырём корзинам. Возьмём антифрод: модель для каждого платежа выдаёт метку «фрод» (1) или «честный» (0), и мы сравниваем её с реальностью.
TP(true positive) — модель сказала «фрод», и это действительно фрод. Поймали.FP(false positive) — модель сказала «фрод», а платёж честный. Ложная тревога, заблокировали хорошего клиента.FN(false negative) — модель сказала «честный», а это был фрод. Пропуск, деньги ушли.TN(true negative) — модель сказала «честный», и он честный. Всё правильно.
Вся дальнейшая арифметика строится на этих четырёх числах. Их удобно посчитать прямо в 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 = 80 / (80 + 70) = 0.53— чуть больше половины блокировок обоснованы, остальное ложные тревоги.recall = 80 / (80 + 20) = 0.80— поймали 80 мошенников из 100.
Ключевая интуиция: 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 ещё не значит удачно выбранный порог, и наоборот.
С чего начать на практике?
Короткий чеклист, который я держу в голове перед тем, как назвать модель «хорошей»:
- Постройте матрицу ошибок и посмотрите на
FPиFNв абсолютных числах, а не только доли. Именно они превращаются в деньги. - Решите, какая ошибка дороже, и выбирайте precision, recall или F-beta осознанно, а не по привычке к F1.
- Не доверяйте accuracy на несбалансированных данных — она врёт в вашу пользу.
- Подберите порог под бизнес, а не оставляйте 0.5 по умолчанию.
- На редком классе смотрите PR-AUC, а ROC-AUC воспринимайте как вспомогательный сигнал.
Метрики классификации — это не про заучивание четырёх формул, а про умение связать TP/FP/FN/TN с ценой ошибки в конкретном продукте. Если хотите довести навык до автоматизма — собирайте confusion matrix руками в Python-тренажёре, решайте практические задания и разбирайте формулировки на вопросах с реальных собеседований. А теорию по SQL, без которого не собрать данные для обучения модели, удобно подтянуть в справочнике.
Полный доступ ко всем 402 Python-задачам, 412 кейсам и безлимитным AI мок-собеседованиям открывается в Pro — это заметно быстрее, чем собирать подготовку по кускам. Но начать точно можно бесплатно: первые задачи и кейсы доступны без подписки, попробуйте прямо сейчас.