Gradient Boosting con Características Categóricas

Beginner

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

Introducción

En este laboratorio, usaremos el conjunto de datos de viviendas de Ames para comparar diferentes métodos de manejo de características categóricas en estimadores de Gradient Boosting. El conjunto de datos contiene características numéricas y categóricas, y la variable objetivo es el precio de venta de las casas. Compararemos el rendimiento de cuatro tuberías diferentes:

  • Eliminando las características categóricas
  • Codificación one-hot de las características categóricas
  • Tratando las características categóricas como valores ordinales
  • Usando el soporte nativo para características categóricas en el estimador de Gradient Boosting

Evaluaremos las tuberías en términos de sus tiempos de ajuste y rendimientos de predicción usando validación cruzada.

Consejos sobre la VM

Una vez finalizada la inicialización de la VM, haga clic en la esquina superior izquierda para cambiar a la pestaña Cuaderno y acceder a Jupyter Notebook para practicar.

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

Si tiene problemas durante el aprendizaje, no dude en preguntar a Labby. Deje comentarios después de la sesión y resolveremos rápidamente el problema para usted.

Cargar el conjunto de datos

Cargaremos el conjunto de datos de viviendas de Ames usando la función fetch_openml de Scikit-Learn y seleccionaremos un subconjunto de las características para que el ejemplo se ejecute más rápido. También convertiremos las características categóricas al tipo de datos 'category'.

from sklearn.datasets import fetch_openml

X, y = fetch_openml(data_id=42165, as_frame=True, return_X_y=True, parser="pandas")

## Selecciona solo un subconjunto de las características de X para que el ejemplo se ejecute más rápido
categorical_columns_subset = [
    "BldgType",
    "GarageFinish",
    "LotConfig",
    "Functional",
    "MasVnrType",
    "HouseStyle",
    "FireplaceQu",
    "ExterCond",
    "ExterQual",
    "PoolQC",
]

numerical_columns_subset = [
    "3SsnPorch",
    "Fireplaces",
    "BsmtHalfBath",
    "HalfBath",
    "GarageCars",
    "TotRmsAbvGrd",
    "BsmtFinSF1",
    "BsmtFinSF2",
    "GrLivArea",
    "ScreenPorch",
]

X = X[categorical_columns_subset + numerical_columns_subset]
X[categorical_columns_subset] = X[categorical_columns_subset].astype("category")

Tubo base - Eliminar características categóricas

Crearemos un tubo en el que eliminamos las características categóricas y entrenamos un estimador HistGradientBoostingRegressor.

from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.pipeline import make_pipeline
from sklearn.compose import make_column_transformer
from sklearn.compose import make_column_selector

dropper = make_column_transformer(
    ("drop", make_column_selector(dtype_include="category")), remainder="passthrough"
)
hist_dropped = make_pipeline(dropper, HistGradientBoostingRegressor(random_state=42))

Tubo de codificación one-hot

Crearemos un tubo en el que codificamos one-hot las características categóricas y entrenamos un estimador HistGradientBoostingRegressor.

from sklearn.preprocessing import OneHotEncoder

one_hot_encoder = make_column_transformer(
    (
        OneHotEncoder(sparse_output=False, handle_unknown="ignore"),
        make_column_selector(dtype_include="category"),
    ),
    remainder="passthrough",
)

hist_one_hot = make_pipeline(
    one_hot_encoder, HistGradientBoostingRegressor(random_state=42)
)

Tubo de codificación ordinal

Crearemos un tubo en el que tratamos las características categóricas como valores ordinales y entrenamos un estimador HistGradientBoostingRegressor. Usaremos un OrdinalEncoder para codificar las características categóricas.

from sklearn.preprocessing import OrdinalEncoder
import numpy as np

ordinal_encoder = make_column_transformer(
    (
        OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=np.nan),
        make_column_selector(dtype_include="category"),
    ),
    remainder="passthrough",
    verbose_feature_names_out=False,
)

hist_ordinal = make_pipeline(
    ordinal_encoder, HistGradientBoostingRegressor(random_state=42)
)

