</>
Тема 4 из 6

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

В прошлой теме качество классификатора измерялось одним числом — долей верных ответов. Этого часто мало. Представьте: из 1000 пациентов болен один, а модель всем говорит «здоров». Accuracy — 99,9 %, а пользы ноль: единственного больного она пропустила.

Чтобы видеть, какие ошибки делает модель, используют матрицу ошибок и метрики, построенные на ней.

Матрица ошибок

Пусть класс 1 — «положительный» (болен, спам, мошенник), класс 0 — «отрицательный». Каждый ответ модели попадает в одну из четырёх клеток:

предсказано 0 предсказано 1
на самом деле 0 TN — верно отвергнут FP — ложная тревога
на самом деле 1 FN — пропуск TP — верно найден
from sklearn.metrics import confusion_matrix

y_true = [0, 0, 0, 1, 1, 1, 1]
y_pred = [0, 1, 0, 1, 1, 0, 1]
print(confusion_matrix(y_true, y_pred))
# [[2 1]
#  [1 3]]

Строки — настоящие классы, столбцы — предсказанные, оба по возрастанию. Если в данных встретился только один класс, матрица будет 1 × 1. Чтобы размер был всегда одинаковым, перечислите классы явно: confusion_matrix(y_true, y_pred, labels=[0, 1]).

Числа из матрицы удобно достать так: tn, fp, fn, tp = confusion_matrix(...).ravel().

Точность и полнота

  • Precision (точность) — какая доля объектов, которые модель назвала положительными, действительно положительна: TP / (TP + FP). Низкая точность — много ложных тревог.
  • Recall (полнота) — какую долю настоящих положительных модель нашла: TP / (TP + FN). Низкая полнота — много пропусков.
from sklearn.metrics import precision_score, recall_score

print(precision_score(y_true, y_pred))   # 3 / 4 = 0.75
print(recall_score(y_true, y_pred))      # 3 / 4 = 0.75

Что важнее, зависит от задачи: при поиске болезни страшнее пропуск (нужна полнота), в спам-фильтре страшнее отправить важное письмо в спам (нужна точность).

F1-мера

Одно число, которое учитывает и точность, и полноту, — их среднее гармоническое:

F1 = 2 · P · R / (P + R) = 2 · TP / (2 · TP + FP + FN)

Оно маленькое, если плоха хотя бы одна из двух метрик.

from sklearn.metrics import f1_score
print(f1_score(y_true, y_pred))

Классы-строки и нулевой знаменатель

По умолчанию положительным считается класс 1. Если классы — строки, укажите нужный параметром pos_label:

y_true = ["кот", "пёс", "кот", "кот"]
y_pred = ["кот", "кот", "пёс", "кот"]
print(recall_score(y_true, y_pred, pos_label="кот"))   # 2 / 3

Если модель ни разу не предсказала положительный класс, в формуле точности получается деление на ноль. scikit-learn тогда вернёт 0 и напечатает предупреждение; параметр zero_division=0 делает то же самое без предупреждения.

Полный отчёт по всем классам сразу печатает classification_report(y_true, y_pred).

Порог вероятности

predict у большинства классификаторов относит объект к классу 1, если его вероятность не меньше 0.5. Но порог можно выбрать самому — по вероятностям из predict_proba:

proba = model.predict_proba(X_test)[:, 1]
pred = [1 if p >= 0.3 else 0 for p in proba]

Понижаем порог — модель чаще говорит «1»: полнота растёт, точность падает. Повышаем — наоборот. Подбирают порог под задачу, например перебирая несколько значений и сравнивая F1.

Итог

  • confusion_matrix(y_true, y_pred, labels=[0, 1]) — TN, FP / FN, TP;
  • precision_score — доля верных среди «найденных», recall_score — доля найденных среди настоящих;
  • f1_score — баланс точности и полноты;
  • pos_label — какой класс положительный, zero_division — что вернуть при делении на ноль;
  • порог по predict_proba позволяет менять баланс точности и полноты.