Calibración de Probabilidades para la Clasificación de Tres Clases

Beginner

This tutorial is from open-source community. Access the source code

Introducción

Esta práctica muestra cómo utilizar la calibración sigmoide para la clasificación de tres clases en Python con scikit-learn. Muestra cómo la calibración sigmoide cambia las probabilidades predichas y cómo se puede utilizar para mejorar el rendimiento del modelo.

Consejos sobre la VM

Una vez finalizada la inicialización de la VM, haga clic en la esquina superior izquierda para cambiar a la pestaña Cuaderno y acceder a Jupyter Notebook para practicar.

A veces, es posible que tenga que esperar unos segundos a que Jupyter Notebook termine de cargarse. La validación de las operaciones no se puede automatizar debido a las limitaciones de Jupyter Notebook.

Si tiene problemas durante el aprendizaje, no dude en preguntar a Labby. Deje su retroalimentación después de la sesión y lo resolveremos rápidamente para usted.

Datos

Generamos un conjunto de datos de clasificación con 2000 muestras, 2 características y 3 clases de destino. Luego dividimos los datos de la siguiente manera:

  • entrenamiento: 600 muestras (para entrenar el clasificador)
  • validación: 400 muestras (para calcular las probabilidades predichas)
  • prueba: 1000 muestras
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:]

Ajuste y Calibración

Entrenamos un clasificador de bosque aleatorio con 25 estimadores base (árboles) en los datos de entrenamiento y validación concatenados (1000 muestras). Este es el clasificador no calibrado.

from sklearn.ensemble import RandomForestClassifier

clf = RandomForestClassifier(n_estimators=25)
clf.fit(X_train_valid, y_train_valid)

Para entrenar el clasificador calibrado, comenzamos con el mismo clasificador de bosque aleatorio, pero lo entrenamos utilizando solo el subconjunto de datos de entrenamiento (600 muestras), y luego lo calibramos, con method='sigmoid', utilizando el subconjunto de datos de validación (400 muestras) en un proceso de dos etapas.

from sklearn.calibration import CalibratedClassifierCV

clf = RandomForestClassifier(n_estimators=25)
clf.fit(X_train, y_train)
cal_clf = CalibratedClassifierCV(clf, method="sigmoid", cv="prefit")
cal_clf.fit(X_valid, y_valid)

Comparar Probabilidades

Graficamos un 2-simplex con flechas que muestran el cambio en las probabilidades predichas de las muestras de prueba.

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="Clase 1")
plt.plot([0.0], [1.0], "go", ms=20, label="Clase 2")
plt.plot([0.0], [0.0], "bo", ms=20, label="Clase 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("Cambio de probabilidades predichas en las muestras de prueba después de la calibración sigmoide")
plt.xlabel("Probabilidad de la clase 1")
plt.ylabel("Probabilidad de la clase 2")
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)
_ = plt.legend(loc="best")

Comparación de log-loss

Comparamos la log-loss del clasificador no calibrado y del clasificador calibrado en las predicciones de las 1000 muestras de prueba.

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 de")
print(f" * clasificador no calibrado: {score:.3f}")
print(f" * clasificador calibrado: {cal_score:.3f}")

Generar cuadrícula y graficar

Generamos una cuadrícula de probabilidades no calibradas posibles sobre el 2-simplex, calculamos las probabilidades calibradas correspondientes y graficamos flechas para cada una. Las flechas se colorean de acuerdo con la probabilidad no calibrada más alta. Esto ilustra el mapa de calibración aprendido:

plt.figure(figsize=(10, 10))
## Generar cuadrícula de valores de probabilidad
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]

## Usar los tres calibradores por clase para calcular las probabilidades calibradas
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-normalizar las predicciones calibradas para asegurarse de que queden dentro del
## simplex. Este mismo paso de renormalización se realiza internamente por el
## método predict de CalibratedClassifierCV en problemas multiclasificación.
prediction /= prediction.sum(axis=1)[:, None]

## Graficar los cambios en las probabilidades predichas inducidos por los calibradores
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])],
    )

## Graficar los límites del simplex unitario
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("Mapa de calibración sigmoide aprendido")
plt.xlabel("Probabilidad de la clase 1")
plt.ylabel("Probabilidad de la clase 2")
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)

plt.show()

Resumen

Esta práctica mostró cómo utilizar la calibración sigmoide para la clasificación de tres clases en Python utilizando scikit-learn. Demostró el impacto de la calibración en las probabilidades predichas y cómo se puede utilizar para mejorar el rendimiento del modelo.