Abrir en Google Colab
|
|
Descargar notebook
|
Interpretación de modelos tabulares con feature importance
Introducción
Una de las preguntas más básicas que podemos hacer sobre un modelo es qué características tienen el mayor impacto en las predicciones. Este concepto se llama importancia y se basa en la idea de que las características más importantes tienen un mayor impacto.
Sin embargo, ¿cómo podemos saber cuánto impacto tiene una feature en la predicción? Para responder a esto, tenemos que mirar un problema desde otra perspectiva: “si una característica es importante, cuando falta, la precisión del modelo disminuiría”. Este método también se llama Mean Decrease Accuracy (MDA, Breiman (2001)).
Para una introducción más detallada puede ver la entrada del blog: Model interpretability — Making your model confesses: Feature importance
Utilizando feature importance en el problema censo de la UCI
Veamos como utilizar esta técnica sobre el conjunto de datos y modelo del problema de censo de la UCI.
Instalación
Necesitaremos instalar las librerias:
[9]:
!pip install eli5 --quiet
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 216.2/216.2 kB 5.7 MB/s eta 0:00:00
Preparing metadata (setup.py) ... done
Building wheel for eli5 (setup.py) ... done
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
[2]:
!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
Lo importamos
[3]:
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')
Generaremos 3 conjuntos de datos: entrenamiento, validación y testing.
[4]:
from sklearn.model_selection import train_test_split
validation = test
test, _ = train_test_split(test, test_size=0.9, random_state=1234)
Separemos los predictores de la variable a predecir:
[5]:
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()
X_val = validation.drop(['income'], axis=1)
y_val = validation['income'].to_numpy()
Entrenando un modelo para explorar
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
[25]:
from typing import Tuple, List
import sklearn
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.compose import ColumnTransformer
def prepare(X: pd.DataFrame, transformations: sklearn.compose.ColumnTransformer = None) -> Tuple[pd.DataFrame, sklearn.compose.ColumnTransformer]:
"""
Escala y codifica los deferentes valores de un conjunto de datos.
Parameters
----------
X: pd.DataFrame:
Connto de datos a transformar
transformations: sklearn.compose.ComlumnTransformer
Transformaciones que se deben aplicar al conjunto de datos. Si no son indicadas, las mismas son aprendidas desde el conjunto de datos.
Returns: Tuple[pd.DataFrame, sklearn.compose.ColumnTransformer]
Una tupla donde el primer component es el conjunto de datos transformado y el segundo las transformaciones que se aplicaron.
"""
features = {
'discrete': X.dtypes[X.dtypes == 'object' ].index.tolist(),
'continuous': X.dtypes[X.dtypes != 'object'].index.tolist(),
}
num_pipe = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
cat_pipe = Pipeline([
('imputer', SimpleImputer(strategy='constant', fill_value='NA')),
('encoder', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1))
])
if transformations is None:
transformations = ColumnTransformer(
[
('continuous_pipe', num_pipe, features['continuous']),
('discrete_pipe', cat_pipe, features['discrete']),
],
remainder='passthrough')
X = transformations.fit_transform(X)
else:
X = transformations.transform(X)
transformed_discrete_features = transformations.transformers_[1][1].named_steps['encoder'].get_feature_names_out(features['discrete'])
all_features = features['continuous'] + list(transformed_discrete_features)
return pd.DataFrame(X, columns=all_features), transformations
X_train_transformed, transformations = prepare(X_train)
X_test_transformed, _ = prepare(X_test, transformations)
Entrenamos un modelo basado en lightgbm
[32]:
from lightgbm import LGBMClassifier
clf = LGBMClassifier(n_estimators=10000)
model = clf.fit(X_train_transformed, y_train)
Computando los valores de importancia:
Computemos algunos datos que serán útiles luego:
classescontiene los nombres de las clases que vamos a predecirfeaturescontiene el nombre de todas las columnas del conjunto de datos.categorical_featurescontiene un dictionario que tiene como índice
[33]:
classes = train['income'].unique().tolist()
features = X_train_transformed.columns.values.tolist()
categorical_features = {idx: X_train_transformed.columns[idx] for idx, col in enumerate(X_train_transformed.dtypes) if col == object}
Utilizando la librería eli5 computaremos la importancia de las características utilizando un conjunto de datos de validación:
[34]:
import eli5
from eli5.sklearn import PermutationImportance
perm = PermutationImportance(model, random_state=123).fit(X_test_transformed, y_test)
Una vez que computamos las permutaciones, podemos visualizarlas:
[35]:
eli5.show_weights(perm, feature_names = features)
[35]:
| Weight | Feature |
|---|---|
| 0.0491 ± 0.0041 | capital-gain |
| 0.0201 ± 0.0082 | marital-status |
| 0.0123 ± 0.0030 | capital-loss |
| 0.0104 ± 0.0097 | education-num |
| 0.0070 ± 0.0051 | age |
| 0.0041 ± 0.0094 | occupation |
| 0.0012 ± 0.0044 | race |
| 0.0000 ± 0.0050 | relationship |
| -0.0004 ± 0.0044 | gender |
| -0.0017 ± 0.0021 | native-country |
| -0.0020 ± 0.0125 | hours-per-week |
| -0.0028 ± 0.0076 | fnlwgt |
| -0.0032 ± 0.0115 | education |
| -0.0066 ± 0.0052 | workclass |
Cómo interpretar los números
La tabla anterior muestra la importancia de las características de cada una de las columnas. La columna de peso representa la importancia de la característica. El signo +/- representa la desviación estándar de la importancia calculada anteriormente. Este valor trata de medir la cantidad de aleatoriedad en nuestro cálculo de la importancia de la permutación repitiendo el proceso con múltiples mezclas. La columna de peso representa entonces la media del error acumulado en las múltiples mezclas en lugar de en una sola prueba.
Curiosamente, puede ver valores negativos para la importancia. En esos casos, las predicciones sobre los datos mezclados (o ruidosos) resultaron ser más precisas que los datos reales. Esto sucede cuando la característica no importaba y debería haber tenido una importancia cercana a 0, pero la probabilidad aleatoria hizo que las predicciones sobre los datos mezclados fueran más precisas.
Abrir en Google Colab
Descargar notebook