使用 Scikit-Learn GridSearchCV 与 PredefinedSplit 进行交叉验证 - 可疑的交叉验证结果

Posted

技术标签:

【中文标题】使用 Scikit-Learn GridSearchCV 与 PredefinedSplit 进行交叉验证 - 可疑的交叉验证结果【英文标题】:Using Scikit-Learn GridSearchCV for cross validation with PredefinedSplit - Suspiciously good cross validation results 【发布时间】:2018-03-30 15:03:13 【问题描述】:

我想使用 scikit-learn 的 GridSearchCV 执行网格搜索并使用预定义的开发和验证拆分(1 倍交叉验证)计算交叉验证错误。

我担心我做错了什么,因为我的验证准确度高得令人怀疑。我认为我出错的地方:我将我的训练数据分成开发集和验证集,在开发集上进行训练并在验证集上记录交叉验证分数。我的准确性可能被夸大了,因为我实际上是在混合开发和验证集上进行训练,然后在验证集上进行测试。我不确定我是否正确使用了 scikit-learn 的 PredefinedSplit 模块。详情如下:

按照this answer,我做了以下事情:

    import numpy as np
    from sklearn.model_selection import train_test_split, PredefinedSplit
    from sklearn.grid_search import GridSearchCV

    # I split up my data into training and test sets. 
    X_train, X_test, y_train, y_test = train_test_split(
        data[training_features], data[training_response], test_size=0.2, random_state=550)

    # sanity check - dimensions of training and test splits
    print(X_train.shape)
    print(X_test.shape)
    print(y_train.shape)
    print(y_test.shape)

    # dimensions of X_train and x_test are (323430, 26) and (323430,1) respectively
    # dimensions of X_test and y_test are (80858, 26) and (80858, 1)

    ''' Now, I define indices for a pre-defined split. 
    this is a 323430 dimensional array, where the indices for the development
    set are set to -1, and the indices for the validation set are set to 0.'''

    validation_idx = np.repeat(-1, y_train.shape)
    np.random.seed(550)
    validation_idx[np.random.choice(validation_idx.shape[0], 
           int(round(.2*validation_idx.shape[0])), replace = False)] = 0

    # Now, create a list which contains a single tuple of two elements, 
    # which are arrays containing the indices for the development and
    # validation sets, respectively.
    validation_split = list(PredefinedSplit(validation_idx).split())

    # sanity check
    print(len(validation_split[0][0])) # outputs 258744 
    print(len(validation_split[0][0]))/float(validation_idx.shape[0])) # outputs .8
    print(validation_idx.shape[0] == y_train.shape[0]) # True
    print(set(validation_split[0][0]).intersection(set(validation_split[0][1]))) # set([]) 

现在,我使用GridSearchCV 运行网格搜索。我的意图是,模型将适合网格上每个参数组合的开发集,并且当将生成的估计器应用于验证集时,将记录交叉验证分数/em>。

    # a vanilla XGboost model
    model1 = XGBClassifier()

    # create a parameter grid for the number of trees and depth of trees
    n_estimators = range(300, 1100, 100)
    max_depth = [8, 10]
    param_grid = dict(max_depth=max_depth, n_estimators=n_estimators)

    # A grid search. 
    # NOTE: I'm passing a PredefinedSplit object as an argument to the `cv` parameter.
    grid_search = GridSearchCV(model1, param_grid,
           scoring='neg_log_loss',
           n_jobs=-1, 
           cv=validation_split,
           verbose=1)

现在,这是为我竖起红旗的地方。我使用网格搜索找到的最佳估计器来查找验证集的准确性。它非常高 - 0.89207865689639176。更糟糕的是,如果我在数据开发集(我刚刚训练的)上使用分类器 - 0.89295597192591902,它几乎相同但是 - 当我在真实测试集上使用分类器时,我得到的准确率要低得多,大致为.78

    # accurracy score on the validation set. This yields .89207865
    accuracy_score(y_pred = 
           grid_result2.predict(X_train.iloc[validation_split[0][1]]),
           y_true=y_train[validation_split[0][1]])

    # accuracy score when applied to the development set. This yields .8929559
    accuracy_score(y_pred = 
           grid_result2.predict(X_train.iloc[validation_split[0][0]]),
           y_true=y_train[validation_split[0][0]])

    # finally, the score when applied to the test set. This yields .783 
    accuracy_score(y_pred = grid_result2.predict(X_test), y_true = y_test)

对我而言,模型在应用于开发和验证数据集时的准确度与应用于测试集时准确度的显着损失之间几乎完全一致,这清楚地表明我是在偶然对验证数据进行训练,因此我的交叉验证分数不能代表模型的真实准确性。

我似乎找不到哪里出错了——主要是因为当GridSearchCV 接收到PredefinedSplit 对象作为cv 参数的参数时,我不知道它到底在做什么。

任何想法我哪里出错了?如果您需要更多详细信息/详细说明,请告诉我。代码也在 this notebook on github.

谢谢!

【问题讨论】:

适配后请看一下GridSearchCV的cv_results_ attribute。您可以获得有关每个折叠的训练和测试折叠的分数信息(在您的情况下为 1)。 【参考方案1】:

您需要设置refit=False(不是默认选项),否则网格搜索完成后,网格搜索将在整个数据集上重新拟合估计器(忽略 cv)。

【讨论】:

同意您的回答,我总是使用 Timi 在这个问题中的内容:将数据拆分为训练、验证、测试 -> 在 gridsearchcv 中使用 1-fold validation 在问题中像这样进行验证拆分,但是使用 refit = True 以获得在训练+验证数据集上训练的最佳模型。最终,当我展示我的模型的性能时,我使用保留的测试数据集进行测量。请问我的方法有问题吗?【参考方案2】:

是的,验证数据存在数据泄漏问题。您需要为GridSearchCV 设置refit = False,它不会重新拟合整个数据,包括训练和验证数据。

【讨论】:

以上是关于使用 Scikit-Learn GridSearchCV 与 PredefinedSplit 进行交叉验证 - 可疑的交叉验证结果的主要内容,如果未能解决你的问题,请参考以下文章

选择超参数 - 网格搜索

使用 yml 环境获取 scikit-learn 版本警告

使用 Scikit-Learn 数据上传查询机器学习

使用Python scikit-learn 库实现神经网络算法

使用 Scikit-learn 谷歌应用引擎

如何使用 scikit-learn 创建我自己的数据集?