Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере с помощью JupyterLite или Binder.
Поддержка категориальных признаков в градиентном бустинге
В этом примере сравниваются время обучения и качество предсказаний HistGradientBoostingRegressor при использовании различных стратегий кодирования категориальных признаков. В частности, мы оцениваем:
- «Удаление»: удаление категориальных признаков;
- «One Hot»: использование
OneHotEncoder; - «Порядковое»: использование
OrdinalEncoderи трактовка категорий как упорядоченных равноотстоящих величин; - «Целевое»: использование
TargetEncoder; - «Встроенное»: использование встроенной поддержки категорий оценивателя
HistGradientBoostingRegressor.
Для этого мы используем набор данных о жилье в Эймсе, штат Айова, содержащий числовые и категориальные признаки. Целевой показатель — цена продажи дома.
См. Признаки деревьев градиентного бустинга на гистограммах — пример, демонстрирующий некоторые другие возможности HistGradientBoostingRegressor.
См. Сравнение целевого кодировщика с другими кодировщиками для сравнения стратегий кодирования при наличии категориальных признаков с высокой кардинальностью.
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Загрузка набора данных о жилье в Эймсе
Сначала загрузим данные о жилье в Эймсе в виде фрейма данных pandas. Признаки бывают категориальными или числовыми:
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))
hist_dropped
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder='passthrough',
transformers=[('drop', 'drop',
<sklearn.compose._column_transformer.make_column_selector object at 0x78416405aba0>)])),
('histgradientboostingregressor',
HistGradientBoostingRegressor(random_state=42))])
В среде Jupyter выполните эту ячейку повторно, чтобы отобразить представление HTML, или доверьте этому блокноту. На GitHub не удаётся отобразить представление HTML; попробуйте открыть эту страницу на nbviewer.org.
Параметры
Параметры
<sklearn.compose._column_transformer.make_column_selector object at 0x78416405aba0>
drop
Параметры
Оцениватель градиентного бустинга с кодированием one-hot
Далее мы создадим конвейер для кодирования категориальных признаков методом one-hot, оставив остальные признаки "passthrough" без изменений:
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)
)
hist_one_hot
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder='passthrough',
transformers=[('onehotencoder',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
<sklearn.compose._column_transformer.make_column_selector object at 0x78414c239810>)])),
('histgradientboostingregressor',
HistGradientBoostingRegressor(random_state=42))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте доступ к записной книжке. На GitHub HTML-представление не отображается; попробуйте открыть эту страницу на nbviewer.org.
Параметры
Параметры
<sklearn.compose._column_transformer.make_column_selector object at 0x78414c239810>
Параметры
Параметры
Оцениватель градиентного бустинга с порядковым кодированием
Далее мы создаём конвейер, который рассматривает категориальные признаки как упорядоченные величины, то есть категории кодируются числами 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",
)
hist_ordinal = make_pipeline(
ordinal_encoder, HistGradientBoostingRegressor(random_state=42)
)
hist_ordinal
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder='passthrough',
transformers=[('ordinalencoder',
OrdinalEncoder(handle_unknown='use_encoded_value',
unknown_value=nan),
<sklearn.compose._column_transformer.make_column_selector object at 0x78414c238cd0>)])),
('histgradientboostingregressor',
HistGradientBoostingRegressor(random_state=42))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте этой записной книжке. На GitHub HTML-представление отобразить не удаётся; попробуйте загрузить эту страницу через nbviewer.org.
Параметры
Параметры
<sklearn.compose._column_transformer.make_column_selector object at 0x78414c238cd0>
Параметры
Параметры
Оцениватель градиентного бустинга с кодированием по целевой переменной
Также можно использовать TargetEncoder, который кодирует категории, вычисленные на основе среднего значения целевой переменной (обучающей выборки), с помощью сглаженного np.mean(y, axis=0), то есть:
- в задачах регрессии используется среднее значение
y; - в бинарной классификации — доля объектов положительного класса;
- в многоклассовой классификации — вектор долей классов (по одной на каждый класс).
Для каждой категории эти средние значения целевой переменной вычисляются с помощью перекрёстного обучения: обучающие данные разбиваются на фолды, и в каждом фолде средние значения вычисляются только по подмножеству данных, а затем применяются к отложенной части. Таким образом, каждый объект кодируется с использованием статистик данных, к которым он сам не относился, что предотвращает утечку информации из целевой переменной.
from sklearn.model_selection import KFold
from sklearn.preprocessing import TargetEncoder
target_encoder = make_column_transformer(
(
TargetEncoder(
target_type="continuous", cv=KFold(shuffle=True, random_state=42)
),
make_column_selector(dtype_include="category"),
),
remainder="passthrough",
)
hist_target = make_pipeline(
target_encoder, HistGradientBoostingRegressor(random_state=42)
)
hist_target
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder='passthrough',
transformers=[('targetencoder',
TargetEncoder(cv=KFold(n_splits=5, random_state=42, shuffle=True),
target_type='continuous'),
<sklearn.compose._column_transformer.make_column_selector object at 0x78416c6f48a0>)])),
('histgradientboostingregressor',
HistGradientBoostingRegressor(random_state=42))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте работу блокноту. На GitHub HTML-представление не отображается. Попробуйте загрузить эту страницу с помощью nbviewer.org.
Параметры
Параметры
<sklearn.compose._column_transformer.make_column_selector object at 0x78416c6f48a0>
Параметры
Параметры
Градиентный бустинг с нативной поддержкой категориальных признаков
Теперь мы создадим оценщик HistGradientBoostingRegressor, который может нативно обрабатывать категориальные признаки без явного кодирования. Эту функциональность можно включить, задав categorical_features="from_dtype", который автоматически определяет признаки с категориальными типами данных, или более явно — с помощью categorical_features=categorical_columns_subset.
В отличие от предыдущих подходов к кодированию, оценщик нативно работает с категориальными признаками. На каждом разбиении он делит категории такого признака на непересекающиеся множества с помощью эвристики, которая сортирует их по влиянию на целевую переменную. Подробнее см. в разделе Поиск разбиений для категориальных признаков.
Порядковое кодирование может хорошо работать с признаками с небольшим числом категорий, даже если у категорий нет естественного порядка. Однако с увеличением числа категорий для получения осмысленных разбиений требуются более глубокие деревья. Нативная поддержка категорий позволяет избежать этого, напрямую работая с неупорядоченными категориями. Преимущество по сравнению с унитарным кодированием — отсутствие этапа предварительной обработки и более быстрое обучение и предсказание.
hist_native = HistGradientBoostingRegressor(
random_state=42, categorical_features="from_dtype"
)
hist_native
HistGradientBoostingRegressor(random_state=42)В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте эту записную книжку.
На GitHub HTML-представление отобразить невозможно. Попробуйте открыть эту страницу с помощью nbviewer.org.
Параметры
Сравнение моделей
Здесь мы используем перекрёстную проверку, чтобы сравнить качество моделей по показателям mean_absolute_percentage_error и времени обучения. На следующих графиках полосы погрешностей показывают одно стандартное отклонение, вычисленное по разбиениям перекрёстной проверки.
from sklearn.model_selection import cross_validate
common_params = {"cv": 5, "scoring": "neg_mean_absolute_percentage_error", "n_jobs": -1}
dropped_result = cross_validate(hist_dropped, X, y, **common_params)
one_hot_result = cross_validate(hist_one_hot, X, y, **common_params)
ordinal_result = cross_validate(hist_ordinal, X, y, **common_params)
target_result = cross_validate(hist_target, X, y, **common_params)
native_result = cross_validate(hist_native, X, y, **common_params)
results = [
("Dropped", dropped_result),
("One Hot", one_hot_result),
("Ordinal", ordinal_result),
("Target", target_result),
("Native", native_result),
]
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
def plot_performance_tradeoff(results, title):
fig, ax = plt.subplots()
markers = ["s", "o", "^", "x", "D"]
for idx, (name, result) in enumerate(results):
test_error = -result["test_score"]
mean_fit_time = np.mean(result["fit_time"])
mean_score = np.mean(test_error)
std_fit_time = np.std(result["fit_time"])
std_score = np.std(test_error)
ax.scatter(
result["fit_time"],
test_error,
label=name,
marker=markers[idx],
)
ax.scatter(
mean_fit_time,
mean_score,
color="k",
marker=markers[idx],
)
ax.errorbar(
x=mean_fit_time,
y=mean_score,
yerr=std_score,
c="k",
capsize=2,
)
ax.errorbar(
x=mean_fit_time,
y=mean_score,
xerr=std_fit_time,
c="k",
capsize=2,
)
ax.set_xscale("log")
nticks = 7
x0, x1 = np.log10(ax.get_xlim())
ticks = np.logspace(x0, x1, nticks)
ax.set_xticks(ticks)
ax.xaxis.set_major_formatter(ticker.FormatStrFormatter("%1.1e"))
ax.minorticks_off()
ax.annotate(
" best\nmodels",
xy=(0.04, 0.04),
xycoords="axes fraction",
xytext=(0.09, 0.14),
textcoords="axes fraction",
arrowprops=dict(arrowstyle="->", lw=1.5),
)
ax.set_xlabel("Time to fit (seconds)")
ax.set_ylabel("Mean Absolute Percentage Error")
ax.set_title(title)
ax.legend()
plt.show()
plot_performance_tradeoff(results, "Gradient Boosting on Ames Housing")

