Razones de verosimilitud de clase para medir el rendimiento de la clasificación

Machine LearningMachine LearningBeginner
Practicar Ahora

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

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, utilizaremos scikit-learn para demostrar cómo calcular las razones de verosimilitud positiva y negativa (LR+, LR-) para evaluar el poder predictivo de un clasificador binario. Estas métricas son independientes de la proporción entre clases en el conjunto de prueba, lo que las hace muy útiles cuando los datos disponibles para un estudio tienen una proporción de clases diferente a la de la aplicación objetivo. Pasaremos por los siguientes pasos:

Consejos para la MV

Después de que la máquina virtual (MV) haya terminado de iniciar, haz clic en la esquina superior izquierda para cambiar a la pestaña Notebook y acceder a Jupyter Notebook para practicar.

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

Si encuentras problemas durante el aprendizaje, no dudes en preguntarle a Labby. Proporciona comentarios después de la sesión y resolveremos rápidamente el problema para ti.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL sklearn(("Sklearn")) -.-> sklearn/CoreModelsandAlgorithmsGroup(["Core Models and Algorithms"]) sklearn(("Sklearn")) -.-> sklearn/DataPreprocessingandFeatureEngineeringGroup(["Data Preprocessing and Feature Engineering"]) sklearn(("Sklearn")) -.-> sklearn/ModelSelectionandEvaluationGroup(["Model Selection and Evaluation"]) sklearn(("Sklearn")) -.-> sklearn/UtilitiesandDatasetsGroup(["Utilities and Datasets"]) ml(("Machine Learning")) -.-> ml/FrameworkandSoftwareGroup(["Framework and Software"]) sklearn/CoreModelsandAlgorithmsGroup -.-> sklearn/linear_model("Linear Models") sklearn/DataPreprocessingandFeatureEngineeringGroup -.-> sklearn/dummy("Dummy Estimators") sklearn/ModelSelectionandEvaluationGroup -.-> sklearn/model_selection("Model Selection") sklearn/ModelSelectionandEvaluationGroup -.-> sklearn/metrics("Metrics") sklearn/ModelSelectionandEvaluationGroup -.-> sklearn/inspection("Inspection") sklearn/UtilitiesandDatasetsGroup -.-> sklearn/datasets("Datasets") ml/FrameworkandSoftwareGroup -.-> ml/sklearn("scikit-learn") subgraph Lab Skills sklearn/linear_model -.-> lab-49196{{"Razones de verosimilitud de clase para medir el rendimiento de la clasificación"}} sklearn/dummy -.-> lab-49196{{"Razones de verosimilitud de clase para medir el rendimiento de la clasificación"}} sklearn/model_selection -.-> lab-49196{{"Razones de verosimilitud de clase para medir el rendimiento de la clasificación"}} sklearn/metrics -.-> lab-49196{{"Razones de verosimilitud de clase para medir el rendimiento de la clasificación"}} sklearn/inspection -.-> lab-49196{{"Razones de verosimilitud de clase para medir el rendimiento de la clasificación"}} sklearn/datasets -.-> lab-49196{{"Razones de verosimilitud de clase para medir el rendimiento de la clasificación"}} ml/sklearn -.-> lab-49196{{"Razones de verosimilitud de clase para medir el rendimiento de la clasificación"}} end

Preparación de los Datos

Generaremos un conjunto de datos sintético utilizando la función make_classification de scikit-learn. Este conjunto de datos simulará una población con una minoría de sujetos que padecen una enfermedad.

Análisis pre-prueba vs. post-prueba

Ajustaremos un modelo de regresión logística a los datos y evaluaremos su rendimiento en un conjunto de prueba reservado. Calcularemos la razón de verosimilitud positiva para evaluar la utilidad de este clasificador como herramienta de diagnóstico de enfermedades.

Validación cruzada de las razones de verosimilitud

Evaluaremos la variabilidad de las mediciones de las razones de verosimilitud de clase en algunos casos particulares utilizando validación cruzada.

Invarianza con respecto a la prevalencia

