使用stacking的组合预测器

stacking是一种组合估计器的方法。在该策略中,一些估计器单独的在一些训练数据上进行拟合,而最终估计器则是使用这些基估计器的预测融合训练的。

在这个例子中,我们说明了一个用例,在这个用例中,不同的回归器被融合在一起,并使用一个最终的线性惩罚回归器来输出预测。我们将每一个回归器的表现与融合策略进行比较。融合稍微提高了整体性能。

print(__doc__)

# Authors: Guillaume Lemaitre <g.lemaitre58@gmail.com>
#          Maria Telenczuk    <https://github.com/maikia>
# License: BSD 3 clause

下载数据集

我们将使用 Ames Housing数据集, 这个数据集是由Dean De Cock制作的并且在Kaggle挑战中被使用后很有名。这是一个有1460套在Ames的房子,每个房子都有80个特征。我们将用它来预测房屋的最终对数价格。在本例中,我们将只使用 GradientBoostingRegressor()选择的20个最有趣的特征,并限制条目的数量(这里我们将不详细介绍如何选择最有趣的特性)

Ames housing数据集不是随着scikit-learn一起提供的, 因此我们需要从 OpenML获取。

import numpy as np

from sklearn.datasets import fetch_openml
from sklearn.utils import shuffle


def load_ames_housing():
    df = fetch_openml(name="house_prices", as_frame=True)
    X = df.data
    y = df.target

    features = ['YrSold''HeatingQC''Street''YearRemodAdd''Heating',
                'MasVnrType''BsmtUnfSF''Foundation''MasVnrArea',
                'MSSubClass''ExterQual''Condition2''GarageCars',
                'GarageType''OverallQual''TotalBsmtSF''BsmtFinSF1',
                'HouseStyle''MiscFeature''MoSold']

    X = X[features]
    X, y = shuffle(X, y, random_state=0)

    X = X[:600]
    y = y[:600]
    return X, np.log(y)


X, y = load_ames_housing()

制作数据预处理工作流

在使用Ames数据集之前,我们还需要进行一些预处理。首先,数据集有许多缺失的值。为了计算它们,我们将用新的类别 ‘missing’来替换分类缺失的值,而用列的“平mean”来表示缺少的数值。我们也会使用sklearn.preprocessing.OneHotEncoder或者sklearn.preprocessing.OrdinalEncoder来编码这些类别, 这取决于我们所使用模型的类型(线性或者非线性模型)。为了伪造这一预处理, 我们将制造两个工作流(pipelines)。如果您的数据已准备好使用且不需要预处理,则可以跳过本节。

from sklearn.compose import make_column_transformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import OrdinalEncoder
from sklearn.preprocessing import StandardScaler

cat_cols = X.columns[X.dtypes == 'O']
num_cols = X.columns[X.dtypes == 'float64']

categories = [
    X[column].unique() for column in X[cat_cols]]

for cat in categories:
    cat[cat == None] = 'missing'  # noqa

cat_proc_nlin = make_pipeline(
    SimpleImputer(missing_values=None, strategy='constant',
                  fill_value='missing'),
    OrdinalEncoder(categories=categories)
    )

num_proc_nlin = make_pipeline(SimpleImputer(strategy='mean'))

cat_proc_lin = make_pipeline(
    SimpleImputer(missing_values=None,
                  strategy='constant',
                  fill_value='missing'),
    OneHotEncoder(categories=categories)
)

num_proc_lin = make_pipeline(
    SimpleImputer(strategy='mean'),
    StandardScaler()
)

# transformation to use for non-linear estimators
processor_nlin = make_column_transformer(
    (cat_proc_nlin, cat_cols),
    (num_proc_nlin, num_cols),
    remainder='passthrough')

# transformation to use for linear estimators
processor_lin = make_column_transformer(
    (cat_proc_lin, cat_cols),
    (num_proc_lin, num_cols),
    remainder='passthrough')

单个数据集预测器的融合

