Gradient Boosting с категориальными признаками

Machine LearningMachine LearningBeginner
Практиковаться сейчас

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

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этом лабораторном задании мы будем использовать датасет Ames Housing для сравнения различных методов обработки категориальных признаков в методах градиентного бустинга. Датасет содержит как числовые, так и категориальные признаки, а таргетом является цена продажи домов. Мы сравним производительность четырех различных конвейеров:

  • Игнорирование категориальных признаков
  • One-hot кодирование категориальных признаков
  • Treating the categorical features as ordinal values
  • Использование встроенной поддержки категориальных признаков в методе градиентного бустинга

Мы оценим конвейеры по времени обучения и производительности предсказания с использованием кросс-валидации.

Советы по работе с ВМ

После запуска ВМ кликните в левом верхнем углу, чтобы переключиться на вкладку Notebook и получить доступ к Jupyter Notebook для практики.

Иногда вам может потребоваться подождать несколько секунд, пока Jupyter Notebook загрузится. Проверка операций не может быть автоматизирована из-за ограничений Jupyter Notebook.

Если вы столкнетесь с проблемами во время обучения, не стесняйтесь задавать вопросы Labby. Оставьте отзыв после занятия, и мы оперативно решим проблему для вас.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL sklearn(("Sklearn")) -.-> sklearn/CoreModelsandAlgorithmsGroup(["Core Models and Algorithms"]) sklearn(("Sklearn")) -.-> sklearn/DataPreprocessingandFeatureEngineeringGroup(["Data Preprocessing and Feature Engineering"]) sklearn(("Sklearn")) -.-> sklearn/ModelSelectionandEvaluationGroup(["Model Selection and Evaluation"]) sklearn(("Sklearn")) -.-> sklearn/UtilitiesandDatasetsGroup(["Utilities and Datasets"]) ml(("Machine Learning")) -.-> ml/FrameworkandSoftwareGroup(["Framework and Software"]) sklearn/CoreModelsandAlgorithmsGroup -.-> sklearn/ensemble("Ensemble Methods") sklearn/DataPreprocessingandFeatureEngineeringGroup -.-> sklearn/preprocessing("Preprocessing and Normalization") sklearn/DataPreprocessingandFeatureEngineeringGroup -.-> sklearn/pipeline("Pipeline") sklearn/ModelSelectionandEvaluationGroup -.-> sklearn/model_selection("Model Selection") sklearn/ModelSelectionandEvaluationGroup -.-> sklearn/compose("Composite Estimators") sklearn/UtilitiesandDatasetsGroup -.-> sklearn/datasets("Datasets") ml/FrameworkandSoftwareGroup -.-> ml/sklearn("scikit-learn") subgraph Lab Skills sklearn/ensemble -.-> lab-49149{{"Gradient Boosting с категориальными признаками"}} sklearn/preprocessing -.-> lab-49149{{"Gradient Boosting с категориальными признаками"}} sklearn/pipeline -.-> lab-49149{{"Gradient Boosting с категориальными признаками"}} sklearn/model_selection -.-> lab-49149{{"Gradient Boosting с категориальными признаками"}} sklearn/compose -.-> lab-49149{{"Gradient Boosting с категориальными признаками"}} sklearn/datasets -.-> lab-49149{{"Gradient Boosting с категориальными признаками"}} ml/sklearn -.-> lab-49149{{"Gradient Boosting с категориальными признаками"}} end

Загрузка датасета

Мы будем загружать датасет 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 оценщике имеет похожие времена обучения. Когда общее число разбиений ограничивается, стратегия встроенной поддержки категориальных признаков показывает наилучшие результаты.