Mostraremos que las razones de verosimilitud de clase son independientes de la prevalencia de la enfermedad y se pueden extrapolar entre poblaciones independientemente de cualquier posible desequilibrio de clases.

Preparación de los datos

Generaremos un conjunto de datos sintético utilizando la función make_classification de scikit-learn. Este conjunto de datos simulará una población con una minoría de sujetos que padecen una enfermedad.

from sklearn.datasets import make_classification

X, y = make_classification(n_samples=10_000, weights=[0.9, 0.1], random_state=0)
print(f"Percentage of people carrying the disease: {100*y.mean():.2f}%")

Análisis pre-prueba vs. post-prueba

Ajustaremos un modelo de regresión logística a los datos y evaluaremos su rendimiento en un conjunto de prueba reservado. Calcularemos la razón de verosimilitud positiva para evaluar la utilidad de este clasificador como herramienta de diagnóstico de enfermedades.

from sklearn.model_selection import train_test_split
from sklearn.metrics import class_likelihood_ratios
from sklearn.linear_model import LogisticRegression

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

estimator = LogisticRegression().fit(X_train, y_train)
y_pred = estimator.predict(X_test)
pos_LR, neg_LR = class_likelihood_ratios(y_test, y_pred)

print(f"LR+: {pos_LR:.3f}")

Validación cruzada de las razones de verosimilitud

Evaluaremos la variabilidad de las mediciones de las razones de verosimilitud de clase en algunos casos particulares utilizando validación cruzada.

import pandas as pd
from sklearn.model_selection import cross_validate
from sklearn.dummy import DummyClassifier

def scoring(estimator, X, y):
    y_pred = estimator.predict(X)
    pos_lr, neg_lr = class_likelihood_ratios(y, y_pred, raise_warning=False)
    return {"positive_likelihood_ratio": pos_lr, "negative_likelihood_ratio": neg_lr}

def extract_score(cv_results):
    lr = pd.DataFrame(
        {
            "positive": cv_results["test_positive_likelihood_ratio"],
            "negative": cv_results["test_negative_likelihood_ratio"],
        }
    )
    return lr.aggregate(["mean", "std"])

estimator = LogisticRegression()
extract_score(cross_validate(estimator, X, y, scoring=scoring, cv=10))

estimator = DummyClassifier(strategy="stratified", random_state=1234)
extract_score(cross_validate(estimator, X, y, scoring=scoring, cv=10))

estimator = DummyClassifier(strategy="most_frequent")
extract_score(cross_validate(estimator, X, y, scoring=scoring, cv=10))

Invarianza con respecto a la prevalencia

Mostraremos que las razones de verosimilitud de clase son independientes de la prevalencia de la enfermedad y se pueden extrapolar entre poblaciones independientemente de cualquier posible desequilibrio de clases.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.inspection import DecisionBoundaryDisplay
from collections import defaultdict

populations = defaultdict(list)
common_params = {
    "n_samples": 10_000,
    "n_features": 2,
    "n_informative": 2,
    "n_redundant": 0,
    "random_state": 0,
}
weights = np.linspace(0.1, 0.8, 6)
weights = weights[::-1]

## fit and evaluate base model on balanced classes
X, y = make_classification(**common_params, weights=[0.5, 0.5])
estimator = LogisticRegression().fit(X, y)
lr_base = extract_score(cross_validate(estimator, X, y, scoring=scoring, cv=10))
pos_lr_base, pos_lr_base_std = lr_base["positive"].values
neg_lr_base, neg_lr_base_std = lr_base["negative"].values

## We will now show the decision boundary for each level of prevalence. Note that
## we only plot a subset of the original data to better assess the linear model
## decision boundary.

fig, axs = plt.subplots(nrows=3, ncols=2, figsize=(15, 12))

