Фундамент
~20 мин

Метрики классификации

Precision, Recall, F1, ROC-AUC, PR-AUC — как оценивать качество моделей.

Метрики классификации — как понять, что модель действительно работает

Загрузка интерактивного виджета...

Ты обучил модель, accuracy = 99%. Хочется праздновать — но подожди. В твоих данных 99% здоровых пациентов и 1% больных. Модель, которая всем говорит «здоров», получает accuracy 99% — и при этом она абсолютно бесполезна. Ни одного больного не нашла.

Это не искусственный пример — дисбаланс классов встречается повсюду: мошеннические транзакции (0.1%), дефекты на производстве (2%), клики по рекламе (1-3%). И accuracy во всех этих случаях врёт: показывает высокое число, скрывая, что модель не справляется с главной задачей.

Вот почему одной метрики мало. Нужен набор инструментов, каждый из которых отвечает на свой вопрос: «Много ли ложных тревог?» (precision), «Не пропускаем ли мы важные случаи?» (recall), «Как модель ведёт себя при разных порогах?» (ROC-AUC). Разберём их все — от фундамента (confusion matrix) до выбора порога под конкретный бизнес.

Confusion Matrix — фундамент всех метрик

Любая метрика классификации начинается с одной таблицы 2×2 — матрицы ошибок (confusion matrix). Она разбивает все предсказания модели на четыре категории по двум осям: «что предсказала модель» и «что было на самом деле».

Матрица ошибок: TP, FP, FN, TN
Четыре ячейки confusion matrix. По строкам — реальный класс, по столбцам — предсказание модели

Аналогия: ты охранник на проходной, ищешь воров среди посетителей. • TP (True Positive) — поймал вора. Модель сказала «положительный», и это правда. • TN (True Negative) — пропустил честного. Модель сказала «отрицательный», и это правда. • FP (False Positive) — задержал невиновного. Ложная тревога. Модель ошиблась, назвав здорового больным. • FN (False Negative) — пропустил вора. Самая опасная ошибка. Модель не заметила больного.

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

from sklearn.metrics import confusion_matrix
import numpy as np

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

cm = confusion_matrix(y_true, y_pred)
tn, fp, fn, tp = cm.ravel()
print(f"TP={tp}, TN={tn}, FP={fp}, FN={fn}")
# TP=4, TN=4, FP=1, FN=1

Accuracy — простая, но коварная метрика

Accuracy — доля правильных ответов среди всех предсказаний

Accuracy отвечает на вопрос: «Какую долю объектов модель классифицировала правильно?». Самая интуитивная метрика — и самая обманчивая при дисбалансе.

Когда accuracy работает: классы сбалансированы (50/50 или хотя бы 70/30), и оба типа ошибок одинаково важны. Пример: определение пола по фото, классификация отзывов на положительные/отрицательные.

Когда accuracy врёт: дисбаланс классов. Вернёмся к примеру: 10 000 пациентов, 9 900 здоровых и 100 больных. Модель-«оптимист», которая всем ставит «здоров», получает accuracy = 9900/10000 = 99%. При этом recall по больным = 0% — она не нашла ни одного.

Правило большого пальца

Если доля минорного класса < 10%, accuracy почти бесполезна. Любая «тупая» модель, предсказывающая мажорный класс, уже получит accuracy > 90%. Переходи на precision, recall, F1 или PR-AUC.

Precision и Recall — два взгляда на ошибки

Precision (точность) — из тех, кого модель назвала положительными, сколько действительно положительных? Recall (полнота) — из всех реальных положительных, сколько модель нашла?

Precision и Recall смотрят на ошибки с разных сторон: • Precision штрафует за ложные срабатывания (FP). Высокая precision = мало ложных тревог. • Recall штрафует за пропуски (FN). Высокий recall = мало пропущенных случаев.

Компромисс: повышаешь порог — точность растёт, полнота падает
Компромисс precision и recall: при повышении порога precision растёт, recall падает

Между ними всегда trade-off через порог (threshold). Модель выдаёт вероятность: P(positive). Если порог 0.5, всё что выше — «положительный». Повышаем порог до 0.9 — модель говорит «положительный» только когда очень уверена → FP падает (precision растёт), но больше случаев пропускает → FN растёт (recall падает). Понижаем порог до 0.1 — наоборот.

