带有分类特征的梯度提升

Machine LearningMachine LearningBeginner
立即练习

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

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在本实验中,我们将使用艾姆斯房屋数据集(Ames Housing dataset)来比较梯度提升估计器(Gradient Boosting estimators)中处理分类特征的不同方法。该数据集包含数值特征和分类特征,目标是房屋的销售价格。我们将比较四种不同管道(pipeline)的性能:

  • 丢弃分类特征
  • 对分类特征进行独热编码(One-hot encoding)
  • 将分类特征视为有序值
  • 在梯度提升估计器中使用原生分类支持

我们将使用交叉验证(cross-validation)根据拟合时间和预测性能来评估这些管道。

虚拟机提示

虚拟机启动完成后,点击左上角切换到“笔记本”(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{{"带有分类特征的梯度提升"}} sklearn/preprocessing -.-> lab-49149{{"带有分类特征的梯度提升"}} sklearn/pipeline -.-> lab-49149{{"带有分类特征的梯度提升"}} sklearn/model_selection -.-> lab-49149{{"带有分类特征的梯度提升"}} sklearn/compose -.-> lab-49149{{"带有分类特征的梯度提升"}} sklearn/datasets -.-> lab-49149{{"带有分类特征的梯度提升"}} ml/sklearn -.-> lab-49149{{"带有分类特征的梯度提升"}} end

加载数据集

我们将使用 Scikit-Learn 的fetch_openml函数加载艾姆斯房屋数据集,并选择部分特征子集以加快示例运行速度。我们还会将分类特征转换为“category”数据类型。

from sklearn.datasets import fetch_openml

X, y = fetch_openml(data_id=42165, as_frame=True, return_X_y=True, parser="pandas")

## 仅选择 X 的部分特征子集以加快示例运行速度
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))

独热编码管道

我们将创建一个管道,在其中对分类特征进行独热编码,并训练一个直方图梯度提升回归器(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=["丢弃", "独热", "有序", "原生"],
            ylim=y_limit,
        )
    fig.suptitle(figure_title)

plot_results("艾姆斯房屋数据集上的梯度提升")

限制分割数量

我们将对欠拟合模型重新运行相同的分析,通过限制树的数量和每棵树的深度来人为地限制分割的总数。

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("艾姆斯房屋数据集上的梯度提升(树少且树小)")

总结

在本实验中,我们使用艾姆斯房屋数据集比较了四种不同的管道,用于处理梯度提升估计器中的分类特征。我们发现,丢弃分类特征会导致较差的预测性能,而使用分类特征的三个模型具有可比的错误率。对分类特征进行独热编码是迄今为止最慢的方法,而将分类特征视为有序值并使用直方图梯度提升回归器(HistGradientBoostingRegressor)估计器的原生分类支持具有相似的拟合时间。当分割总数受到限制时,原生分类支持策略表现最佳。