Spec-Zone.ru › scikit-learn

Примечание

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

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

Этот пример демонстрирует, как калибровка сигмоидой калибровка изменяет предсказанные вероятности для задачи 3-классовой классификации. Иллюстрируется стандартный 2-симплекс, где три вершины соответствуют трём классам. Стрелки указывают от векторов вероятностей, предсказанных некалиброванным классификатором, к векторам вероятностей, предсказанных тем же классификатором после калибровки сигмоидой на проверочном наборе. Цвета обозначают истинный класс объекта (красный: класс 1, зелёный: класс 2, синий: класс 3).

Данные

Ниже мы генерируем набор данных для классификации с 2000 объектами, 2 признаками и 3 целевыми классами. Затем мы разделим данные следующим образом:

  • обучение: 600 объектов (для обучения классификатора)
  • валидация: 400 объектов (для калибровки предсказанных вероятностей)
  • тестирование: 1000 объектов

Обратите внимание, что мы также создаём X_train_valid и y_train_valid, которые состоят из объединения обучающего и валидационного подмножеств. Это используется, когда мы хотим только обучить классификатор, но не калибровать предсказанные вероятности.

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

import numpy as np

from sklearn.datasets import make_blobs

np.random.seed(0)

X, y = make_blobs(
    n_samples=2000, n_features=2, centers=3, random_state=42, cluster_std=5.0
)
X_train, y_train = X[:600], y[:600]
X_valid, y_valid = X[600:1000], y[600:1000]
X_train_valid, y_train_valid = X[:1000], y[:1000]
X_test, y_test = X[1000:], y[1000:]

Обучение и калибровка

Сначала мы обучим RandomForestClassifier с 25 базовыми оценщиками (деревьями) на объединённых обучающих и валидационных данных (1000 объектов). Это некалиброванный классификатор.

from sklearn.ensemble import RandomForestClassifier

clf = RandomForestClassifier(n_estimators=25)
clf.fit(X_train_valid, y_train_valid)
RandomForestClassifier(n_estimators=25)
В среде Jupyter, пожалуйста, перезапустите ячейку, чтобы показать HTML-представление, или доверьтесь блокноту.
На GitHub HTML-представление не может быть отображено, пожалуйста, попробуйте загрузить эту страницу с nbviewer.org.
RandomForestClassifier(n_estimators=25)


Для обучения калиброванного классификатора мы начнём с того же RandomForestClassifier, но обучим его только на обучающих данных (600 объектов), а затем произведём калибровку, с помощью method='sigmoid', используя валидационные данные (400 объектов) в двухэтапном процессе.

from sklearn.calibration import CalibratedClassifierCV
from sklearn.frozen import FrozenEstimator

clf = RandomForestClassifier(n_estimators=25)
clf.fit(X_train, y_train)
cal_clf = CalibratedClassifierCV(FrozenEstimator(clf), method="sigmoid")
cal_clf.fit(X_valid, y_valid)
CalibratedClassifierCV(estimator=FrozenEstimator(estimator=RandomForestClassifier(n_estimators=25)))
В среде Jupyter, пожалуйста, перезапустите ячейку, чтобы показать HTML-представление, или доверьтесь блокноту.
На GitHub HTML-представление не может быть отображено, пожалуйста, попробуйте загрузить эту страницу с nbviewer.org.
CalibratedClassifierCV(estimator=FrozenEstimator(estimator=RandomForestClassifier(n_estimators=25)))
FrozenEstimator(estimator=RandomForestClassifier(n_estimators=25))
RandomForestClassifier(n_estimators=25)
RandomForestClassifier(n_estimators=25)


Сравнение вероятностей

Ниже мы отображаем 2-симплекс со стрелками, показывающими изменение предсказанных вероятностей тестовых образцов.

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
colors = ["r", "g", "b"]