Какая метрика важнее — зависит от цены ошибок:

  • Спам-фильтр → precision. Отправить важное письмо в спам (FP) — хуже, чем пропустить спам (FN). Пользователь не простит потерянное письмо.
  • Диагностика рака → recall. Пропустить больного (FN) — потенциально фатально. Лучше отправить на дополнительное обследование лишних 10 человек (FP), чем пропустить 1 больного.
  • Антифрод → баланс. Заблокировать легитимную транзакцию (FP) — потеря клиента. Пропустить мошенника (FN) — финансовые потери. Нужен компромисс.
  • Рекомендации → precision@k. Показать неинтересный контент (FP) — раздражает пользователя. Не показать что-то интересное (FN) — менее критично, есть ещё предложения.
from sklearn.metrics import precision_score, recall_score

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

print(f"Precision: {precision_score(y_true, y_pred):.2f}")  # 0.80
print(f"Recall:    {recall_score(y_true, y_pred):.2f}")     # 0.80

F1-Score и F-beta — баланс precision и recall

Когда нужна одна метрика, объединяющая precision и recall, используют F1-score — их гармоническое среднее.

F1 — гармоническое среднее precision и recall. Выражается и через TP/FP/FN напрямую

Почему гармоническое, а не арифметическое? Гармоническое среднее сильно штрафует за дисбаланс. Пример: precision = 0.9, recall = 0.1. Арифметическое среднее = 0.5 — звучит нормально. Гармоническое = 0.18 — честно показывает, что одна метрика провалена. F1 высокое только когда обе метрики высокие.

Иногда precision и recall неравнозначны. Для этого есть обобщение — F-beta:

β > 1 — recall важнее (F2 для медицины). β < 1 — precision важнее (F0.5 для спам-фильтров)

  • F0.5 — precision вдвое важнее recall. Спам-фильтр: лучше пропустить спам, чем потерять письмо.
  • F1 — precision и recall одинаково важны. Дефолтный выбор, когда нет явных предпочтений.
  • F2 — recall вдвое важнее precision. Медицинская диагностика: лучше лишнее обследование, чем пропущенная болезнь.

ROC-AUC — как модель разделяет классы при всех порогах

Все предыдущие метрики зависят от выбранного порога. Но что если ты хочешь оценить качество модели в целом, не привязываясь к конкретному threshold? Для этого — ROC-кривая и её площадь AUC.

ROC-кривая (Receiver Operating Characteristic) строится так: 1. Модель выдаёт вероятности для каждого объекта. 2. Перебираем порог от 1 до 0 (от самого строгого к самому мягкому). 3. Для каждого порога вычисляем две величины: — TPR (True Positive Rate) = Recall = TP / (TP + FN) — доля пойманных положительных — FPR (False Positive Rate) = FP / (FP + TN) — доля ошибочно «обвинённых» отрицательных 4. Строим график: по оси X — FPR, по оси Y — TPR.

ROC-кривая: TPR vs FPR
ROC-кривая: чем ближе к верхнему левому углу (TPR=1, FPR=0), тем лучше модель

Как читать ROC:Идеальный классификатор — точка (0, 1): TPR = 1, FPR = 0. Кривая проходит через верхний левый угол. AUC = 1.0. • Случайный классификатор — диагональ из (0,0) в (1,1). AUC = 0.5. Модель не лучше монетки. • Реальная модель — кривая между диагональю и идеалом. Чем выше кривая — тем лучше. • AUC < 0.5 — модель хуже случайного угадывания. Обычно значит, что перепутаны классы.

Интуиция за AUC: ROC-AUC = вероятность того, что модель присвоит случайному положительному объекту более высокий скор, чем случайному отрицательному. AUC = 0.85 означает: в 85% случаев модель корректно ранжирует пару «положительный выше отрицательного».

from sklearn.metrics import roc_auc_score, roc_curve
import numpy as np

# Вероятности (не бинарные предсказания!)
y_true  = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0]
y_score = [0.9, 0.2, 0.8, 0.6, 0.3, 0.75, 0.4, 0.1, 0.95, 0.15]

auc = roc_auc_score(y_true, y_score)
print(f"ROC-AUC: {auc:.2f}")  # 0.96

# Для построения кривой:
fpr, tpr, thresholds = roc_curve(y_true, y_score)

PR-AUC — метрика для несбалансированных данных

ROC-AUC — отличная метрика, но у неё есть слабость: на сильно несбалансированных данных она может быть завышена.

Почему? FPR = FP / (FP + TN). Если отрицательных объектов 99 000, а положительных 1 000, даже 100 ложных срабатываний дают FPR = 100/99000 ≈ 0.001 — почти ноль. ROC-кривая «прижимается» к верхнему левому углу, AUC выглядит отлично, хотя 100 ложных тревог — это 10% от положительного класса.