for ax, (n, weight) in zip(axs.ravel(), enumerate(weights)):
    X, y = make_classification(
        **common_params,
        weights=[weight, 1 - weight],
    )
    prevalence = y.mean()
    populations["prevalence"].append(prevalence)
    populations["X"].append(X)
    populations["y"].append(y)

    ## down-sample for plotting
    rng = np.random.RandomState(1)
    plot_indices = rng.choice(np.arange(X.shape[0]), size=500, replace=True)
    X_plot, y_plot = X[plot_indices], y[plot_indices]

    ## plot fixed decision boundary of base model with varying prevalence
    disp = DecisionBoundaryDisplay.from_estimator(
        estimator,
        X_plot,
        response_method="predict",
        alpha=0.5,
        ax=ax,
    )
    scatter = disp.ax_.scatter(X_plot[:, 0], X_plot[:, 1], c=y_plot, edgecolor="k")
    disp.ax_.set_title(f"prevalence = {y_plot.mean():.2f}")
    disp.ax_.legend(*scatter.legend_elements())

def scoring_on_bootstrap(estimator, X, y, rng, n_bootstrap=100):
    results_for_prevalence = defaultdict(list)
    for _ in range(n_bootstrap):
        bootstrap_indices = rng.choice(
            np.arange(X.shape[0]), size=X.shape[0], replace=True
        )
        for key, value in scoring(
            estimator, X[bootstrap_indices], y[bootstrap_indices]
        ).items():
            results_for_prevalence[key].append(value)
    return pd.DataFrame(results_for_prevalence)

results = defaultdict(list)
n_bootstrap = 100
rng = np.random.default_rng(seed=0)

for prevalence, X, y in zip(
    populations["prevalence"], populations["X"], populations["y"]
):
    results_for_prevalence = scoring_on_bootstrap(
        estimator, X, y, rng, n_bootstrap=n_bootstrap
    )
    results["prevalence"].append(prevalence)
    results["metrics"].append(
        results_for_prevalence.aggregate(["mean", "std"]).unstack()
    )

results = pd.DataFrame(results["metrics"], index=results["prevalence"])
results.index.name = "prevalence"
results

fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(15, 6))
results["positive_likelihood_ratio"]["mean"].plot(
    ax=ax1, color="r", label="extrapolation through populations"
)
ax1.axhline(y=pos_lr_base + pos_lr_base_std, color="r", linestyle="--")
ax1.axhline(
    y=pos_lr_base - pos_lr_base_std,
    color="r",
    linestyle="--",
    label="base model confidence band",
)
ax1.fill_between(
    results.index,
    results["positive_likelihood_ratio"]["mean"]
    - results["positive_likelihood_ratio"]["std"],
    results["positive_likelihood_ratio"]["mean"]
    + results["positive_likelihood_ratio"]["std"],
    color="r",
    alpha=0.3,
)
ax1.set(
    title="Positive likelihood ratio",
    ylabel="LR+",
    ylim=[0, 5],
)
ax1.legend(loc="lower right")

ax2 = results["negative_likelihood_ratio"]["mean"].plot(
    ax=ax2, color="b", label="extrapolation through populations"
)
ax2.axhline(y=neg_lr_base + neg_lr_base_std, color="b", linestyle="--")
ax2.axhline(
    y=neg_lr_base - neg_lr_base_std,
    color="b",
    linestyle="--",
    label="base model confidence band",
)
ax2.fill_between(
    results.index,
    results["negative_likelihood_ratio"]["mean"]
    - results["negative_likelihood_ratio"]["std"],
    results["negative_likelihood_ratio"]["mean"]
    + results["negative_likelihood_ratio"]["std"],
    color="b",
    alpha=0.3,
)
ax2.set(
    title="Negative likelihood ratio",
    ylabel="LR-",
    ylim=[0, 0.5],
)
ax2.legend(loc="lower right")

plt.show()

Resumen

En este laboratorio, hemos aprendido cómo calcular las razones de verosimilitud positiva y negativa para evaluar el poder predictivo de un clasificador binario. Estas métricas son independientes de la proporción entre clases en el conjunto de prueba, lo que las hace muy útiles cuando los datos disponibles para un estudio tienen una proporción de clases diferente a la de la aplicación objetivo. También aprendimos cómo evaluar la variabilidad de las mediciones de las razones de verosimilitud de clase en algunos casos particulares utilizando validación cruzada y cómo demostrar que las razones de verosimilitud de clase son independientes de la prevalencia de la enfermedad.