在sklearn管道中,使用GridSearchCV、缩放、主成分分析和提前停止的XGBoost。

9
我希望将XGBoost模型与输入缩放和PCA特征空间缩减相结合。此外,应使用交叉验证来调整模型的超参数以及PCA中使用的组件数。为防止模型过度拟合,需要添加早期停止。
为了将各个步骤组合起来,我决定使用sklearn的Pipeline功能。
一开始,我遇到了一些问题,无法确保PCA也应用于验证集。但是我认为使用XGB__eval_set就可以解决这个问题。
代码实际上没有出现任何错误,但似乎会永远运行下去(某个时候,所有核的CPU使用率都降至零,但进程仍然运行数小时;不得不在某个时候杀死会话)。
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from xgboost import XGBRegressor   

# Train / Test split
X_train, X_test, y_train, y_test = train_test_split(X_with_features, y, test_size=0.2, random_state=123)

# Train / Validation split
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=123)

# Pipeline
pipe = Pipeline(steps=[("Scale", StandardScaler()),
                       ("PCA", PCA()),
                       ("XGB", XGBRegressor())])

# Hyper-parameter grid (Test only)
grid_param_pipe = {'PCA__n_components': [5],
                   'XGB__n_estimators': [1000],
                   'XGB__max_depth': [3],
                   'XGB__reg_alpha': [0.1],
                   'XGB__reg_lambda': [0.1]}

# Grid object
grid_search_pipe = GridSearchCV(estimator=pipe,
                                param_grid=grid_param_pipe,
                                scoring="neg_mean_squared_error",
                                cv=5,
                                n_jobs=5,
                                verbose=3)

# Run CV
grid_search_pipe.fit(X_train, y_train, XGB__early_stopping_rounds=10, XGB__eval_metric="rmse", XGB__eval_set=[[X_val, y_val]])

1
似乎将管道转换应用于验证集以进行早期停止并不容易,我怀疑仅使用XGB__eval_set是不够的。请参阅此sklearn问题https://github.com/scikit-learn/scikit-learn/issues/8414,了解有关管道步骤子集的建议应用。 - Mischa Lisovyi
将最后一个管道步骤(分类器)pop出来,对数据进行transform,然后重新附加分类器并不难。挑战在于在CV过程中,早期停止集不是验证集的情况下完成此操作。这可能需要自定义的GridSearchCV - Bert Kellerman
1个回答

12
问题在于fit方法需要一个外部创建的评估集,但是在管道转换之前我们无法创建这个集合。 虽然有点不正规,但是思路是创建一个薄包装器到xgboost回归/分类器内部准备评估集。
from sklearn.base import BaseEstimator
from sklearn.model_selection import train_test_split
from xgboost import XGBRegressor, XGBClassifier

class XGBoostWithEarlyStop(BaseEstimator):
    def __init__(self, early_stopping_rounds=5, test_size=0.1, 
                 eval_metric='mae', **estimator_params):
        self.early_stopping_rounds = early_stopping_rounds
        self.test_size = test_size
        self.eval_metric=eval_metric='mae'        
        if self.estimator is not None:
            self.set_params(**estimator_params)

    def set_params(self, **params):
        return self.estimator.set_params(**params)

    def get_params(self, **params):
        return self.estimator.get_params()

    def fit(self, X, y):
        x_train, x_val, y_train, y_val = train_test_split(X, y, test_size=self.test_size)
        self.estimator.fit(x_train, y_train, 
                           early_stopping_rounds=self.early_stopping_rounds, 
                           eval_metric=self.eval_metric, eval_set=[(x_val, y_val)])
        return self

    def predict(self, X):
        return self.estimator.predict(X)

class XGBoostRegressorWithEarlyStop(XGBoostWithEarlyStop):
    def __init__(self, *args, **kwargs):
        self.estimator = XGBRegressor()
        super(XGBoostRegressorWithEarlyStop, self).__init__(*args, **kwargs)

class XGBoostClassifierWithEarlyStop(XGBoostWithEarlyStop):
    def __init__(self, *args, **kwargs):
        self.estimator = XGBClassifier()
        super(XGBoostClassifierWithEarlyStop, self).__init__(*args, **kwargs)

以下是一个测试。

from sklearn.datasets import load_diabetes
from sklearn.pipeline import Pipeline
from sklearn.decomposition import PCA
from sklearn.model_selection import GridSearchCV

x, y = load_diabetes(return_X_y=True)
print(x.shape, y.shape)
# (442, 10) (442,)

pipe = Pipeline([
    ('pca', PCA(5)),
    ('xgb', XGBoostRegressorWithEarlyStop())
])

param_grid = {
    'pca__n_components': [3, 5, 7],
    'xgb__n_estimators': [10, 20, 30, 50]
}

grid = GridSearchCV(pipe, param_grid, scoring='neg_mean_absolute_error')
grid.fit(x, y)
print(grid.best_params_)

如果向开发人员请求功能需求,最简单的扩展是允许XGBRegressor在未提供评估集时内部创建评估集。这样,就不需要扩展scikit-learn(我猜测)。


如果想在同一管道中使用套索回归、随机森林或其他预测模型,该怎么办? - Areza
3
@Kota Mori,我不确定你介绍的这个类是否适用于具有early stopping的交叉验证。假设你想进行5折交叉验证。在第一轮中,我们将前4个折作为训练集,拟合模型并使用最后一个折来评估/验证模型以获得分数。但是使用你的类,我们会进行另一个训练集和测试集的划分(在fit函数中的test_size=0.1),基本上你会在前4个折的10%内评估模型,而不是使用最后一个折验证模型。如果我理解有误,请纠正我。 - Amin Kiany
非常好的答案!但是,有 1 个 Bug。set_params 应该设置 self.estimator=self.estimator.set_params(...),并返回 self。这样,在 GridSearchCV 中设置参数后,估计器对象仍将是此包装器对象,而不是原始的 XGBClassifier - Tim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接