Gradient Boosting com Recursos Categóricos

Beginner

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

Introdução

Neste laboratório, utilizaremos o conjunto de dados Ames Housing para comparar diferentes métodos de lidar com características categóricas em estimadores de Gradient Boosting. O conjunto de dados contém características numéricas e categóricas, e a variável-alvo é o preço de venda das casas. Compararemos o desempenho de quatro pipelines diferentes:

  • Remover as características categóricas
  • Codificação one-hot das características categóricas
  • Tratar as características categóricas como valores ordinais
  • Utilizar o suporte nativo para características categóricas no estimador Gradient Boosting

Avaliaremos os pipelines em termos de seus tempos de ajuste e desempenho de previsão usando validação cruzada.

Dicas da Máquina Virtual

Após o arranque da VM, clique no canto superior esquerdo para mudar para a aba Notebook para aceder ao Jupyter Notebook para a prática.

Por vezes, pode ser necessário esperar alguns segundos para o Jupyter Notebook terminar de carregar. A validação das operações não pode ser automatizada devido a limitações no Jupyter Notebook.

Se tiver problemas durante o aprendizado, não hesite em contactar o Labby. Forneça feedback após a sessão e resolveremos o problema rapidamente para si.

Carregar o conjunto de dados

Carregaremos o conjunto de dados Ames Housing utilizando a função fetch_openml do Scikit-Learn e selecionaremos um subconjunto de características para tornar o exemplo mais rápido de executar. Também converteremos as características categóricas para o tipo de dados 'category'.

from sklearn.datasets import fetch_openml

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

## Selecionar apenas um subconjunto de características de X para tornar o exemplo mais rápido de executar
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 Base - Remover Características Categóricas

Criaremos um pipeline onde removeremos as características categóricas e treinaremos um 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))

Pipeline de Codificação One-Hot

Criaremos um pipeline onde codificaremos as características categóricas usando a codificação one-hot e treinaremos um 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)
)

Pipeline de Codificação Ordinal

Criaremos um pipeline onde trataremos as características categóricas como valores ordinais e treinaremos um estimador HistGradientBoostingRegressor. Usaremos um OrdinalEncoder para codificar as 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)
)

Pipeline de Suporte Categórico Nativo

Criaremos um pipeline onde usaremos o suporte categórico nativo do estimador HistGradientBoostingRegressor para lidar com as características categóricas. Continuaremos a usar um OrdinalEncoder para pré-processar os dados.

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

Comparação de Modelos

Vamos comparar o desempenho dos quatro pipelines usando validação cruzada e plotar os tempos de ajuste e as pontuações de erro percentual absoluto médio.

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", "Tempos de ajuste (s)", ax1, None),
        ("test_score", "Erro Percentual Absoluto Médio", 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=["Excluído", "One Hot", "Ordinal", "Nativo"],
            ylim=y_limit,
        )
    fig.suptitle(figure_title)

plot_results("Gradient Boosting em Dados de Habitação de Ames")

Limitando o Número de Divisões

Vamos executar novamente a mesma análise com modelos de subajuste onde limitamos artificialmente o número total de divisões, limitando o número de árvores e a profundidade de cada árvore.

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 em Dados de Habitação de Ames (poucas e pequenas árvores)")

Resumo

Neste laboratório, comparamos quatro pipelines diferentes para lidar com recursos categóricos em estimadores de Gradient Boosting usando o conjunto de dados de Habitação de Ames. Descobrimos que a exclusão de recursos categóricos levou a um desempenho de previsão pior e que os três modelos que utilizaram recursos categóricos apresentaram taxas de erro comparáveis. A codificação one-hot dos recursos categóricos foi de longe o método mais lento, enquanto tratar os recursos categóricos como valores ordinais e usar o suporte categórico nativo do estimador HistGradientBoostingRegressor teve tempos de ajuste semelhantes. Quando o número total de divisões foi limitado, a estratégia de suporte categórico nativo apresentou o melhor desempenho.