使用 GridSearchCV 提前停止 - 使用保留的 CV 集进行验证

Posted

技术标签:

【中文标题】使用 GridSearchCV 提前停止 - 使用保留的 CV 集进行验证【英文标题】:Early stopping with GridSearchCV - use hold-out set of CV for validation 【发布时间】:2018-09-20 05:45:59 【问题描述】:

我想在 scikit-learns GridSearchCV-method 中使用 early-stopping-option。此 SO-thread 中显示了一个示例:

import xgboost as xgb
from sklearn.model_selection import GridSearchCV

trainX= [[1], [2], [3], [4], [5]]
trainY = [1, 2, 3, 4, 5]

testX = trainX 
testY = trainY

param_grid = "subsample" : [0.5, 0.8],
              "n_estimators" : [600]

fit_params = "early_stopping_rounds":1,
             "eval_set" : [[testX, testY]]


model = xgb.XGBRegressor()
gridsearch = GridSearchCV(estimator  = xgb.XGBRegressor(), 
                          param_grid=param_grid,
                          fit_params=fit_params,                          
                          verbose=1,                          
                          cv=2)
gridsearch.fit(trainX,trainY)

但是,我想将交叉验证过程的保留集用作验证集。有没有办法在GridSearchCV 中指定这个?

【问题讨论】:

人们一直在想这个问题。确实很奇怪,不能为此目的使用 CV 折叠 ***.com/questions/43866284/… 你可以用 hyperOpt 包做到这一点。 @EranMoshe 谢谢。你有例子吗? Grid Search and Early Stopping Using Cross Validation with XGBoost in SciKit-Learn的可能重复 【参考方案1】:

这在当前的 xgboost 实现中是不可能的(参考 0.6 和 0.7 版本)。 请注意 native xgboost

之间的区别
    xgboost.train(params, dtrain, num_boost_round=10, evals=(), obj=None, 
feval=None, maximize=False, early_stopping_rounds=None, evals_result=None, 
verbose_eval=True, xgb_model=None, callbacks=None, learning_rates=None)

xgboost.cv(params, dtrain, num_boost_round=10, nfold=3, stratified=False, 
folds=None, metrics=(), obj=None, feval=None, maximize=False, 
early_stopping_rounds=None, fpreproc=None, as_pandas=True, verbose_eval=None, 
show_stdv=True, seed=0, callbacks=None, shuffle=True)

sklearn 界面

    class xgboost.XGBRegressor(max_depth=3, learning_rate=0.1, 
n_estimators=100, silent=True, objective='reg:linear', booster='gbtree', 
n_jobs=1, nthread=None, gamma=0, min_child_weight=1, max_delta_step=0, 
subsample=1, colsample_bytree=1, colsample_bylevel=1, reg_alpha=0, 
reg_lambda=1, scale_pos_weight=1, base_score=0.5, random_state=0, seed=None, 
missing=None, **kwargs)

如您所见,xgboost.XGBRegressor 没有提前停止这样的事情。请注意,sklearn 接口是唯一可以与 GridSearchCV 结合使用的接口,它需要具有 .fit()、.predict() 等的适当 sklearn 估计器...

您可以将 early_stopping_roundseval_set 作为额外的 fit_params 传递给 GridSearchCV,这实际上是可行的。 但是,GridSearchCV 不会更改不同折叠之间的 fit_params,因此您最终会在所有折叠中使用相同的 eval_set,这可能不是您所说的 CV。

model=xgb.XGBClassifier()
clf = GridSearchCV(model, parameters,
                         fit_params='early_stopping_rounds':20,\
                         'eval_set':[(X,y)],cv=kfold)  

经过一些调整,我找到了集成early_stopping_rounds 和 sklearn API 的最安全方法是自己实现 early_stopping 机制。如果您使用n_rounds 作为要调整的参数来执行GridSearchCV,则可以这样做。然后,您可以通过增加n_rounds 来查看不同型号的mean_validation_score。然后,您可以为提前停止定义自定义启发式;你会注意到默认的不是最优的。

我认为这也是一种更好的方法,然后为此目的使用单个拆分保持。

【讨论】:

@00_00_00 。我在别处问过同样的问题。提前停止总是根据为所有 cv 折叠提供的 (X,y) 完成。我想没关系,只要准确度指标是在保留的折叠/数据集上计算的。这种方式最终将获得正确的“平均测试”分数,从而获得最佳 XGB。我不明白这里有什么不一致的地方。你能解释一下吗?提供的 eval_set(X,y) 仅用于停止过度拟合。否则 CV 会按预期执行,对吗?【参考方案2】:

过去我构建了一个类,包装了 “HyperOpt” 包以满足我的需求。

我会尽量为您快速将其最小化,以便您可以使用它。这是代码,最后有一些注释,以帮助您解决问题:

