Abrir en Google Colab
|
|
Descargar notebook
|
Ejemplo: Comparando modelos utilizando una prueba de McNemar
Introducción
Instalamos la librerias necesarias
[ ]:
!wget https://raw.githubusercontent.com/santiagxf/E72102/master/docs/develop/modeling/selection/code/mcnemar.txt \
--quiet --no-clobber
!pip install -r mcnemar.txt --quiet
Sobre el conjunto de datos del censo UCI
El conjunto de datos del censo de la UCI es un conjunto de datos en el que cada registro representa a una persona. Cada registro contiene 14 columnas que describen a una una sola persona, de la base de datos del censo de Estados Unidos de 1994. Esto incluye información como la edad, el estado civil y el nivel educativo. La tarea es determinar si una persona tiene un ingreso alto (definido como ganar más de $50 mil al año). Esta tarea, dado el tipo de datos que utiliza, se usa a menudo en el estudio de equidad, en parte debido a los atributos comprensibles del conjunto de datos, incluidos algunos que contienen tipos sensibles como la edad y el género, y en parte también porque comprende una tarea claramente del mundo real.
Descargamos el conjunto de datos
[ ]:
!wget https://santiagxf.blob.core.windows.net/public/datasets/uci_census.zip \
--quiet --no-clobber
!mkdir -p datasets/uci_census
!unzip -qq uci_census.zip -d datasets/uci_census
Preparando nuestros conjuntos de datos
[1]:
import pandas as pd
import numpy as np
train = pd.read_csv('datasets/uci_census/data/adult-train.csv')
test = pd.read_csv('datasets/uci_census/data/adult-test.csv')
X_train = train.drop(['income'], axis=1)
y_train = train['income'].to_numpy()
X_test = test.drop(['income'], axis=1)
y_test = test['income'].to_numpy()
Preparación de los datos para el ejemplo
Realizaremos un pequeño preprocesamiento antes de entrenar el modelo:
Imputaremos los valores faltantes de las caracteristicas numéricas con la media
Imputaremos los valores faltantes de las caracteristicas categóricas con el valor
?Escalaremos los valores numericos utilizando un
StandardScalerCodificaremos las variables categóricas utilizando
OneHotEncoder
[3]:
from typing import Tuple, List
import sklearn
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
def prepare(X: pd.DataFrame) -> Tuple[np.ndarray, sklearn.compose.ColumnTransformer]:
pipe_cfg = {
'num_cols': X.dtypes[X.dtypes == 'int64'].index.values.tolist(),
'cat_cols': X.dtypes[X.dtypes == 'object'].index.values.tolist(),
}
num_pipe = Pipeline([
('num_imputer', SimpleImputer(strategy='median')),
('num_scaler', StandardScaler())
])
cat_pipe = Pipeline([
('cat_imputer', SimpleImputer(strategy='constant', fill_value='?')),
('cat_encoder', OneHotEncoder(handle_unknown='ignore', sparse=False))
])
transformations = ColumnTransformer([
('num_pipe', num_pipe, pipe_cfg['num_cols']),
('cat_pipe', cat_pipe, pipe_cfg['cat_cols'])
])
X = transformations.fit_transform(X)
return X, transformations
X_train_transformed, transformations = prepare(X_train)
X_test_transformed = transformations.transform(X_test)
Definiendo nuestros modelos a comparar
Para demostrar la técnica, utilizaremos dos clasificadores basados en LightGBM
[14]:
from lightgbm import LGBMClassifier
clf1 = LGBMClassifier(n_estimators=100, n_jobs=2)
clf1.fit(X_train_transformed, y_train)
clf2 = LGBMClassifier(n_estimators=100, reg_alpha=1, min_split_gain=2, n_jobs=2)
clf2.fit(X_train_transformed, y_train)
[14]:
LGBMClassifier(min_split_gain=2, n_jobs=2, reg_alpha=1)
Evaluemos su performance:
[15]:
clf1_pred = clf1.predict(X_test_transformed)
clf2_pred = clf2.predict(X_test_transformed)
[22]:
from sklearn.metrics import accuracy_score
print(f'Modelo 1: {accuracy_score(clf1_pred, y_test):.3g}')
print(f'Modelo 2: {accuracy_score(clf2_pred, y_test):.3g}')
Modelo 1: 0.875
Modelo 2: 0.874
Pareciera que el modelo 1 tiene ligeramente una mejor performance. Verifiquemos si vale la pena:
Procedimiento de McNemar
Construimos una tabla de contingencia
La prueba de McNemar se base en una matrix de contingencia de 2x2 donde en las filas tenemos las diferentes instancias de datos, y en las columnas tenemos un idicador mencionando si el modelo realizó una predicción correcta o no. Esto es equivalente a generar una matriz de confusión entre los ambos modelos.
[16]:
from sklearn.metrics import confusion_matrix
cont_table = confusion_matrix(clf1_pred, clf2_pred)
Computamos el valor estadístico
En base a esta tabla, el valor estádistico de McNemar se calcula, para dos modelos:
[17]:
from statsmodels.stats.contingency_tables import mcnemar
results = mcnemar(cont_table, exact=False)
print(results)
pvalue 0.1581559805552949
statistic 1.991769547325103
Tomamos una decisión
[18]:
if results.pvalue <= 0.05:
print("Rechazamos la hipótesis nula en favor de la alternativa para concluir que los modelos no cometen mismos errores.")
else:
print("No podemos rechazar la hipotesis de que ambos modelos cometen los mismos errores.")
No podemos rechazar la hipotesis de que ambos modelos cometen los mismos errores.
Abrir en Google Colab
Descargar notebook