Урок курса
Precision, recall и F1
Машинное обучение с нуля на Python: первая модельВ прошлом уроке вы извлекли из матрицы ошибок четыре числа — TP, FP, TN, FN — и научились правильно раскладывать confusion_matrix по индексам. Теперь эти четыре числа перестают быть просто ячейками: из них собираются метрики, которые говорят о качестве модели на языке конкретных инженерных компромиссов.
TP, FP, FN как знаменатели precision и recall
Матрица ошибок хранит четыре числа, но accuracy сводит их к одному отношению: (TP + TN) / (TP + TN + FP + FN) — доля верных предсказаний среди всех. Precision и recall смотрят на более узкие срезы — и каждый срез отвечает ровно на один вопрос.
Precision спрашивает: «Из всего, что модель назвала положительным, сколько действительно положительных?» Множество «всего названного положительным» — это TP плюс FP. Отсюда:
Precision = TP / (TP + FP)
Знаменатель — вертикальный срез по предсказанным положительным (столбец 1 в матрице). Ложные тревоги (FP) тянут его вниз, потому что они увеличивают знаменатель без изменения числителя.
Recall спрашивает другое: «Из всех реальных положительных сколько модель нашла?» Теперь берём все реальные положительные — это TP плюс FN (те, кого модель пропустила):
Recall = TP / (TP + FN)
Знаменатель — горизонтальный срез по реальным положительным (строка 1 в матрице). Пропуски (FN) снижают recall: каждый пропущенный положительный объект увеличивает знаменатель, не трогая числитель.
Заметьте, что TN не входит ни в одну из этих формул. Это значит: если к данным добавить только новые истинно отрицательные объекты, не меняя TP и FP, ни precision, ни recall не изменятся — TN просто не участвует в расчёте. Однако это не означает устойчивости к любым изменениям состава данных: если вместе с новыми отрицательными объектами растёт и число FP (модель ошибочно относит часть из них к положительному классу), precision упадёт, несмотря на неизменный TP.
Чтобы связать формулы с конкретными ячейками матрицы: при вызове confusion_matrix(y_true, y_pred, labels=[0, 1]) строки соответствуют реальным классам, столбцы — предсказанным. Тогда:
cm[1, 1]= TPcm[0, 1]= FPcm[1, 0]= FN
Precision берёт столбец 1 (предсказано «1»): cm[1,1] / (cm[1,1] + cm[0,1]). Recall берёт строку 1 (реально «1»): cm[1,1] / (cm[1,1] + cm[1,0]).
Из формул напрямую следует, как менять поведение модели при прочих равных: если зафиксировать TP и снизить только FP — precision вырастет; если зафиксировать TP и снизить только FN — вырастет recall. Это и есть инженерный компромисс между двумя метриками: FP «стоит» precision, FN «стоит» recall.
Однако на практике прочие равные не сохраняются: при сдвиге порога классификации вместе с FP меняется и TP, поэтому precision может как вырасти, так и упасть. Предсказать направление изменений без пересчёта нельзя — нужно смотреть на конкретные числа. Взаимозависимость precision и recall при изменении порога разбирается подробнее в следующем уроке.
F1-score: гармоническое среднее и чувствительность к дисбалансу метрик
Precision и recall нередко движутся в противоположных направлениях: модель, которая предсказывает «1» очень часто, как правило, охватывает больше реальных положительных (recall растёт), но при этом может генерировать много ложных тревог (precision падает). Осторожная модель, срабатывающая редко, может давать меньше ложных тревог — но пропускать больше реальных положительных. Это типичный сценарий, а не гарантированное свойство: фактическое соотношение precision и recall зависит от состава данных и качества модели, и его нужно измерять, а не предсказывать интуитивно.
Нужна одна цифра, которая штрафует оба крайних случая — и провал precision, и провал recall.
F1 — это гармоническое среднее двух метрик:
F1 = 2 * (precision * recall) / (precision + recall)
Почему именно гармоническое, а не арифметическое? Арифметическое среднее (p + r) / 2 нечувствительно к провалу одной из составляющих. Например, precision = 0.9, recall = 0.1 дают арифметическое среднее 0.5 — звучит терпимо. F1 при тех же числах:
F1 = 2 * 0.9 * 0.1 / (0.9 + 0.1) = 0.18 / 1.0 = 0.18
0.18 против 0.5 — разница почти в три раза. Гармоническое среднее всегда не превышает арифметического, и разрыв тем больше, чем сильнее расходятся слагаемые. Это математическое свойство, а не дизайнерское решение.
Граничные случаи:
- F1 = 1 достигается тогда и только тогда, когда precision = 1 и recall = 1 одновременно, то есть TP = все реальные положительные и FP = FN = 0.
- F1 = 0 наступает, если хотя бы одна из двух метрик равна нулю — модель либо ни разу не предсказала «1» (TP + FP = 0, precision неопределена и считается 0), либо не нашла ни одного реального положительного (TP = 0, recall = 0).
Ещё один числовой пример для интуиции: precision = 0.6, recall = 0.6. Арифметическое среднее = 0.6, F1 тоже = 0.6. Как только метрики расходятся — precision = 0.8, recall = 0.4 — арифметическое среднее остаётся 0.6, а F1 падает до 2 * 0.8 * 0.4 / 1.2 ≈ 0.533. 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, разобранным выше.
Попробуйте решить
Две проверки точности предупреждений
В журнале доставок есть реальные метки задержки и ответы модели. Ложная тревога и пропущенная задержка влияют на разные показатели качества: долю оправданных предупреждений и долю найденных задержек. Подготовьте отчёт, в котором эти показатели можно сверить с матрицей ошибок.
Что уже дано
В видимом коде находятся массивы y_true и y_pred с бинарными метками. Положительный класс — 1. Скрытая подготовка данных не создаёт; переменные прошлых уроков сюда не переносятся.
Что нужно сделать
Функция audit_predictions(actual, predicted) должна возвращать словарь с ключами precision, recall, f1, tp, fp, fn, manual_precision, manual_recall. Значения precision, recall и f1 — библиотечные метрики для положительного класса 1; tp, fp и fn — соответствующие счётчики матрицы ошибок при порядке меток [0, 1]. Значения manual_precision и manual_recall — независимая проверка первых двух метрик по этим счётчикам. Если долю невозможно вычислить из-за нулевого знаменателя, её значение равно 0.0. После вашего кода проверяется переменная audit — сохраните в ней отчёт для данных из шаблона.
Ввод и вывод
stdin не используется. Печатать через print в stdout ничего не нужно. Проверяются значения audit, совпадение ручных долей с библиотечными и работа функции на других бинарных метках.
Продолжить с проверкой и прогрессом
Откройте интерактивный раннер с заданиями урока.
