Gradient Boosting avec des variables catégorielles

Beginner

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

Introduction

Dans ce laboratoire, nous allons utiliser le jeu de données Ames Housing pour comparer différentes méthodes de gestion des variables catégorielles dans les estimateurs Gradient Boosting. Le jeu de données contient des variables numériques et catégorielles, et la variable cible est le prix de vente des maisons. Nous comparerons les performances de quatre pipelines différents :

  • Suppression des variables catégorielles
  • Encodage one-hot des variables catégorielles
  • Traitement des variables catégorielles comme des valeurs ordinale
  • Utilisation du support natif pour les variables catégorielles dans l'estimateur Gradient Boosting

Nous évaluerons les pipelines en termes de temps d'ajustement et de performances de prédiction en utilisant la validation croisée.

Conseils sur la VM

Une fois le démarrage de la VM terminé, cliquez dans le coin supérieur gauche pour basculer vers l'onglet Notebook pour accéder à Jupyter Notebook pour la pratique.

Parfois, vous devrez peut-être attendre quelques secondes pour que Jupyter Notebook ait fini de charger. La validation des opérations ne peut pas être automatisée en raison des limitations de Jupyter Notebook.

Si vous rencontrez des problèmes pendant l'apprentissage, n'hésitez pas à demander à Labby. Donnez votre feedback après la session, et nous réglerons rapidement le problème pour vous.

Charger le jeu de données

Nous allons charger le jeu de données Ames Housing en utilisant la fonction fetch_openml de Scikit-Learn et sélectionner un sous-ensemble des caractéristiques pour que l'exemple s'exécute plus rapidement. Nous allons également convertir les variables catégorielles au type de données 'category'.

from sklearn.datasets import fetch_openml

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

## Sélectionner seulement un sous-ensemble de caractéristiques de X pour que l'exemple s'exécute plus rapidement
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")

Pipeline de base - Suppression des variables catégorielles

Nous allons créer un pipeline dans lequel nous supprimons les variables catégorielles et entraîner un estimateur 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))

Pipeline d'encodage one-hot

Nous allons créer un pipeline dans lequel nous effectuons un encodage one-hot des variables catégorielles et entraîner un estimateur 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)
)

Pipeline d'encodage ordinal

Nous allons créer un pipeline dans lequel nous traitons les variables catégorielles comme des valeurs ordinale et entraîner un estimateur HistGradientBoostingRegressor. Nous allons utiliser un OrdinalEncoder pour encoder les variables catégorielles.

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)
)

Pipeline de prise en charge native des variables catégorielles

Nous allons créer un pipeline dans lequel nous utilisons la prise en charge native des variables catégorielles de l'estimateur HistGradientBoostingRegressor pour traiter les variables catégorielles. Nous allons toujours utiliser un OrdinalEncoder pour prétraiter les données.

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

Comparaison des modèles

Nous allons comparer les performances des quatre pipelines en utilisant la validation croisée et tracer les temps d'ajustement et les scores d'erreur moyenne en pourcentage absolue.

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", "Temps d'ajustement (s)", ax1, None),
        ("test_score", "Erreur moyenne en pourcentage absolue", 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="Modèle",
            title=title,
            xticks=x,
            xticklabels=["Supprimé", "One Hot", "Ordinal", "Natif"],
            ylim=y_limit,
        )
    fig.suptitle(figure_title)

plot_results("Gradient Boosting sur le logement d'Ames")

Limitation du nombre de divisions

Nous allons rerun la même analyse avec des modèles sous-ajustés où nous limitons artificiellement le nombre total de divisions en limitant à la fois le nombre d'arbres et la profondeur de chaque arbre.

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)")

Sommaire

Dans ce laboratoire, nous avons comparé quatre pipelines différents pour traiter les variables catégorielles dans les estimateurs Gradient Boosting en utilisant l'ensemble de données du logement d'Ames. Nous avons constaté que supprimer les variables catégorielles entraînait une performance de prédiction moins bonne, et que les trois modèles qui utilisaient les variables catégorielles avaient des taux d'erreur comparables. Le codage one-hot des variables catégorielles était de loin la méthode la plus lente, tandis que traiter les variables catégorielles comme des valeurs ordinale et utiliser la prise en charge native des variables catégorielles de l'estimateur HistGradientBoostingRegressor avaient des temps d'ajustement similaires. Lorsque le nombre total de divisions était limité, la stratégie de prise en charge native des variables catégorielles a eu la meilleure performance.