有时候,在给定数据集上找到一个表现最好的模型是很乏味的。Stacking通过组合几个学习器的输出提供了一个替代方案,而不需要具体地选择一个模型。Stacking的性能通常接近最佳模型,有时甚至可以超过各个模型的预测性能。

在这里,我们结合了三个学习器(线性和非线性),并使用岭回归器将他们的输出组合在一起。

注意:虽然我们将建立一个新的处理工作流, 这个处理就是之前三个学习期的预处理过程, 但是最终的估计器RidgeCV()并不需要数据的预处理,因为它将被输入三个学习器已经预处理的输出。

from sklearn.experimental import enable_hist_gradient_boosting  # noqa
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import StackingRegressor
from sklearn.linear_model import LassoCV
from sklearn.linear_model import RidgeCV


lasso_pipeline = make_pipeline(processor_lin,
                               LassoCV())

rf_pipeline = make_pipeline(processor_nlin,
                            RandomForestRegressor(random_state=42))

gradient_pipeline = make_pipeline(
    processor_nlin,
    HistGradientBoostingRegressor(random_state=0))

estimators = [('Random Forest', rf_pipeline),
              ('Lasso', lasso_pipeline),
              ('Gradient Boosting', gradient_pipeline)]

stacking_regressor = StackingRegressor(estimators=estimators,
                                       final_estimator=RidgeCV())

现在我们可以使用Ames住房数据集进行预测。我们检查每个预测器的性能以及回归器的融合。

函数图 plot_regression_results 用于绘制预测和真实的目标。

import time
import matplotlib.pyplot as plt
from sklearn.model_selection import cross_validate, cross_val_predict


def plot_regression_results(ax, y_true, y_pred, title, scores, elapsed_time):
    """Scatter plot of the predicted vs true targets."""
    ax.plot([y_true.min(), y_true.max()],
            [y_true.min(), y_true.max()],
            '--r', linewidth=2)
    ax.scatter(y_true, y_pred, alpha=0.2)

    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.get_xaxis().tick_bottom()
    ax.get_yaxis().tick_left()
    ax.spines['left'].set_position(('outward'10))
    ax.spines['bottom'].set_position(('outward'10))
    ax.set_xlim([y_true.min(), y_true.max()])
    ax.set_ylim([y_true.min(), y_true.max()])
    ax.set_xlabel('Measured')
    ax.set_ylabel('Predicted')
    extra = plt.Rectangle((00), 00, fc="w", fill=False,
                          edgecolor='none', linewidth=0)
    ax.legend([extra], [scores], loc='upper left')
    title = title + '\n Evaluation in {:.2f} seconds'.format(elapsed_time)
    ax.set_title(title)


fig, axs = plt.subplots(22, figsize=(97))
axs = np.ravel(axs)

for ax, (name, est) in zip(axs, estimators + [('Stacking Regressor',
                                               stacking_regressor)]):
    start_time = time.time()
    score = cross_validate(est, X, y,
                           scoring=['r2''neg_mean_absolute_error'],
                           n_jobs=-1, verbose=0)
    elapsed_time = time.time() - start_time

    y_pred = cross_val_predict(est, X, y, n_jobs=-1, verbose=0)

    plot_regression_results(
        ax, y, y_pred,
        name,
        (r'$R^2={:.2f} \pm {:.2f}$' + '\n' + r'$MAE={:.2f} \pm {:.2f}$')
        .format(np.mean(score['test_r2']),
                np.std(score['test_r2']),
                -np.mean(score['test_neg_mean_absolute_error']),
                np.std(score['test_neg_mean_absolute_error'])),
        elapsed_time)

plt.suptitle('Single predictors versus stacked predictors')
plt.tight_layout()
plt.subplots_adjust(top=0.9)
plt.show()

融合的回归器将结合不同的回归者的优势。然而,我们也看到,训练融合的回归器在计算上要昂贵得多。

脚本的总运行时间:(0分25.100秒)

Download Python source code: plot_stack_predictors.py

Download Jupyter notebook: plot_stack_predictors.ipynb