clf_probs = clf.predict_proba(X_test)
cal_clf_probs = cal_clf.predict_proba(X_test)
# Plot arrows
for i in range(clf_probs.shape[0]):
    plt.arrow(
        clf_probs[i, 0],
        clf_probs[i, 1],
        cal_clf_probs[i, 0] - clf_probs[i, 0],
        cal_clf_probs[i, 1] - clf_probs[i, 1],
        color=colors[y_test[i]],
        head_width=1e-2,
    )

# Plot perfect predictions, at each vertex
plt.plot([1.0], [0.0], "ro", ms=20, label="Class 1")
plt.plot([0.0], [1.0], "go", ms=20, label="Class 2")
plt.plot([0.0], [0.0], "bo", ms=20, label="Class 3")

# Plot boundaries of unit simplex
plt.plot([0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], "k", label="Simplex")

# Annotate points 6 points around the simplex, and mid point inside simplex
plt.annotate(
    r"($\frac{1}{3}$, $\frac{1}{3}$, $\frac{1}{3}$)",
    xy=(1.0 / 3, 1.0 / 3),
    xytext=(1.0 / 3, 0.23),
    xycoords="data",
    arrowprops=dict(facecolor="black", shrink=0.05),
    horizontalalignment="center",
    verticalalignment="center",
)
plt.plot([1.0 / 3], [1.0 / 3], "ko", ms=5)
plt.annotate(
    r"($\frac{1}{2}$, $0$, $\frac{1}{2}$)",
    xy=(0.5, 0.0),
    xytext=(0.5, 0.1),
    xycoords="data",
    arrowprops=dict(facecolor="black", shrink=0.05),
    horizontalalignment="center",
    verticalalignment="center",
)
plt.annotate(
    r"($0$, $\frac{1}{2}$, $\frac{1}{2}$)",
    xy=(0.0, 0.5),
    xytext=(0.1, 0.5),
    xycoords="data",
    arrowprops=dict(facecolor="black", shrink=0.05),
    horizontalalignment="center",
    verticalalignment="center",
)
plt.annotate(
    r"($\frac{1}{2}$, $\frac{1}{2}$, $0$)",
    xy=(0.5, 0.5),
    xytext=(0.6, 0.6),
    xycoords="data",
    arrowprops=dict(facecolor="black", shrink=0.05),
    horizontalalignment="center",
    verticalalignment="center",
)
plt.annotate(
    r"($0$, $0$, $1$)",
    xy=(0, 0),
    xytext=(0.1, 0.1),
    xycoords="data",
    arrowprops=dict(facecolor="black", shrink=0.05),
    horizontalalignment="center",
    verticalalignment="center",
)
plt.annotate(
    r"($1$, $0$, $0$)",
    xy=(1, 0),
    xytext=(1, 0.1),
    xycoords="data",
    arrowprops=dict(facecolor="black", shrink=0.05),
    horizontalalignment="center",
    verticalalignment="center",
)
plt.annotate(
    r"($0$, $1$, $0$)",
    xy=(0, 1),
    xytext=(0.1, 1),
    xycoords="data",
    arrowprops=dict(facecolor="black", shrink=0.05),
    horizontalalignment="center",
    verticalalignment="center",
)
# Add grid
plt.grid(False)
for x in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
    plt.plot([0, x], [x, 0], "k", alpha=0.2)
    plt.plot([0, 0 + (1 - x) / 2], [x, x + (1 - x) / 2], "k", alpha=0.2)
    plt.plot([x, x + (1 - x) / 2], [0, 0 + (1 - x) / 2], "k", alpha=0.2)

plt.title("Change of predicted probabilities on test samples after sigmoid calibration")
plt.xlabel("Probability class 1")
plt.ylabel("Probability class 2")
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)
_ = plt.legend(loc="best")
Change of predicted probabilities on test samples after sigmoid calibration

На рисунке выше каждая вершина симплекса представляет идеально предсказанный класс (например, 1, 0, 0). Середина внутри симплекса представляет предсказание трех классов с равной вероятностью (т.е., 1/3, 1/3, 1/3). Каждая стрелка начинается с некалиброванных вероятностей и заканчивается головкой стрелки в калиброванной вероятности. Цвет стрелки представляет истинный класс этого тестового образца.