PR-кривая (Precision-Recall curve) строится аналогично ROC, но по осям Precision (Y) и Recall (X). Она не содержит TN — а именно TN при дисбалансе «размывает» FPR.

Precision-Recall кривая
PR-кривая: идеальная точка — (1, 1), случайный классификатор — горизонтальная линия на уровне доли положительного класса

ROC-AUC vs PR-AUC — когда что

Сбалансированные данные (50/50 до 80/20) → ROC-AUC. Оба класса хорошо представлены. • Сильный дисбаланс (95/5 и выше) → PR-AUC. Чувствительнее к ошибкам на редком классе. • Сравнение моделей → используй ту же метрику для обеих. ROC-AUC удобнее для сравнения, т.к. 0.5 = baseline. • Практическое правило: если тебе важен именно положительный класс (фрод, болезнь, клик), PR-AUC информативнее.

Log Loss — оцениваем качество вероятностей

Все предыдущие метрики работают с бинарными предсказаниями или рангами. Но модель выдаёт вероятности — и их качество тоже можно оценить. Если модель говорит «вероятность рака 80%», то из 100 таких пациентов ~80 действительно должны быть больны. Это называется калибровка.

y_i — истинная метка (0 или 1), p_i — предсказанная вероятность положительного класса. Чем ниже — тем лучше

Как работает Log Loss: если объект положительный (y=1) и модель дала p=0.99 — штраф почти нулевой: −log(0.99) ≈ 0.01. Если модель дала p=0.01 — штраф огромный: −log(0.01) ≈ 4.6. Log Loss экспоненциально наказывает за уверенные неправильные ответы.

Когда использовать: когда важны именно вероятности, а не бинарные решения — ранжирование рекламы (bid = f(p·value)), калибровка медицинских моделей, ансамблирование. Идеальный Log Loss = 0 (все вероятности равны 0 или 1 и все верные).

from sklearn.metrics import log_loss

y_true  = [1, 0, 1, 1, 0]
y_prob  = [0.9, 0.1, 0.8, 0.7, 0.2]

print(f"Log Loss: {log_loss(y_true, y_prob):.4f}")  # 0.1998

# Плохая калибровка:
y_prob_bad = [0.6, 0.4, 0.6, 0.55, 0.45]
print(f"Log Loss (плохая): {log_loss(y_true, y_prob_bad):.4f}")  # 0.5765

Выбор порога — где провести границу

Модель выдаёт вероятность P(positive). Чтобы получить бинарное решение, нужен порог (threshold): если P ≥ t → «положительный». Дефолт — 0.5, но это почти всегда не оптимально.

Как выбрать правильный порог:

  • По F1 на валидации. Перебери пороги от 0.01 до 0.99 с шагом 0.01, посчитай F1 для каждого. Выбери максимум. Простой и универсальный способ.
  • По бизнес-метрике. Посчитай стоимость FP и FN. Если FN стоит $10 000 (пропущенный фрод), а FP стоит $10 (проверка транзакции) — оптимальный порог будет низким (ловим всех подозрительных).
  • По точке на ROC/PR-кривой. Ближайшая точка к идеалу (0, 1) на ROC или (1, 1) на PR. Геометрически — нормаль из идеальной точки к кривой.
  • По Youden's J statistic. J = TPR − FPR. Максимум J = оптимальный баланс TPR и FPR. Быстрый способ, не требующий знания стоимостей.
from sklearn.metrics import f1_score
import numpy as np

y_true  = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0]
y_score = [0.9, 0.2, 0.8, 0.6, 0.3, 0.75, 0.4, 0.1, 0.95, 0.15]

# Перебор порогов для максимизации F1
best_t, best_f1 = 0.5, 0
for t in np.arange(0.01, 1.0, 0.01):
    y_pred = [1 if s >= t else 0 for s in y_score]
    f1 = f1_score(y_true, y_pred)
    if f1 > best_f1:
        best_t, best_f1 = t, f1

print(f"Лучший порог: {best_t:.2f}, F1: {best_f1:.2f}")
# Лучший порог: 0.60, F1: 0.91

Частая ошибка

Подбирать порог на тестовом наборе → переобучение на тест. Порог подбирается только на валидации (или через кросс-валидацию), а затем проверяется на тесте.

Multi-class: как считать метрики для нескольких классов

Все метрики выше определены для бинарной классификации. Когда классов больше двух (кошка/собака/птица, тональность позитивная/нейтральная/негативная), метрики нужно агрегировать.

