Más sobre ciencia de datos: cienciadedatos.net
Los gráficos Individual Conditional Expectation (ICE) y Partial Dependence (PDP) muestran cómo varían las predicciones de un modelo de machine learning en función del valor que toma alguno de sus predictores. Además de ser muy útiles para entender la relación entre la variable respuesta y los predictores, permiten diferenciar cuándo dicha relación es aditiva o está afectada por interacciones con otros predictores. También permiten entender cómo se comporta un modelo cuando se extrapola a regiones para las que no se dispone de observaciones.
Los gráficos PDP muestran, con una única curva, cómo varía en promedio la predicción de la variable respuesta a medida que se modifica uno de los predictores, manteniendo constante el resto. Los gráficos ICE muestran cómo varía la predicción para cada una de las observaciones a medida que se modifica uno de los predictores, manteniendo constante el resto. Son por lo tanto una extensión más detallada de los gráficos PDP.
Tres de las librerías que permiten crear gráficos ICE y PDP en python son sklearn.inspection, PyCEbox y PDPbox.
La versión de scikit-learn 0.23
solo permite la creación de gráficos PDP, la versión scikit-learn 0.24
(todavía en desarrollo en el momento de escribir este documento) sí permite crear las curvas ICE.
La librería PyCEbox
ya no está mantenida por lo que, aunque muy fácil de utilizar, podría dejar de funcionar con futuras versiones.
La librería PDPbox
está activamente mantenida y permite la creación de múltiples gráficos exploratorios de modelos, entre ellos, PDP e ICE.
A lo largo de este documento se muestran ejemplos de cómo crear e interpretar gráficos ICE y PDP con la librería PDPbox
.
El set de datos Boston
contiene información sobre la mediana del precio en las viviendas de la ciudad de Boston junto con información de las características de las casas y la zona donde se encuentran.
# Configuración warnings
# ==============================================================================
import warnings
warnings.filterwarnings('ignore')
# Librerías
# ==============================================================================
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.inspection import plot_partial_dependence
from pdpbox import pdp
# Datos
# ==============================================================================
boston = load_boston(return_X_y=False)
datos = pd.DataFrame(boston.data, columns = boston.feature_names)
datos['MEDV'] = boston.target
datos.head()
Se entrena un modelo de tipo Random Forest con el objetivo de predecir el precio de la vivienda (MEDV
) en función de todas las demás variables disponibles.
# Modelo
# ==============================================================================
X_train = datos.drop(columns=['MEDV'])
y_train = datos['MEDV']
modelo = RandomForestRegressor(n_estimators=500, max_depth=4)
modelo.fit(
X_train,
y_train
)
Una vez entrenado el modelo, con las funciones pdp_isolate()
y pdp_plot()
del paquete pdpbox
se pueden obtener los gráficos ICE y PDP de cualquiera los predictores.
La función pdp_isolate()
crea un dataframe con los valores de cada curva. Sus principales argumentos son:
dataset
: dataframe con el valor de los predictores, normalmente el de los datos de entrenamiento.
feature
: nombre del predictor cuyas curvas ICE se quieren calcular.
model_features
: nombre de todos los predictores del modelo.
model
: modelo entrenado sobre el que se quieren generar las curvas.
num_grid_points
: número de puntos calculados para cada curva ICE. Si el set de datos es muy grande, se recomienda reducirlo.
La función pdp_plot()
emplea los resultados generados con pdp_isolate()
para representar el gráfico. Sus principales argumentos son:
pdp_isolate_out
: resultados generados con pdp_isolate()
.
feature_name
: nombre del predictor cuyas curvas ICE se quieren representar.
frac_to_plot
: fracción de curvas ICE que se muestran en el gráfico.
plot_pts_dist
: si se muestra o no la distribución de las observaciones originales en el conjunto de entrenamiento.
center
: centrar las curvas en cero.
A continuación, se explora la influencia que tiene la antigüedad de la vivienda (AGE
) sobre el precio de la vivienda (MEDV
).
# Cálculo curvas ICE
# ==============================================================================
ice_df = pdp.pdp_isolate(
model = modelo,
dataset = X_train,
feature = 'AGE',
model_features = X_train.columns,
num_grid_points=20,
grid_type='percentile'
)
# Gráfico curvas ICE
# ==============================================================================
fig, axes = pdp.pdp_plot(
ice_df,
feature_name = 'AGE',
center = False,
plot_pts_dist = True,
plot_lines = True,
frac_to_plot = 1,
x_quantile = False,
show_percentile=False,
figsize = (9, 8),
ncols = 1,
plot_params = {'fill_alpha': 0.7},
which_classes = None,
)
# Para mostrar el valor real de las observaciones de entrenamiento
axes['pdp_ax']['_pdp_ax'].scatter(
X_train.AGE,
modelo.predict(X_train),
color = 'k',
alpha = 0.4
);
Cada curva del gráfico anterior (curva ICE) muestra el valor predicho por el modelo para cada observación, conforme se va aumentando el valor de AGE
y manteniendo constantes el resto de predictores en su valor observado. La curva resaltada en amarillo se corresponde con la curva PDP, que es la variación promedio de todas las observaciones. Además, el gráfico incluye puntos que representan el verdadero valor de AGE
de cada observación.
La gran mayoría de las curvas son planas, lo que apunta a que, en la mayor parte de los casos, la antigüedad de la vivienda apenas influye en su precio. Sin embargo, puede apreciarse que, unas pocas observaciones, presentan una ligera tendencia de subida o bajada.
Cuando los valores observados de la variable respuesta se acumulan en una región pequeña, el solapamiento de las curvas puede hacer difícil distinguir qué observaciones se escapan de la tendencia general. Para evitar este problema, se puede recurrir a los gráficos ICE centrados (c-ICE). Los gráficos c-ICE se obtienen igual que los gráficos ICE con la única diferencia de que, a cada una de las curvas, se les resta un valor de referencia, normalmente el valor predicho para el mínimo observado del predictor (primer valor de la curva). De esta forma, se consigue que todas las curvas tengan su origen en el 0.
Con esta nueva representación puede observarse con más claridad que, aunque la mayoría de observaciones se mantienen constantes, algunas tienen un claro patrón divergente (para algunas el precio incrementa con la antigüedad y en otras disminuye). Tal y como se describe más adelante, esto suele ser un indicativo de que el predictor AGE
interacciona con otros predictores.
# Gráfico curvas ICE centradas
# ==============================================================================
fig, axes = pdp.pdp_plot(
ice_df,
feature_name = 'AGE',
center = True,
plot_pts_dist = True,
plot_lines = True,
frac_to_plot = 1,
x_quantile = False,
show_percentile=False,
figsize = (9, 8),
ncols = 2,
which_classes = None,
)
# Para mostrar el valor real de las observaciones de entrenamiento
axes['pdp_ax']['_pdp_ax'].scatter(
X_train.AGE,
modelo.predict(X_train) - ice_df.ice_lines.transpose().iloc[0,:],
color = 'k',
alpha = 0.4
);
Como se ha visto en los apartados anteriores, algunas observaciones pueden alejarse de la tendencia general del modelo. Una forma de conseguir información extra que permita comprender las razones de estos patrones divergentes, es colorear las curvas de cada observación (o los puntos de cada observación) en función de otro factor.
Por ejemplo, se colorean los puntos en función de una nueva variable binaria que indique si el número de habitaciones de la vivienda está por encima o por debajo de la mediana.
# Color de cada observación
# ==============================================================================
mediana_habitaciones = np.median(X_train.RM)
colores = np.where(X_train.RM > mediana_habitaciones, 'b', 'r')
# Gráfico curvas ICE centradas
# ==============================================================================
fig, axes = pdp.pdp_plot(
ice_df,
feature_name = 'AGE',
center = True,
plot_pts_dist = True,
plot_lines = True,
frac_to_plot = 1,
x_quantile = False,
show_percentile=False,
figsize = (9, 8),
ncols = 2,
which_classes = None,
)
# Para mostrar el valor real de las observaciones de entrenamiento
# coloreadas en función de la nueva clasificación
axes['pdp_ax']['_pdp_ax'].scatter(
X_train.AGE,
modelo.predict(X_train) - ice_df.ice_lines.transpose().iloc[0,:],
color = colores,
alpha = 0.5
);
Gracias a los colores puede verse claramente que, para viviendas con un número de habitaciones por encima de la mediana (azul), la antigüedad de la vivienda está asociada positivamente con el precio. Para viviendas con un número de habitaciones inferior a la media, ocurre lo contrario.
En la introducción de este documento se menciona que la diferencia entre los gráficos ICE y los PDP es que ,los segundos, muestran una única curva creada con el promedio de todas las curvas ICE. La ventaja de los gráficos ICE queda patente cuando existe interacción entre predictores o cuando no todas las observaciones siguen una misma tendencia. Véase el siguiente ejemplo ilustrativo.
Se simula un set de datos siguiendo la siguiente ecuación:
$$Y = 0.2 X_1 - 5X_2 + 10 X_2 \mathbf{1}_{X_3 \geq 0} + \epsilon$$$$Y=\begin{cases} 0.2X_1 - 5X_2 + 10X_2 + \epsilon & \text{ si } X_3 \geq0 \\ 0.2X_1 - 5X_2 \epsilon & \text{ si } X_3 < 0 \end{cases}$$$$\epsilon \sim N(0,1) \ \ \ \ X_1,X_2,X_3 \sim U(-1,1)$$# Librerías
# ==============================================================================
import scipy as sp
import matplotlib.pyplot as plt
from sklearn.ensemble import GradientBoostingRegressor
# Datos simulados
# ==============================================================================
N = 1000
df = pd.DataFrame(
sp.stats.uniform.rvs(-1, 2, size=(N, 3)),
columns=['x1', 'x2', 'x3']
)
noise = sp.stats.norm.rvs(size=N)
y = 0.2 * df.x1 - 5 * df.x2 + 10 * df.x2 * (df.x3 >= 0) + noise
fig, ax = plt.subplots(figsize=(8, 4.5))
ax.scatter(df.x2, y, c='k', alpha=0.5)
ax.set_xlim(-1.05, 1.05);
ax.set_xlabel('$X_2$')
ax.set_ylabel('$Y$')
ax.set_title('Datos');
Se entrena un modelo Gradient Boosting para predecir $Y$ en función de las 3 variables disponibles.
# Modelo
# ==============================================================================
modelo = GradientBoostingRegressor()
modelo.fit(df.values, y)
# Gráfico curvas ICE
# ==============================================================================
ice_df = pdp.pdp_isolate(
model = modelo,
dataset = df,
feature = 'x2',
model_features = df.columns,
num_grid_points=20,
grid_type='percentile'
)
fig, axes = pdp.pdp_plot(
ice_df,
feature_name = 'x2',
center = False,
plot_pts_dist = True,
plot_lines = True,
frac_to_plot = 1,
x_quantile = False,
show_percentile=False,
figsize = (9, 8),
ncols = 1,
plot_params = {'fill_alpha': 0.7},
which_classes = None,
)
Viendo únicamente la curva PDP (amarillo), podría asumirse que, la variable respuesta, apenas varía en función del valor que tome el predictor $X2$. Esta interpretación puede llevar a conclusiones erróneas ya que, el promedio, está ocultando el verdadero comportamiento individual de las observaciones.
from sinfo import sinfo
sinfo()
Goldstein, Alex & Kapelner, Adam & Bleich, Justin & Pitkin, Emil. (2013). Peeking Inside the Black Box: Visualizing Statistical Learning With Plots of Individual Conditional Expectation. Journal of Computational and Graphical Statistics.
Interpretable Machine Learning A Guide for Making Black Box Models Explainable by Christoph Molnar libro
¿Cómo citar este documento?
Gráficos ICE y PDP para interpretar modelos predictivos en python por Joaquín Amat Rodrigo, disponible con licencia CC BY-NC-SA 4.0 en https://www.cienciadedatos.net/documentos/py16-interpretacion-modelos-graficos-pdp-ice.html
¿Te ha gustado el artículo? Tu ayuda es importante
Mantener un sitio web tiene unos costes elevados, tu contribución me ayudará a seguir generando contenido divulgativo gratuito. ¡Muchísimas gracias! 😊
Este contenido, creado por Joaquín Amat Rodrigo, tiene licencia Attribution-NonCommercial-ShareAlike 4.0 International.
Se permite:
Compartir: copiar y redistribuir el material en cualquier medio o formato.
Adaptar: remezclar, transformar y crear a partir del material.
Bajo los siguientes términos:
Atribución: Debes otorgar el crédito adecuado, proporcionar un enlace a la licencia e indicar si se realizaron cambios. Puedes hacerlo de cualquier manera razonable, pero no de una forma que sugiera que el licenciante te respalda o respalda tu uso.
NoComercial: No puedes utilizar el material para fines comerciales.
CompartirIgual: Si remezclas, transformas o creas a partir del material, debes distribuir tus contribuciones bajo la misma licencia que el original.