Некалиброванный классификатор чрезмерно уверен в своих предсказаниях и имеет большой логический убыток. Калиброванный классификатор имеет меньший логический убыток по двум причинам. Во-первых, обратите внимание на рисунке выше, что стрелки, как правило, направлены от краев симплекса, где вероятность одного класса равна 0. Во-вторых, большая часть стрелок направлена к истинному классу, например, зеленые стрелки (образцы, где истинный класс — «зеленый») обычно направлены к зеленой вершине. Это приводит к меньшему количеству избыточно уверенных предсказаний с вероятностью 0 и одновременно к увеличению предсказанных вероятностей правильного класса. Таким образом, калиброванный классификатор создает более точные предсказанные вероятности, которые имеют меньший логический убыток.

Мы можем объективно показать это, сравнив логический убыток некалиброванного и калиброванного классификаторов по предсказаниям 1000 тестовых образцов. Обратите внимание, что альтернативой было бы увеличение числа базовых оценщиков (деревьев) RandomForestClassifier, что привело бы к аналогичному снижению логического убытка.

from sklearn.metrics import log_loss

score = log_loss(y_test, clf_probs)
cal_score = log_loss(y_test, cal_clf_probs)

print("Log-loss of")
print(f" * uncalibrated classifier: {score:.3f}")
print(f" * calibrated classifier: {cal_score:.3f}")
Log-loss of
 * uncalibrated classifier: 1.327
 * calibrated classifier: 0.549

Наконец, мы генерируем сетку возможных некалиброванных вероятностей по 2-симплексу, вычисляем соответствующие калиброванные вероятности и строим стрелки для каждого. Стрелки окрашиваются в соответствии с самой высокой некалиброванной вероятностью. Это иллюстрирует выученную калибровочную карту:

plt.figure(figsize=(10, 10))
# Generate grid of probability values
p1d = np.linspace(0, 1, 20)
p0, p1 = np.meshgrid(p1d, p1d)
p2 = 1 - p0 - p1
p = np.c_[p0.ravel(), p1.ravel(), p2.ravel()]
p = p[p[:, 2] >= 0]

# Use the three class-wise calibrators to compute calibrated probabilities
calibrated_classifier = cal_clf.calibrated_classifiers_[0]
prediction = np.vstack(
    [
        calibrator.predict(this_p)
        for calibrator, this_p in zip(calibrated_classifier.calibrators, p.T)
    ]
).T

# Re-normalize the calibrated predictions to make sure they stay inside the
# simplex. This same renormalization step is performed internally by the
# predict method of CalibratedClassifierCV on multiclass problems.
prediction /= prediction.sum(axis=1)[:, None]

# Plot changes in predicted probabilities induced by the calibrators
for i in range(prediction.shape[0]):
    plt.arrow(
        p[i, 0],
        p[i, 1],
        prediction[i, 0] - p[i, 0],
        prediction[i, 1] - p[i, 1],
        head_width=1e-2,
        color=colors[np.argmax(p[i])],
    )

# Plot the boundaries of the unit simplex
plt.plot([0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], "k", label="Simplex")

plt.grid(False)
for x in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
    plt.plot([0, x], [x, 0], "k", alpha=0.2)
    plt.plot([0, 0 + (1 - x) / 2], [x, x + (1 - x) / 2], "k", alpha=0.2)
    plt.plot([x, x + (1 - x) / 2], [0, 0 + (1 - x) / 2], "k", alpha=0.2)

plt.title("Learned sigmoid calibration map")
plt.xlabel("Probability class 1")
plt.ylabel("Probability class 2")
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)

plt.show()
Learned sigmoid calibration map

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

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_calibration_multiclass.ipynb

Download Python source code: plot_calibration_multiclass.py

Download zipped: plot_calibration_multiclass.zip

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

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

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

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

Построение вероятностей классов, вычисленных VotingClassifier

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

Spec-Zone.ru

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