Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере с помощью JupyterLite или Binder.
Кривые калибровки вероятностей
При классификации часто требуется предсказывать не только метку класса, но и соответствующую вероятность. Эта вероятность дает представление о степени уверенности в предсказании. В этом примере показано, как с помощью кривых калибровки, также известных как диаграммы надежности, визуализировать качество калибровки предсказанных вероятностей. Также будет продемонстрирована калибровка некалиброванного классификатора.
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Набор данных
Мы будем использовать синтетический набор данных для бинарной классификации со 100 000 образцами и 20 признаками. Из 20 признаков только 2 являются информативными, 10 — избыточными (случайные комбинации информативных признаков), а остальные 8 — неинформативными (случайные числа). Из 100 000 образцов 1 000 будут использоваться для обучения модели, а остальные — для тестирования.
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(
n_samples=100_000, n_features=20, n_informative=2, n_redundant=10, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.99, random_state=42
)
Кривые калибровки
Наивный байесовский классификатор с распределением Гаусса
Сначала мы сравним:
-
LogisticRegression(используется в качестве базового классификатора, поскольку правильно настроенная регуляризация логистической регрессии часто обеспечивает хорошую калибровку по умолчанию благодаря использованию логарифмической функции потерь) - Некалиброванный
GaussianNB -
GaussianNBс изотонической и сигмоидальной калибровкой (см. Руководство пользователя)
Ниже построены кривые калибровки для всех 4 вариантов: по оси x отложена средняя предсказанная вероятность для каждого интервала, а по оси y — доля положительных классов в каждом интервале.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from sklearn.calibration import CalibratedClassifierCV, CalibrationDisplay
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
lr = LogisticRegression(C=1.0)
gnb = GaussianNB()
gnb_isotonic = CalibratedClassifierCV(gnb, cv=2, method="isotonic")
gnb_sigmoid = CalibratedClassifierCV(gnb, cv=2, method="sigmoid")
clf_list = [
(lr, "Logistic"),
(gnb, "Naive Bayes"),
(gnb_isotonic, "Naive Bayes + Isotonic"),
(gnb_sigmoid, "Naive Bayes + Sigmoid"),
]
fig = plt.figure(figsize=(10, 10))
gs = GridSpec(4, 2)
colors = plt.get_cmap("Dark2")
ax_calibration_curve = fig.add_subplot(gs[:2, :2])
calibration_displays = {}
for i, (clf, name) in enumerate(clf_list):
clf.fit(X_train, y_train)
display = CalibrationDisplay.from_estimator(
clf,
X_test,
y_test,
n_bins=10,
name=name,
ax=ax_calibration_curve,
color=colors(i),
)
calibration_displays[name] = display
ax_calibration_curve.grid()
ax_calibration_curve.set_title("Calibration plots (Naive Bayes)")
# Add histogram
grid_positions = [(2, 0), (2, 1), (3, 0), (3, 1)]
for i, (_, name) in enumerate(clf_list):
row, col = grid_positions[i]
ax = fig.add_subplot(gs[row, col])
ax.hist(
calibration_displays[name].y_prob,
range=(0, 1),
bins=10,
label=name,
color=colors(i),
)
ax.set(title=name, xlabel="Mean predicted probability", ylabel="Count")
plt.tight_layout()
plt.show()

