Spec-Zone.ru › scikit-learn

Примечание

Перейти к концу для загрузки полного кода примера. Или запустите этот пример в вашем браузере через JupyterLite или Binder

Кривые калибровки вероятностей

При выполнении классификации часто требуется предсказывать не только метку класса, но и соответствующую вероятность. Эта вероятность даёт некую уверенность в прогнозе. В этом примере показано, как визуализировать, насколько хорошо калиброваны предсказанные вероятности с помощью кривых калибровки, также известных как диаграммы надёжности. Также будет продемонстрировано калибрование некалиброванного классификатора.

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

Набор данных

Мы будем использовать синтетический набор данных для бинарной классификации с 100 000 примерами и 20 признаками. Из 20 признаков только 2 информативны, 10 избыточны (случайные комбинации информативных признаков), а оставшиеся 8 неинформативны (случайные числа). Из 100 000 примеров 1000 будет использовано для подбора модели, а остальные — для тестирования.

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 с исотонической и сигмоидной калибровкой (см. Руководство пользователя)

Ниже приведены кривые калибровки для всех четырёх условий, где по оси абсцисс указана средняя предсказанная вероятность для каждого интервала, а по оси ординат — доля положительных классов в каждом интервале.

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()
Calibration plots (Naive Bayes), Logistic, Naive Bayes, Naive Bayes + Isotonic, Naive Bayes + Sigmoid

Некалиброванный GaussianNB плохо калибруется из-за избыточных признаков, которые нарушают предположение о независимости признаков и приводят к излишне уверенному классификатору, что видно по типичной кривой, трансформирующей сигмоиду. Калибровка вероятностей GaussianNB с помощью Исотонической регрессии может исправить эту проблему, как видно из почти диагональной кривой калибровки. Сигмоидная регрессия также немного улучшает калибровку, хотя и не так сильно, как непараметрическая исотоническая регрессия. Это можно объяснить тем, что у нас достаточно данных для калибровки, так что можно использовать большую гибкость непараметрической модели.

Ниже мы проведем количественный анализ, учитывая несколько метрик классификации: Потеря Бриера, Логарифмическая потеря, Точность, полнота, F1-мера и AUC ROC.

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
Потеря Бриера Логарифмическая потеря Roc auc Точность Полнота F1
Классификатор
Логистическая 0.098932 0.323200 0.937443 0.871965 0.851348 0.861533
Наивный Байес 0.117608 0.782755 0.940374 0.857400 0.875941 0.866571
Наивный Байес + Исотонический 0.098332 0.370738 0.938613 0.883065 0.836224 0.859007
Наивный Байес + Сигмоидный 0.108880 0.368896 0.940201 0.861106 0.871277 0.866161


Обратите внимание, что хотя калибровка улучшает потерю Бриера (метрика, состоящая из калибровочной части и части уточнения) и логарифмическую потерю, она не существенно изменяет метрики точности прогноза (точность, полнота и F1-мера). Это связано с тем, что калибровка не должна значительно изменять вероятности предсказания в месте порогового значения (в x = 0,5 на графике). Однако калибровка должна сделать предсказанные вероятности более точными и, следовательно, более полезными для принятия решений о распределении при неопределённости. Кроме того, AUC ROC не должна меняться вообще, поскольку калибровка — это монотонное преобразование. Действительно, метрики ранжирования не изменяются при калибровке.

Линейный классификатор опорных векторов

Далее мы сравним:

  • 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()
Calibration plots (SVC), Logistic, SVC, SVC + Isotonic, SVC + Sigmoid

LinearSVC показывает противоположное поведение по сравнению с GaussianNB; кривая калибровки имеет сигмоидную форму, что характерно для классификатора с недостаточной уверенностью. В случае с LinearSVC это вызвано свойством маржи функции потерь хинджа, которая фокусируется на примерах, близких к границе принятия решения (опорные векторы). Примеры, находящиеся далеко от границы принятия решения, не влияют на функцию потерь хинджа. Поэтому логично, что LinearSVC не пытается разделить примеры в областях высокой уверенности. Это приводит к более плоским кривым калибровки около 0 и 1, и это эмпирически показано с использованием различных наборов данных в работе Niculescu-Mizil & Caruana [1].

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

Как и прежде, мы покажем потерю Бриера, логарифмическую потерю, точность, полноту, F1-меру и AUC ROC.

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
Потеря Бриера Логарифмическая потеря Roc auc Точность Полнота F1
Классификатор
Логистическая 0.098932 0.323200 0.937443 0.871965 0.851348 0.861533
SVC 0.144943 0.465660 0.937597 0.872186 0.851792 0.861868
SVC + Исотонический 0.099820 0.376999 0.936480 0.853174 0.877981 0.865400
SVC + Сигмоидный 0.098758 0.321301 0.937532 0.873724 0.848743 0.861053


Как и в случае с GaussianNB выше, калибровка улучшает как потерю Бриера, так и логарифмическую потерю, но не сильно изменяет метрики точности прогноза (точность, полнота и F1-мера).

Краткое описание

Параметрическая сигмоидная калибровка может справляться с ситуациями, когда кривая калибровки базового классификатора является сигмоидальной (например, для LinearSVC) но не там, где она является перевёрнуто-сигмоидальной (например, GaussianNB). Непараметрическая изотоническая калибровка может справляться с обеими ситуациями, но может потребовать больше данных для получения хороших результатов.

Ссылки

[1]

Прогнозирование хороших вероятностей с помощью контролируемого обучения, А. Никулеску-Мизил и Р. Каруана, ICML 2005

Общее время выполнения скрипта: (0 минут 2.267 секунды)

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_calibration_curve.ipynb

Download Python source code: plot_calibration_curve.py

Download zipped: plot_calibration_curve.zip

Связанные примеры

Калибровка вероятностей классификаторов

Сравнение калибровки классификаторов

Калибровка вероятностей для классификации с тремя классами

Примеры использования FrozenEstimator

© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/calibration/plot_calibration_curve.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API