简介
在本实验中,我们将使用艾姆斯房屋数据集(Ames Housing dataset)来比较梯度提升估计器(Gradient Boosting estimators)中处理分类特征的不同方法。该数据集包含数值特征和分类特征,目标是房屋的销售价格。我们将比较四种不同管道(pipeline)的性能:
- 丢弃分类特征
- 对分类特征进行独热编码(One-hot encoding)
- 将分类特征视为有序值
- 在梯度提升估计器中使用原生分类支持
我们将使用交叉验证(cross-validation)根据拟合时间和预测性能来评估这些管道。
虚拟机提示
虚拟机启动完成后,点击左上角切换到“笔记本”(Notebook)标签页,以访问 Jupyter Notebook 进行练习。
有时,你可能需要等待几秒钟让 Jupyter Notebook 完成加载。由于 Jupyter Notebook 的限制,操作验证无法自动化。
如果你在学习过程中遇到问题,可以随时向 Labby 提问。课程结束后提供反馈,我们会及时为你解决问题。
加载数据集
我们将使用 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)估计器的原生分类支持具有相似的拟合时间。当分割总数受到限制时,原生分类支持策略表现最佳。