Tubo de soporte nativo para variables categóricas

Crearemos un tubo en el que usaremos el soporte nativo para variables categóricas del estimador HistGradientBoostingRegressor para manejar las características categóricas. Todavía usaremos un OrdinalEncoder para pre-procesar los datos.

hist_native = make_pipeline(
    ordinal_encoder,
    HistGradientBoostingRegressor(
        random_state=42,
        categorical_features=categorical_columns,
    ),
).set_output(transform="pandas")

Comparación de modelos

Compararemos el rendimiento de los cuatro tubos de procesamiento de datos utilizando validación cruzada y graficaremos los tiempos de ajuste y las puntuaciones de error absoluto promedio porcentual.

from sklearn.model_selection import cross_validate
import matplotlib.pyplot as plt

scoring = "neg_mean_absolute_percentage_error"
n_cv_folds = 3

dropped_result = cross_validate(hist_dropped, X, y, cv=n_cv_folds, scoring=scoring)
one_hot_result = cross_validate(hist_one_hot, X, y, cv=n_cv_folds, scoring=scoring)
ordinal_result = cross_validate(hist_ordinal, X, y, cv=n_cv_folds, scoring=scoring)
native_result = cross_validate(hist_native, X, y, cv=n_cv_folds, scoring=scoring)

def plot_results(figure_title):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 8))

    plot_info = [
        ("fit_time", "Tiempos de ajuste (s)", ax1, None),
        ("test_score", "Error Absoluto Promedio Porcentual", ax2, None),
    ]

    x, width = np.arange(4), 0.9
    for key, title, ax, y_limit in plot_info:
        items = [
            dropped_result[key],
            one_hot_result[key],
            ordinal_result[key],
            native_result[key],
        ]

        mape_cv_mean = [np.mean(np.abs(item)) for item in items]
        mape_cv_std = [np.std(item) for item in items]

        ax.bar(
            x=x,
            height=mape_cv_mean,
            width=width,
            yerr=mape_cv_std,
            color=["C0", "C1", "C2", "C3"],
        )
        ax.set(
            xlabel="Modelo",
            title=title,
            xticks=x,
            xticklabels=["Eliminadas", "One Hot", "Ordinal", "Nativas"],
            ylim=y_limit,
        )
    fig.suptitle(figure_title)

plot_results("Gradient Boosting on Ames Housing")

Limitando el número de divisiones

Volveremos a ejecutar el mismo análisis con modelos subajustados en los que limitamos artificialmente el número total de divisiones limitando tanto el número de árboles como la profundidad de cada árbol.

for pipe in (hist_dropped, hist_one_hot, hist_ordinal, hist_native):
    pipe.set_params(
        histgradientboostingregressor__max_depth=3,
        histgradientboostingregressor__max_iter=15,
    )

dropped_result = cross_validate(hist_dropped, X, y, cv=n_cv_folds, scoring=scoring)
one_hot_result = cross_validate(hist_one_hot, X, y, cv=n_cv_folds, scoring=scoring)
ordinal_result = cross_validate(hist_ordinal, X, y, cv=n_cv_folds, scoring=scoring)
native_result = cross_validate(hist_native, X, y, cv=n_cv_folds, scoring=scoring)

plot_results("Gradient Boosting on Ames Housing (few and small trees)")

Resumen

En este laboratorio, comparamos cuatro tubos de procesamiento de datos diferentes para manejar características categóricas en estimadores de Gradient Boosting utilizando el conjunto de datos de la vivienda de Ames. Encontramos que eliminar las características categóricas llevó a un rendimiento de predicción más pobre, y que los tres modelos que utilizaron características categóricas tuvieron tasas de error comparables. Codificar en caliente las características categóricas fue, de lejos, el método más lento, mientras que tratar las características categóricas como valores ordinales y utilizar el soporte nativo para variables categóricas del estimador HistGradientBoostingRegressor tuvo tiempos de ajuste similares. Cuando el número total de divisiones se limitó, la estrategia de soporte nativo para variables categóricas tuvo el mejor rendimiento.