Некалиброванный GaussianNB плохо откалиброван из-за избыточных признаков, нарушающих предположение о независимости признаков и приводящих к чрезмерно уверенным предсказаниям классификатора. На это указывает типичная кривая в форме перевернутой сигмоиды. Калибровка вероятностей GaussianNB с помощью изотонической регрессии позволяет устранить эту проблему, что видно по почти диагональной кривой калибровки. Сигмоидальная регрессия также немного улучшает калибровку, хотя и не так значительно, как непараметрическая изотоническая регрессия. Это можно объяснить тем, что у нас достаточно данных для калибровки, поэтому можно воспользоваться большей гибкостью непараметрической модели.
Ниже мы проведем количественный анализ с использованием нескольких метрик классификации: потери по оценке Брайера, логарифмические потери, точность, полнота и оценка F1 и ROC AUC.
from collections import defaultdict
import pandas as pd
from sklearn.metrics import (
brier_score_loss,
f1_score,
log_loss,
precision_score,
recall_score,
roc_auc_score,
)
scores = defaultdict(list)
for i, (clf, name) in enumerate(clf_list):
clf.fit(X_train, y_train)
y_prob = clf.predict_proba(X_test)
y_pred = clf.predict(X_test)
scores["Classifier"].append(name)
for metric in [brier_score_loss, log_loss, roc_auc_score]:
score_name = metric.__name__.replace("_", " ").replace("score", "").capitalize()
scores[score_name].append(metric(y_test, y_prob[:, 1]))
for metric in [precision_score, recall_score, f1_score]:
score_name = metric.__name__.replace("_", " ").replace("score", "").capitalize()
scores[score_name].append(metric(y_test, y_pred))
score_df = pd.DataFrame(scores).set_index("Classifier")
score_df.round(decimals=3)
score_df
Обратите внимание: хотя калибровка улучшает потери по оценке Брайера (метрику, состоящую из компонента калибровки и компонента уточнения) и логарифмические потери, она существенно не изменяет показатели точности предсказаний (точность, полноту и оценку F1). Это объясняется тем, что калибровка не должна заметно менять вероятности предсказаний в точке порога принятия решения (x = 0.5 на графике). Однако калибровка должна повышать точность предсказанных вероятностей и тем самым делать их более полезными при принятии решений о распределении ресурсов в условиях неопределенности. Кроме того, ROC AUC не должна изменяться, поскольку калибровка является монотонным преобразованием. Действительно, калибровка не влияет ни на одну метрику ранжирования.
Линейный классификатор на опорных векторах
Далее мы сравним:
-
LogisticRegression(базовый классификатор) - Некалиброванный
LinearSVC. Поскольку SVC по умолчанию не выдает вероятности, мы наивно масштабируем выход функции decision_function в диапазон [0, 1] с помощью min-max-нормализации. -
LinearSVCс изотонической и сигмоидальной калибровкой (см. Руководство пользователя)
import numpy as np
from sklearn.svm import LinearSVC
class NaivelyCalibratedLinearSVC(LinearSVC):
"""LinearSVC with `predict_proba` method that naively scales
`decision_function` output for binary classification."""
def fit(self, X, y):
super().fit(X, y)
df = self.decision_function(X)
self.df_min_ = df.min()
self.df_max_ = df.max()
def predict_proba(self, X):
"""Min-max scale output of `decision_function` to [0, 1]."""
df = self.decision_function(X)
calibrated_df = (df - self.df_min_) / (self.df_max_ - self.df_min_)
proba_pos_class = np.clip(calibrated_df, 0, 1)
proba_neg_class = 1 - proba_pos_class
proba = np.c_[proba_neg_class, proba_pos_class]
return proba
lr = LogisticRegression(C=1.0)
svc = NaivelyCalibratedLinearSVC(max_iter=10_000)
svc_isotonic = CalibratedClassifierCV(svc, cv=2, method="isotonic")
svc_sigmoid = CalibratedClassifierCV(svc, cv=2, method="sigmoid")
clf_list = [
(lr, "Logistic"),
(svc, "SVC"),
(svc_isotonic, "SVC + Isotonic"),
(svc_sigmoid, "SVC + Sigmoid"),
]
fig = plt.figure(figsize=(10, 10))
gs = GridSpec(4, 2)
ax_calibration_curve = fig.add_subplot(gs[:2, :2])
calibration_displays = {}
for i, (clf, name) in enumerate(clf_list):
clf.fit(X_train, y_train)
display = CalibrationDisplay.from_estimator(
clf,
X_test,
y_test,
n_bins=10,
name=name,
ax=ax_calibration_curve,
color=colors(i),
)
calibration_displays[name] = display
ax_calibration_curve.grid()
ax_calibration_curve.set_title("Calibration plots (SVC)")
# Add histogram
grid_positions = [(2, 0), (2, 1), (3, 0), (3, 1)]
for i, (_, name) in enumerate(clf_list):
row, col = grid_positions[i]
ax = fig.add_subplot(gs[row, col])
ax.hist(
calibration_displays[name].y_prob,
range=(0, 1),
bins=10,
label=name,
color=colors(i),
)
ax.set(title=name, xlabel="Mean predicted probability", ylabel="Count")
plt.tight_layout()
plt.show()

LinearSVC демонстрирует поведение, противоположное GaussianNB: кривая калибровки имеет форму сигмоиды, что характерно для недостаточно уверенного классификатора. В случае LinearSVC это вызвано свойством отступа функции потерь hinge, которая учитывает образцы, находящиеся близко к границе принятия решения (опорные векторы). Образцы, расположенные далеко от границы принятия решения, не влияют на функцию потерь hinge. Поэтому логично, что LinearSVC не пытается разделять образцы в областях высокой уверенности. В результате кривые калибровки становятся более пологими вблизи 0 и 1; это эмпирически показано на различных наборах данных в работе Niculescu-Mizil & Caruana [1].
Оба типа калибровки (сигмоидальная и изотоническая) могут устранить эту проблему и дают схожие результаты.
Как и прежде, мы приводим значения потерь по оценке Брайера, логарифмических потерь, точности, полноты и оценки F1 и ROC AUC.
scores = defaultdict(list)
for i, (clf, name) in enumerate(clf_list):
clf.fit(X_train, y_train)
y_prob = clf.predict_proba(X_test)
y_pred = clf.predict(X_test)
scores["Classifier"].append(name)
for metric in [brier_score_loss, log_loss, roc_auc_score]:
score_name = metric.__name__.replace("_", " ").replace("score", "").capitalize()
scores[score_name].append(metric(y_test, y_prob[:, 1]))
for metric in [precision_score, recall_score, f1_score]:
score_name = metric.__name__.replace("_", " ").replace("score", "").capitalize()
scores[score_name].append(metric(y_test, y_pred))
score_df = pd.DataFrame(scores).set_index("Classifier")
score_df.round(decimals=3)
score_df
Как и в случае с рассмотренным выше GaussianNB, калибровка улучшает показатели и потерь по оценке Брайера, и логарифмических потерь, но мало влияет на показатели точности предсказаний (точность, полноту и оценку F1).
Итоги
Параметрическая сигмоидальная калибровка позволяет справиться с ситуациями, когда кривая калибровки базового классификатора имеет форму сигмоиды (например, для LinearSVC), но не с теми, где она имеет форму перевернутой сигмоиды (например, для GaussianNB). Непараметрическая изотоническая калибровка подходит для обеих ситуаций, но для получения хороших результатов ей может потребоваться больше данных.
Список литературы
Общее время выполнения скрипта: (0 минут 3.201 секунды)
Похожие примеры
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/calibration/plot_calibration_curve.html