Python «pred_proba» на самом деле не предсказывает вероятности (и как это исправить)

Как оценить и исправить вероятность ошибки калибровки

Специалисты по анализу данных обычно оценивают свои прогностические модели с точки зрения точности или точности, но никогда не задаются вопросом:

«Способна ли моя модель предсказывать реальные вероятности?»

Однако точная оценка вероятности чрезвычайно важна с точки зрения бизнеса (иногда даже более ценно, чем хорошая точность). Хотите пример?

Представьте, что ваша компания продает 2 кружки, одна из которых представляет собой простую белую кружку, а на другой изображен котенок. Вы должны решить, какую кружку показать конкретному покупателю. Для этого вам необходимо спрогнозировать вероятность того, что данный пользователь купит каждую из них. Итак, вы тренируете несколько разных моделей и получаете следующие результаты:

Какую кружку вы бы посоветовали этому пользователю?

Обе модели согласны с тем, что пользователь с большей вероятностью купит обычную кружку (так, модель A и модель B имеют одинаковую площадь под ROC, поскольку этот показатель только оценивает сортировку).

Но, согласно модели А, вы максимизируете ожидаемую прибыль, порекомендовав простую кружку. В то время как согласно модели B ожидаемая прибыль максимизируется кружкой для котенка.

В приложениях, подобных этому, очень важно выяснить, какая модель может лучше оценить вероятности.

В этой статье мы увидим, как измерить калибровку вероятности (как визуально, так и численно) и как «исправить» существующую модель, чтобы получить лучшие вероятности.

Что не так с «pred_proba»

Все самые популярные библиотеки машинного обучения в Python имеют метод под названием «pred_proba»: Scikit-learn (например, LogisticRegression, SVC, RandomForest,…), XGBoost, LightGBM, CatBoost, Keras…

Но, несмотря на свое название, pred_proba не совсем предсказывает вероятности. Фактически, разные исследования (особенно это и это) показали, что самые популярные прогностические модели не откалиброваны.

Тот факт, что число находится между нулем и единицей, недостаточно для того, чтобы назвать это вероятностью!

Но тогда когда мы можем сказать, что число на самом деле представляет собой вероятность?

Представьте, что вы обучили прогностическую модель предсказывать, разовьется ли у пациента рак. Теперь предположим, что для данного пациента модель предсказывает 5% вероятность. В принципе, мы должны наблюдать за одним и тем же пациентом в нескольких параллельных вселенных и смотреть, действительно ли он заболевает раком в 5% случаев.

Поскольку мы не можем идти по этой дороге, лучший пример - взять всех пациентов с вероятностью около 5% и подсчитать, у скольких из них развился рак. Если наблюдаемый процент фактически близок к 5%, мы говорим, что вероятности, предоставляемые моделью, «откалиброваны».

Когда предсказанные вероятности отражают реальные лежащие в основе вероятности, они называются «откалиброванными».

Но как проверить, откалибрована ли ваша модель?

Калибровочная кривая

Самый простой способ оценить калибровку вашей модели - это построить график под названием «калибровочная кривая» (также известная как «диаграмма надежности»).

Идея состоит в том, чтобы разделить наблюдения на интервалы вероятности. Таким образом, наблюдения, которые принадлежат одному интервалу, имеют одинаковую вероятность. В этот момент для каждого интервала калибровочная кривая сравнивает прогнозируемое среднее (т.е. среднее значение прогнозируемой вероятности) с теоретическим средним (т.е. средним значением наблюдаемой целевой переменной).

Scikit-learn выполняет всю эту работу за вас с помощью функции «Calibration_curve»:

from sklearn.calibration import calibration_curve
y_means, proba_means = calibration_curve(y, proba, n_bins, strategy)

Вам нужно только выбрать количество ящиков и (необязательно) стратегию объединения между:

  • «Uniform», интервал 0–1 разделен на n_bins одинаковой ширины;
  • «Квантиль», края интервала определяются таким образом, чтобы в каждом интервале было одинаковое количество наблюдений.

Для построения графиков я лично предпочитаю «квантильный» подход. Фактически, «равномерное» разбиение на интервалы может вводить в заблуждение, потому что некоторые интервалы могут содержать очень мало наблюдений.

Функция Numpy возвращает два массива, содержащих для каждой ячейки среднюю вероятность и среднее значение целевой переменной. Поэтому все, что нам нужно сделать, это построить их:

import matplotlib.pyplot as plt
plt.plot([0, 1], [0, 1], linestyle = '--', label = 'Perfect calibration')
plt.plot(proba_means, y_means)

Предположим, что ваша модель имеет хорошую точность, калибровочная кривая будет монотонно увеличиваться. Но это не значит, что модель хорошо откалибрована. Действительно, ваша модель хорошо откалибрована только в том случае, если калибровочная кривая очень близка к биссектрисе (т. Е. Пунктирной серой линии), поскольку это будет означать, что прогнозируемая вероятность в среднем близка к теоретической.

Давайте посмотрим на несколько примеров распространенных типов калибровочных кривых, указывающих на неправильную калибровку вашей модели:

Наиболее распространенные типы ошибок калибровки:

Как исправить ошибку калибровки (в Python)