На графике выше «лучшими моделями» считаются те, которые расположены ближе к левому нижнему углу, как показано стрелкой. Такие модели действительно обеспечивают более быстрое обучение и меньшую ошибку.
Модель с данными, закодированными унитарным кодированием, работает медленнее всех. Это ожидаемо: унитарное кодирование создаёт дополнительный признак для каждого значения категории каждого категориального признака, значительно увеличивая число кандидатов на разбиение во время обучения. Теоретически мы ожидаем, что нативная обработка категориальных признаков будет немного медленнее, чем обработка категорий как упорядоченных величин («Ordinal»), поскольку для нативной обработки требуется сортировать категории. Однако при небольшом числе категорий время обучения должно быть близким, и на практике это не всегда заметно.
Время обучения при использовании TargetEncoder зависит от параметра перекрёстного кодирования cv, поскольку добавление разбиений требует дополнительных вычислений.
Что касается качества предсказаний, удаление категориальных признаков приводит к худшим результатам. Четыре модели, использующие категориальные признаки, имеют сопоставимые показатели ошибки, причём небольшое преимущество у модели с нативной обработкой.
Ограничение числа разбиений
В целом от данных, закодированных унитарным кодированием, можно ожидать менее точных предсказаний, особенно если глубина деревьев или число узлов ограничены: для восстановления разбиения, которое при нативной обработке можно получить за одну точку разбиения, при унитарном кодировании требуется больше точек разбиения, то есть большая глубина.
Это справедливо и для случая, когда категории рассматриваются как порядковые величины: если категории — A..F, а оптимальное разбиение — ACF - BDE, модели с унитарным кодированием потребуется 3 точки разбиения (по одной для каждой категории в левом узле), а модели с порядковым кодированием — 4 разбиения: 1 разбиение, чтобы отделить A, 1 разбиение, чтобы отделить F, и 2 разбиения, чтобы отделить C от BCDE.
Насколько различается качество моделей на практике, зависит от набора данных и гибкости деревьев.
Чтобы это проверить, повторим тот же анализ для недообученных моделей, искусственно ограничив общее число разбиений за счёт ограничения как числа деревьев, так и глубины каждого дерева.
for pipe in (hist_dropped, hist_one_hot, hist_ordinal, hist_target, 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, **common_params)
one_hot_result = cross_validate(hist_one_hot, X, y, **common_params)
ordinal_result = cross_validate(hist_ordinal, X, y, **common_params)
target_result = cross_validate(hist_target, X, y, **common_params)
native_result = cross_validate(hist_native, X, y, **common_params)
results_underfit = [
("Dropped", dropped_result),
("One Hot", one_hot_result),
("Ordinal", ordinal_result),
("Target", target_result),
("Native", native_result),
]
plot_performance_tradeoff(
results_underfit, "Gradient Boosting on Ames Housing (few and shallow trees)"
)

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