Идея: для каждого класса отдельно считаем precision, recall, F1 (по схеме «этот класс vs остальные»). Получаем K значений. Как свести к одному числу? Три стратегии:

  • Macro — среднее по классам: (F1_class1 + F1_class2 + F1_class3) / 3. Каждый класс одинаково важен, независимо от размера. Малый класс (100 объектов) весит столько же, сколько большой (10 000).
  • Micro — считаем глобальные TP, FP, FN по всем классам, потом вычисляем одну метрику. Эквивалентно accuracy для мультиклассовой задачи. Больше внимания крупным классам.
  • Weighted — среднее, взвешенное по числу объектов в каждом классе. Компромисс: учитывает дисбаланс, но крупные классы доминируют.
from sklearn.metrics import classification_report

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

print(classification_report(y_true, y_pred, target_names=["кот", "собака", "птица"]))
#               precision    recall  f1-score   support
#          кот       0.50      0.67      0.57         3
#       собака       0.50      0.67      0.57         3
#        птица       1.00      0.75      0.86         4
#     accuracy                           0.70        10
#    macro avg       0.67      0.69      0.67        10
# weighted avg       0.72      0.70      0.70        10

Какую агрегацию выбрать

• Все классы одинаково важны (даже редкие) → macro • Важна общая доля правильных ответов → micro (= accuracy) • Дисбаланс есть, но крупные классы важнее → weighted • На собесе: по умолчанию обычно спрашивают macro F1, если не уточнено иное

🎯 На собеседовании

Junior

Что такое precision и recall? Precision — из предсказанных положительных, сколько верных. Recall — из реальных положительных, сколько нашли. • Когда accuracy плохая метрика? При дисбалансе классов. Модель может предсказывать мажорный класс всем и получить высокую accuracy. • Чем F1 отличается от среднего арифметического P и R? F1 — гармоническое среднее, штрафует за дисбаланс. P=0.9, R=0.1 → арифм.=0.5, F1=0.18. • Что показывает ROC-AUC? Вероятность, что модель присвоит положительному объекту более высокий скор, чем отрицательному. 0.5 — рандом, 1.0 — идеал.

Middle

ROC-AUC vs PR-AUC — когда какую? PR-AUC при сильном дисбалансе (>95/5), т.к. ROC-AUC завышен из-за большого TN в FPR. PR-AUC не использует TN. • Как выбрать порог классификации? По F1 на валидации, по бизнес-стоимости FP/FN, по Youden's J = TPR−FPR. Никогда на тесте. • Macro vs micro F1? Macro — среднее по классам (все равноважны), micro — глобальное TP/FP/FN (= accuracy для мультикласса). Macro чувствительнее к малым классам. • Зачем Log Loss, если есть F1? F1 работает с бинарными решениями, Log Loss — с вероятностями. Если важна калибровка (реклама, медицина) — Log Loss.

Senior

Модель даёт AUC=0.95, но в продакшене не работает. Почему? Data leakage при обучении, сдвиг распределения (train/prod), неправильный порог, метрика не отражает бизнес-задачу. • Как подобрать порог, если стоимость ошибок асимметрична? Минимизация expected cost: E[Cost] = C_FP·FP + C_FN·FN. Оптимальный порог t* при котором dCost/dt = 0. На практике — перебор порогов с подсчётом реальных денег. • Почему метрики на валидации и в A/B тесте могут расходиться? Offline метрики — на статическом датасете. Online — с feedback loops, сдвигом данных, user behavior shift. Модель может оптимизировать precision, но ухудшать user engagement. • Калибровка: как проверить и починить? Reliability diagram (calibration curve). Если P(y=1|score=0.8) ≠ 0.8 — нужна рекалибровка: Platt scaling (логистическая регрессия на скорах) или isotonic regression.

Собираем всё вместе

Все метрики классификации растут из одной таблички 2×2 — confusion matrix. Accuracy считает общую долю правильных ответов, но врёт при дисбалансе. Precision и recall смотрят на разные типы ошибок, а F1 объединяет их в одно число. ROC-AUC оценивает модель при всех порогах, PR-AUC делает то же, но честнее при дисбалансе. Log Loss оценивает качество вероятностей. А порог — это бизнес-решение, а не свойство модели.

Если запомнить одну вещь из этой ноды: не бывает «лучшей» метрики — бывает метрика, подходящая под задачу. Сначала пойми цену FP и FN, потом выбирай метрику и порог.

Дальше на роадмапе: выбор модели и валидация научат тебя оценивать метрики честно (кросс-валидация, переобучение), а ранжирование и рекомендации покажут метрики за пределами классификации — NDCG, MRR, MAP.