Abrir en Google Colab Abrir en Binder Descargar notebook

Ejemplo: Comparando modelos utilizando 5x2 cross-validation

En este ejemplo, veremos como utilizar la técnica 5x2 cross-validation para la comparación de performance de dos modelos de aprendizaje automático. Para mas detalle de la técnica puede referirse aÑ

  • [1] Dietterich TG (1998) Approximate Statistical Tests for Comparing Supervised Classification Learning Algorithms. Neural Comput 10:1895–1923.

Introducción

Instalamos la librerias necesarias

[ ]:
!wget https://raw.githubusercontent.com/santiagxf/E72102/master/docs/develop/modeling/selection/code/5x2.txt \
    --quiet --no-clobber
!pip install -r 5x2.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 los datos

[5]:
!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

[6]:
import pandas as pd
import numpy as np

df = pd.read_csv('datasets/uci_census/data/adult-train.csv')

train = df.drop(['income'], axis=1)
target = df['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 StandardScaler

  • Codificaremos las variables categóricas utilizando OneHotEncoder

[7]:
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


train, transformations = prepare(train)

Definiendo nuestros modelos a comparar

Para demostrar la técnica, utilizaremos dos clasificadores basados en LightGBM

[57]:
from lightgbm import LGBMClassifier

clf1 = LGBMClassifier(n_estimators=100, n_jobs=2)
clf2 = LGBMClassifier(n_estimators=100, reg_alpha=1, reg_lambda=1, min_split_gain=2, n_jobs=2)

Procedimiento de 5x2

Utilizaremos la libreria mlxtend que dispone de una implementation de este procedimiento. La utilización de la misma es bastante sencilla:

[65]:
from mlxtend.evaluate import paired_ttest_5x2cv

Iniciamos el test:

[69]:
statistic, pvalue = paired_ttest_5x2cv(clf1, clf2,
                             X=train, y=target, scoring='accuracy')

Notemos que aqui las hipótesis nula y alternativa son como sigue:

  • H0: La diferencia en performance de los dos modelos es zero (los modelos son iguales).

  • HA: La diferencia en performance de los modelos es distinta de zero (los modelos son distintos).

Tomamos una decisión:

[70]:
if pvalue > 0.05:
    print("No podemos tomar ninguna conclusión. No se puede rechazar la idea de que ambos modelos son equivalente")
else:
    print("Existe suficiente evidencia para rechazar la idea de que los modelos son equivalentes en favor de \
          una alternativa de que los modelos son distintos.")

print("\nValor estadístico:", statistic)
print("p-value:", pvalue)
No podemos tomar ninguna conclusión. No se puede rechazar la idea de que ambos modelos son equivalente

Valor estadístico: 1.281174535572354
p-value: 0.25633323457069157