Предположим, вы обучили классификатор, который дает точные, но не откалиброванные вероятности. Идея калибровки вероятности состоит в том, чтобы построить вторую модель (называемую калибратором), которая может «скорректировать» их до реальных вероятностей.

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

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

В качестве калибраторов в основном используются два метода:

  • Изотоническая регрессия. Непараметрический алгоритм, который подгоняет неубывающую строку произвольной формы к данным. Тот факт, что строка не убывает, является фундаментальным, потому что соблюдается исходная сортировка.
  • Логистическая регрессия.

Давайте посмотрим, как на практике использовать калибраторы в Python с помощью набора данных игрушек:

from sklearn.datasets import make_classification
X, y = make_classification(
    n_samples = 15000, 
    n_features = 50, 
    n_informative = 30, 
    n_redundant = 20,
    weights = [.9, .1],
    random_state = 0
)
X_train, X_valid, X_test = X[:5000], X[5000:10000], X[10000:]
y_train, y_valid, y_test = y[:5000], y[5000:10000], y[10000:]

Прежде всего, нам нужно будет подогнать классификатор. Давайте использовать случайный лес (но подойдет любая модель, в которой есть метод «pred_proba»).

from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier().fit(X_train, y_train)
proba_valid = forest.predict_proba(X_valid)[:, 1]

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

  • Изотоническая регрессия:
from sklearn.isotonic import IsotonicRegression
iso_reg = IsotonicRegression(y_min = 0, y_max = 1, out_of_bounds = 'clip').fit(proba_valid, y_valid)
proba_test_forest_isoreg = iso_reg.predict(forest.predict_proba(X_test)[:, 1])
  • Логистическая регрессия:
from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression().fit(proba_valid.reshape(-1, 1), y_valid)
proba_test_forest_logreg = log_reg.predict_proba(forest.predict_proba(X_test)[:, 1].reshape(-1, 1))[:, 1]

На данный момент у нас есть три варианта прогнозирования вероятностей:

  1. равнина случайный лес,
  2. случайный лес + изотоническая регрессия,
  3. случайный лес + логистическая регрессия.

Но как определить, какой из них наиболее откалиброван?

Количественная оценка ошибки калибровки

Сюжеты нравятся всем. Но помимо калибровочного графика нам нужен количественный способ измерения (ошибочной) калибровки. Наиболее часто используемый показатель называется Ожидаемая ошибка калибровки. Он отвечает на вопрос:

Насколько далеко в среднем наша предсказанная вероятность от истинной вероятности?

Возьмем, к примеру, один классификатор:

Определить ошибку калибровки отдельной ячейки очень просто: это абсолютная разница между средним значением предсказанных вероятностей и долей положительных результатов в одной ячейке.

Если задуматься, это довольно интуитивно понятно. Возьмем один бункер и предположим, что среднее значение его прогнозируемой вероятности составляет 25%. Таким образом, мы ожидаем, что доля положительных результатов в этом бине примерно равна 25%. Чем дальше это значение от 25%, тем хуже калибровка этого бина.

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

где b обозначает корзину, а B - количество корзин. Обратите внимание, что знаменатель - это просто общее количество образцов.

Но эта формула оставляет нам проблему определения количества ящиков. Чтобы найти максимально нейтральный показатель, я предлагаю установить количество ящиков в соответствии с правилом Фридмана-Диакониса (которое является статистическим правилом, предназначенным для нахождения количество интервалов, которое делает гистограмму максимально приближенной к теоретическому распределению вероятностей).

Использовать правило Фридмана-Диакониса в Python чрезвычайно просто, поскольку оно уже реализовано в функции гистограммы numpy (достаточно передать строку «fd» в параметр «bins»).

Вот реализация Python ожидаемой ошибки калибровки, в которой по умолчанию используется правило Фридмана-Диакониса:

def expected_calibration_error(y, proba, bins = 'fd'):
  import numpy as np
  bin_count, bin_edges = np.histogram(proba, bins = bins)
  n_bins = len(bin_count)
  bin_edges[0] -= 1e-8 # because left edge is not included
  bin_id = np.digitize(proba, bin_edges, right = True) - 1
  bin_ysum = np.bincount(bin_id, weights = y, minlength = n_bins)
  bin_probasum = np.bincount(bin_id, weights = proba, minlength = n_bins)
  bin_ymean = np.divide(bin_ysum, bin_count, out = np.zeros(n_bins), where = bin_count > 0)
  bin_probamean = np.divide(bin_probasum, bin_count, out = np.zeros(n_bins), where = bin_count > 0)
  ece = np.abs((bin_probamean - bin_ymean) * bin_count).sum() / len(proba)
  return ece

Теперь, когда у нас есть метрика для калибровки, давайте сравним калибровку трех моделей, которые мы получили выше (на тестовом наборе):

В этом случае изотоническая регрессия дала лучший результат с точки зрения калибровки, в среднем составляя всего 1,2% от истинной вероятности. Это огромное улучшение, если учесть, что ECE равнинного случайного леса составлял 7%.

Ссылка

Вот несколько интересных статей (на которых основана эта статья), которые я рекомендую, если вы хотите углубить тему калибровки вероятностей:

Спасибо за чтение! Надеюсь, этот пост был вам полезен.

Я ценю отзывы и конструктивную критику. Если вы хотите поговорить об этой статье или о других связанных темах, вы можете написать мне на мой контакт в Linkedin.