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.