import numpy as np
from hyperopt import fmin, tpe, hp, STATUS_OK, Trials
import xgboost as xgb
max_float_digits = 4


def rounded(val):
    return ':.f'.format(val, max_float_digits)


class HyperOptTuner(object):
    """
    Tune my parameters!
    """
    def __init__(self, dtrain, dvalid, early_stopping=200, max_evals=200):
        self.counter = 0
        self.dtrain = dtrain
        self.dvalid = dvalid
        self.early_stopping = early_stopping
        self.max_evals = max_evals
        self.tuned_params = None


    def score(self, params):
        self.counter += 1
        # Edit params
        print("Iteration /".format(self.counter, self.max_evals))
        num_round = int(params['n_estimators'])
        del params['n_estimators']

        watchlist = [(self.dtrain, 'train'), (self.dvalid, 'eval')]
        model = xgb.train(params, self.dtrain, num_round, evals=watchlist, early_stopping_rounds=self.early_stopping,
                          verbose_eval=False)
        n_epoach = model.best_ntree_limit
        score = model.best_score
        params['n_estimators'] = n_epoach
        params = dict([(key, rounded(params[key]))
                       if type(params[key]) == float
                       else (key, params[key])
                       for key in params])

        print "Trained with: "
        print params
        print "\tScore 0\n".format(score)
        return 'loss': 1 - score, 'status': STATUS_OK, 'params': params

    def optimize(self, trials):
        space = 
            'n_estimators': 2000,  # hp.quniform('n_estimators', 10, 1000, 10),
            'eta': hp.quniform('eta', 0.025, 0.3, 0.025),
            'max_depth': hp.choice('max_depth', np.arange(1, 9, dtype=int)),
            'min_child_weight': hp.choice('min_child_weight', np.arange(1, 10, dtype=int)),
            'subsample': hp.quniform('subsample', 0.3, 1, 0.05),
            'gamma': hp.quniform('gamma', 0.1, 20, 0.1),
            'colsample_bytree': hp.quniform('colsample_bytree', 0.5, 1, 0.25),
            'eval_metric': 'map',
            'objective': 'rank:pairwise',
            'silent': 1
        

        fmin(self.score, space, algo=tpe.suggest, trials=trials, max_evals=self.max_evals),

        min_loss = 1
        min_params = 
        for trial in trials.trials:
            tmp_loss, tmp_params = trial['result']['loss'], trial['result']['params']
            if tmp_loss < min_loss:
                min_loss, min_params = tmp_loss, tmp_params

        print("Winning params:")
        print(min_params)
        print "\tScore: ".format(1-min_loss)
        self.tuned_params = min_params

    def tune(self):
        print "Tuning...\n"
        # Trials object where the history of search will be stored
        trials = Trials()
        self.optimize(trials)

所以我使用了一个类,主要是定义参数和保存结果以供进一步使用。有 2 个电源功能。

    optimize() 创建来定义我们的“搜索空间”,计算 最小化错误的最佳参数(请注意,您是 最小化错误)并保存找到的最佳参数。还添加了一些印刷品以帮助您遵循流程。

    score() 创建用于使用特定的计算模型的分数 来自“搜索空间”的 HyperParams。它使用 early_stopping 作为 在中定义。因为我不需要使用 cross 验证我用过 xgb.train(),但你可以把它改成 xgb.cv() 它确实支持 early_stopping_rounds。还在那里添加了印刷品 帮助您遵循流程。分数返回 1 - 分数(因为我已经 计算出的 MAP 是需要增加的评估,所以如果 您计算 RMSE 之类的错误,只需按原样返回分数。)

这是您在拥有 dtrain 和 dtest 矩阵后从代码中激活它的方式:

# dtrain is a training set of type DMatrix
# dtest is a testing set of type DMatrix
tuner = HyperOptTuner(dtrain=dtrain, dvalid=dtest, early_stopping=200, max_evals=400)
tuner.tune()

max_evals 是“搜索网格”的大小

请遵循这些指南,如果您遇到问题,请告诉我。

【讨论】:

没错,但他好心地要求 hyperopt 解决方案。而且我认为它是一个更好的解决方案,比 GridSearchCV 产生更好的结果 同意这一点。它是否也优于随机网格搜索? 是的。它也优于贝叶斯优化。但我只在我的域上检查过。所以我建议在其他领域也进行测试,而不是完全在 1 上进行中继:>

以上是关于使用 GridSearchCV 提前停止 - 使用保留的 CV 集进行验证的主要内容,如果未能解决你的问题,请参考以下文章

使用 GridSearchCV 提前停止 - 使用保留的 CV 集进行验证

XGBoost 提前停止 cv 与 GridSearchCV

在 scikit-learn 中训练神经网络时提前停止

使用 GridSearchCv 优化 SVR() 参数

GridSearchCV 处理的出口与评分值连接

如何使用 lightgbm.cv 进行回归?