Примечание
Перейти к концу для загрузки полного примера кода. или для запуска этого примера в вашем браузере через JupyterLite или Binder
Поддержка категориальных признаков в градиентном бустинге
В этом примере мы сравним время обучения и качество предсказаний HistGradientBoostingRegressor с различными стратегиями кодирования категориальных признаков. В частности, мы оценим:
- удаление категориальных признаков
- использование
OneHotEncoder - использование
OrdinalEncoderи обработку категорий как упорядоченных, равноудаленных величин - использование
OrdinalEncoderи использование родной поддержки категорий уHistGradientBoostingRegressorоценщика.
Мы будем работать с набором данных о домах в Амес, штат Айова, который состоит из количественных и категориальных признаков, где ценой продажи домов является целевая переменная.
См. Признаки в деревьях градиентного бустинга с гистограммами для примера, демонстрирующего некоторые другие особенности HistGradientBoostingRegressor.
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Загрузка набора данных Ames Housing
Сначала мы загружаем данные Ames Housing в виде pandas dataframe. Признаки являются либо категориальными, либо количественными:
from sklearn.datasets import fetch_openml
X, y = fetch_openml(data_id=42165, as_frame=True, return_X_y=True)
# 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")
categorical_columns = X.select_dtypes(include="category").columns
n_categorical_features = len(categorical_columns)
n_numerical_features = X.select_dtypes(include="number").shape[1]
print(f"Number of samples: {X.shape[0]}")
print(f"Number of features: {X.shape[1]}")
print(f"Number of categorical features: {n_categorical_features}")
print(f"Number of numerical features: {n_numerical_features}")
Number of samples: 1460 Number of features: 20 Number of categorical features: 10 Number of numerical features: 10
Оценщик градиентного бустинга с удаленными категориальными признаками
В качестве базовой модели мы создаем оценщик, где категориальные признаки удаляются:
from sklearn.compose import make_column_selector, make_column_transformer
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.pipeline import make_pipeline
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 кодирования, а остальные количественные данные будут проходить без изменений:
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)
)
Оценщик градиентного бустинга с порядковым кодированием
Далее, мы создаем конвейер, который будет обрабатывать категориальные признаки так, как будто они представляют собой упорядоченные величины, т. е. категории будут закодированы как 0, 1, 2 и т. д. и будут обрабатываться как непрерывные признаки.
import numpy as np
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = make_column_transformer(
(
OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=np.nan),
make_column_selector(dtype_include="category"),
),
remainder="passthrough",
# Use short feature names to make it easier to specify the categorical
# variables in the HistGradientBoostingRegressor in the next step
# of the pipeline.
verbose_feature_names_out=False,
)
hist_ordinal = make_pipeline(
ordinal_encoder, HistGradientBoostingRegressor(random_state=42)
)
Оценщик градиентного бустинга с родной поддержкой категорий
Теперь мы создаем HistGradientBoostingRegressor оценщик, который будет обрабатывать категориальные признаки непосредственно. Этот оценщик не будет рассматривать категориальные признаки как упорядоченные величины. Мы устанавливаем categorical_features="from_dtype" таким образом, что признаки с категориальным типом данных рассматриваются как категориальные.
Основное различие между этим оценщиком и предыдущим заключается в том, что в этом оценщике мы позволяем HistGradientBoostingRegressor определить, какие признаки являются категориальными из типов данных столбцов DataFrame.
hist_native = HistGradientBoostingRegressor(
random_state=42, categorical_features="from_dtype"
)
Сравнение моделей
Наконец, мы оцениваем модели с помощью перекрестной проверки. Здесь мы сравниваем производительность моделей с точки зрения mean_absolute_percentage_error и времени обучения.
import matplotlib.pyplot as plt
from sklearn.model_selection import cross_validate
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", "Fit times (s)", ax1, None),
("test_score", "Mean Absolute Percentage Error", 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="Model",
title=title,
xticks=x,
xticklabels=["Dropped", "One Hot", "Ordinal", "Native"],
ylim=y_limit,
)
fig.suptitle(figure_title)
plot_results("Gradient Boosting on Ames Housing")

Мы видим, что модель с данными, закодированными с помощью one-hot-кодирования, безусловно, самая медленная. Это ожидаемо, так как one-hot-кодирование создает один дополнительный признак на значение каждой категории (для каждого категориального признака), а значит, при обучении необходимо учитывать больше точек разбиения. Теоретически, мы ожидаем, что родная обработка категориальных признаков будет немного медленнее, чем обработка категорий как упорядоченных величин («Порядковый»), так как для родной обработки требуется сортировка категорий. Однако времена обучения должны быть близки, когда количество категорий невелико, и это не всегда отражается на практике.
С точки зрения качества предсказаний, удаление категориальных признаков приводит к худшей производительности. Три модели, использующие категориальные признаки, имеют сопоставимые ошибки, с небольшим преимуществом для родной обработки.
Ограничение числа разбиений
В целом, можно ожидать худших предсказаний от данных, закодированных с помощью one-hot-кодирования, особенно при ограниченной глубине деревьев или количестве узлов: с данными, закодированными с помощью one-hot-кодирования, требуется больше точек разбиения, т. е. большая глубина, для того, чтобы восстановить эквивалентное разбиение, которое можно получить с помощью одной точки разбиения при родной обработке.
Это также верно, когда категории обрабатываются как порядковые величины: если категории A..F и наилучшее разбиение ACF - BDE является тем, что one-hot-кодер будет нуждаться в 3 точках разбиения (по одной на каждую категорию в левом узле), а модель порядкового кодирования без родной поддержки будет нуждаться в 4 разбиениях: 1 разбиение для изоляции A, 1 разбиение для изоляции F, и 2 разбиения для изоляции C от BCDE.
Насколько сильно модели будут отличаться по производительности на практике, будет зависеть от набора данных и гибкости деревьев.
Чтобы увидеть это, давайте заново выполним тот же анализ с моделями недообучения, где мы искусственно ограничим общее количество разбиений, ограничив количество деревьев и глубину каждого дерева.
for pipe in (hist_dropped, hist_one_hot, hist_ordinal, hist_native):
if pipe is hist_native:
# The native model does not use a pipeline so, we can set the parameters
# directly.
pipe.set_params(max_depth=3, max_iter=15)
else:
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)")
plt.show()

Результаты для этих моделей недообучения подтверждают нашу предыдущую интуицию: стратегия родной обработки категорий работает лучше всего при ограничении бюджета на разбиения. Две другие стратегии (one-hot кодирование и обработка категорий как порядковых значений) приводят к ошибкам, сопоставимым с базовой моделью, которая просто удалила категориальные признаки.
Общее время выполнения сценария: (0 минут 3.791 секунды)
Связанные примеры
© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/ensemble/plot_gradient_boosting_categorical.html