Введение
В этом лабораторном задании мы будем использовать датасет Ames Housing для сравнения различных методов обработки категориальных признаков в методах градиентного бустинга. Датасет содержит как числовые, так и категориальные признаки, а таргетом является цена продажи домов. Мы сравним производительность четырех различных конвейеров:
- Игнорирование категориальных признаков
- One-hot кодирование категориальных признаков
- Treating the categorical features as ordinal values
- Использование встроенной поддержки категориальных признаков в методе градиентного бустинга
Мы оценим конвейеры по времени обучения и производительности предсказания с использованием кросс-валидации.
Советы по работе с ВМ
После запуска ВМ кликните в левом верхнем углу, чтобы переключиться на вкладку Notebook и получить доступ к Jupyter Notebook для практики.
Иногда вам может потребоваться подождать несколько секунд, пока Jupyter Notebook загрузится. Проверка операций не может быть автоматизирована из-за ограничений Jupyter Notebook.
Если вы столкнетесь с проблемами во время обучения, не стесняйтесь задавать вопросы Labby. Оставьте отзыв после занятия, и мы оперативно решим проблему для вас.
Загрузка датасета
Мы будем загружать датасет Ames Housing с использованием функции fetch_openml из Scikit-Learn и выбирать подмножество признаков, чтобы пример выполнялся быстрее. Мы также преобразуем категориальные признаки в тип данных 'category'.
from sklearn.datasets import fetch_openml
X, y = fetch_openml(data_id=42165, as_frame=True, return_X_y=True, parser="pandas")
## Select only a subset of features of X to make the example faster to run
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")
Базовый конвейер - удаление категориальных признаков
Мы создадим конвейер, в котором мы удалим категориальные признаки и обучим оценщик 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))
Конвейер one-hot кодирования
Мы создадим конвейер, в котором мы будем применять one-hot кодирование к категориальным признакам и обучать оценщик 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)
)
Конвейер ординального кодирования
Мы создадим конвейер, в котором мы будем рассматривать категориальные признаки как ординальные значения и обучать оценщик HistGradientBoostingRegressor. Мы будем использовать OrdinalEncoder для кодирования категориальных признаков.
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)
)
Конвейер с встроенной поддержкой категориальных признаков
Мы создадим конвейер, в котором мы будем использовать встроенную поддержку категориальных признаков у оценщика HistGradientBoostingRegressor для обработки категориальных признаков. Мы по-прежнему будем использовать OrdinalEncoder для предварительной обработки данных.
hist_native = make_pipeline(
ordinal_encoder,
HistGradientBoostingRegressor(
random_state=42,
categorical_features=categorical_columns,
),
).set_output(transform="pandas")
Сравнение моделей
Мы сравним производительность четырех конвейеров с использованием кросс-валидации и построим графики времени обучения и средних абсолютных процентных ошибок.
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", "Время обучения (с)", ax1, None),
("test_score", "Средняя абсолютная процентная ошибка", 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="Модель",
title=title,
xticks=x,
xticklabels=["Удалено", "One Hot", "Ординальное", "Встроенное"],
ylim=y_limit,
)
fig.suptitle(figure_title)
plot_results("Gradient Boosting on Ames Housing")
Ограничение числа разбиений
Мы повторим тот же анализ с недообучающими моделями, где искусственно ограничим общее число разбиений, ограничив количество деревьев и глубину каждого дерева.
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)")
Резюме
В этом практическом занятии мы сравнили четыре различных конвейера для обработки категориальных признаков в оценщиках Gradient Boosting с использованием набора данных Ames Housing. Мы обнаружили, что удаление категориальных признаков приводит к более худшим показателям предсказательной производительности, и что у трех моделей, которые использовали категориальные признаки, ошибочные率 были сопоставимы. One-hot кодирование категориальных признаков является на сегодняшний день самым медленным методом, в то время как обработка категориальных признаков как ординальных значений и использование встроенной поддержки категориальных признаков в HistGradientBoostingRegressor оценщике имеет похожие времена обучения. Когда общее число разбиений ограничивается, стратегия встроенной поддержки категориальных признаков показывает наилучшие результаты.