嵌套交叉验证(Nested cross-validation)

1. 传统交叉验证和嵌套交叉验证的区别

在开始分享嵌套交叉验证前,首先以K-Fold为例,区分K-Fold交叉验证和嵌套K-Fold交叉验证的区别,这是我刚开示始学习时的困惑点:

(1)K-Fold交叉验证:只有一个loop(循环),即内层循环

(a) 将数据集切分为k-折叠;

(b) 对于任意$i \in [1; k]$,在第i个折叠上测试模型,并且在剩余的折叠上训练模型;

(c) 最后的结果是计算k个验证结果的均值。

(2)嵌套K-Fold交叉验证:有两个loops(循环),即内层循环和外层循环

(a) 对于在K-Fold中每一个i,都有一个嵌套的K-Fold交叉验证。

以上内容详见。

2. 嵌套交叉验证简介

为什么需要嵌套的交叉验证:

在scikit-learn官方文档中指出:“嵌套交叉验证包含于交叉验证,并用在训练模型的步骤中,如果模型的超参数需要被优化,则应该选择嵌套交叉验证方法。

嵌套交叉验证是通过对基础模型泛化误差的估计来进行超参数的搜索,以得到模型最佳参数。” 内置的交叉验证是传统交叉验证法(这里常指K-Fold交叉验证)的延伸,传统的交叉验证仅是将数据集拆分为训练集和测试集,无法解决最优模型的选择及模型调参问题。

它的弊端主要有两个:其一、可能会造成信息泄漏;其二、由于是对相同数据进行误差估计,所以会导致较高的偏差(当用可能的模型最佳超参数对相同的训练集和测试集进行误差的计算时,模型是有偏的,会导致较大偏差) 。 模型选择看重偏差和方差,因此一个好的评估模型真实误差的方法应是结合无偏和低方差两个方面。

内置交叉验证的原理如下图所示,其运行流程包含两个循环,即外层循环和内层循环。内层循环是指带有搜索模型最佳超参数功能的交叉验证,目的是给外层循环提供模型的最佳超参数。例如,随机搜索或者网格搜索。而外层循环是给内层循环提供训练数据,同时保留部分数据,以作对内层循环模型的测试。通过这样的方式,可以防止数据的信息泄漏,以得到相对较低的模型评分偏差。

如果从较为抽象的视角看交叉验证的原理,可以参加下图所示流程:其中Require表示需要提供的超参数,RandomSample(${P}{sets}$)是一个从超参数网格中获取随机数据集的函数。

何时使用嵌套的交叉验证:

如果数据集非常大时,使用内置交叉验证法的计算成本会很高。如果你的数据集包涵几千个样本,你就会发现计算过程是较漫长的。 内置交叉验证的使用场景较为有限,尤其应用在数据量不会很大的领域,如生物领域(数据量不会很大,一般指小于几千个样本的数据)。

当计算成本不是很大时,需要考虑可以提供的硬件条件。 如果满足以上两个标准,就可以考虑使用嵌套交叉验证来计算数据,得到几乎无偏的估计误差,从而对比不同算法的表现。

以下是关于何时使用嵌套交叉验证的两句话,详见论文:

其一,“在使用随机森林,支持向量机(Gaussian kernel),和梯度提升等算法时,很可能不需要使用嵌套交叉验证。” (这里说的是“很可能”,不是一个绝对的表达,因此,如果计算成本是可支付的,那么嵌套交叉验证仍是需要的)

其二,“当使用任何分类算法时,内置交叉验证过程很可能是不需要使用的。” (这里说的仍是“很可能”,但如果需可以使用的超参数是有限的,即你不需要如何费力调节,那么使用嵌套交叉验证就会很浪费时间)。

后嵌套交叉验证步骤:

如果模型结果稳定(例如,一些超参数的设置基本得到相同的误差估计结果),接下来可以使用传统的交叉验证。

运行步骤总结如下:

(1)将所有你希望使用的算法放到嵌套交叉验证中计算模型误差。 a. 如果结果稳定就进入到下一步,结果不稳定就就该结果不稳定的模型。

(2)选择泛化误差最小的模型。

(3)将该算法在带有网格搜索或随机搜索的交叉验证中运行(不提供任何最佳的超参数)。

(4)将模型在嵌套交叉验证中的得分与上一步中使用的交叉验证得分进行比较。 从而,可以检验传统的交叉验证方法是否可以对模型进行无偏估计。如果结果相差很大,将嵌套交叉验证的得分作为传统交叉验证的指标,并在传统的交叉验证中引入误差估计。

以上内容参考。

3. 嵌套交叉验证可以应用于时间序列数据

在熟悉嵌套交叉验证方法和其优良的性质后,可以发现嵌套交叉验证可以用于时间序列数据的模型选择和参数优化。对于包含时间信息的数据,如果使用传统的交叉验证方法,单纯的将数据集随机拆分为训练集和测试集,则模型在测试集的表现很可能不好,即出现过拟合的情况。

因此,传统的交叉验证方法在处理时间序列数据时具有一定的局限性。(其中:时间序列数据指的是在每一个样本中,至少含有一个时间戳字段)

为避免对时间序列数据集不当的拆分,模型在训练集上表现较佳,而在测试集表现较差,即出现过拟合现象。传统的交叉验证方法不适用于时间序列数据。

针对时间序列数据应着重解决如下三个问题:

(1)拆分时间序列数据不会造成信息的泄漏;

(2)在使用内置的交叉验证时,可以在独立的测试集上获得误差的无偏估计;

(3)数据集可以对多时间序列数据进行验证。(参见Courtney Cochrane,2019)

4. Scikit-learn中代码实现

以下是scikit-learn中,应用莺尾花(iris)数据集进行K-Fold交叉验证和嵌套K-Fold交叉验证的对比,代码如下:

 

 

 

from sklearn.datasets import load_iris
from matplotlib import pyplot as plt
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, cross_val_score, KFold
import numpy as np
​
print(__doc__)
​
# Number of random trials
NUM_TRIALS = 30
​
# Load the dataset
iris = load_iris()
X_iris = iris.data
y_iris = iris.target
​
# Set up possible values of parameters to optimize over
p_grid = {"C": [1, 10, 100],
          "gamma": [.01, .1]}
​
# We will use a Support Vector Classifier with "rbf" kernel
svm = SVC(kernel="rbf")
​
# Arrays to store scores
non_nested_scores = np.zeros(NUM_TRIALS)
nested_scores = np.zeros(NUM_TRIALS)
​
# Loop for each trial
for i in range(NUM_TRIALS):
​
    # Choose cross-validation techniques for the inner and outer loops,
    # independently of the dataset.
    # E.g "GroupKFold", "LeaveOneOut", "LeaveOneGroupOut", etc.
    inner_cv = KFold(n_splits=4, shuffle=True, random_state=i)
    outer_cv = KFold(n_splits=4, shuffle=True, random_state=i)
​
    # Non_nested parameter search and scoring
    clf = GridSearchCV(estimator=svm, param_grid=p_grid, cv=inner_cv)
    clf.fit(X_iris, y_iris)
    non_nested_scores[i] = clf.best_score_
​
    # Nested CV with parameter optimization
    nested_score = cross_val_score(clf, X=X_iris, y=y_iris, cv=outer_cv)
    nested_scores[i] = nested_score.mean()
​
score_difference = non_nested_scores - nested_scores
​
print("Average difference of {:6f} with std. dev. of {:6f}."
      .format(score_difference.mean(), score_difference.std()))
​
# Plot scores on each trial for nested and non-nested CV
plt.figure()
plt.subplot(211)
non_nested_scores_line, = plt.plot(non_nested_scores, color='r')
nested_line, = plt.plot(nested_scores, color='b')
plt.ylabel("score", fontsize="14")
plt.legend([non_nested_scores_line, nested_line],
           ["Non-Nested CV", "Nested CV"],
           bbox_to_anchor=(0, .4, .5, 0))
plt.title("Non-Nested and Nested Cross Validation on Iris Dataset",
          x=.5, y=1.1, fontsize="15")
​
# Plot bar chart of the difference.
plt.subplot(212)
difference_plot = plt.bar(range(NUM_TRIALS), score_difference)
plt.xlabel("Individual Trial #")
plt.legend([difference_plot],
           ["Non-Nested CV - Nested CV Score"],
           bbox_to_anchor=(0, 1, .8, 0))
plt.ylabel("score difference", fontsize="14")
​
plt.show()

结果展示如下图所示:

5. 有意思的小问题

以下是某网友在StackExchage上提的小问题:

Question:

我理解小的样本量使用交叉验证的最好方法不是传统的hold-out交叉验证,而是使用整个数据集,并应用K-Fold交叉验证。但是,在我的例子中,我想找到最佳超参数和选择最佳模型,我 认为在我的例子中,嵌套交叉验证可能是更好的选择。

如果我理解嵌套交叉验证的方法,例如,将数据集分为2个折叠。应用每一个折叠并执行一个K-Fold交叉验证,决定模型的最佳超参数,并用另一个折叠执行K-Fold交叉验证来决定最佳模型。我这样做对吗?

基于对上文交叉验证的理解,他应该怎么处理数据?

Answer:

  • 在内部loop(循环)中,你选择最佳超参数可以通过在内层循环的训练折叠训练不同的超参数,用内层循环的测试折叠测试模型,以找到最佳超参数(每个模型的)。
  • 然后使用选定的最佳模型,在外部的测试集上测试模型。
  • 外循环的平均得分就是你模型表现的估计值。

综上,本文分享了对嵌套交叉验证的理解及其应用,有关交叉验证其它部分的内容将在后续进行分享。

 

(1)获取更多优质内容及精彩资讯,可前往:https://www.cda.cn/?seo

(2)了解更多数据领域的优质课程:

 

  • 8
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
`nested-draggable` 是 `vuedraggable` 库提供的一个嵌套拖拽组件,可以用于实现多层级嵌套的拖拽排序功能。 使用 `nested-draggable`,需要先安装 `vuedraggable` 库: ``` npm install vuedraggable --save ``` 然后在需要使用 `nested-draggable` 的组件中,引入 `vuedraggable` 和 `nested-draggable` 组件: ```vue <template> <div> <nested-draggable v-model="list" :options="draggableOptions" class="list"> <template #default="{ element }"> <div :class="[element.type === 'folder' ? 'folder' : 'item']"> <div class="drag-handle"></div> <div class="content"> <div>{{ element.name }}</div> <div v-if="element.children"> <nested-draggable :list="element.children" :options="draggableOptions" class="sublist"></nested-draggable> </div> </div> </div> </template> </nested-draggable> </div> </template> <script> import draggable from 'vuedraggable' import nestedDraggable from 'vuedraggable/dist/vuedraggable.nested' export default { components: { draggable, nestedDraggable }, data () { return { list: [ { type: 'folder', name: 'Folder 1', children: [ { type: 'item', name: 'Item 1' }, { type: 'item', name: 'Item 2' } ] }, { type: 'folder', name: 'Folder 2', children: [ { type: 'item', name: 'Item 3' } ] } ], draggableOptions: { group: 'items', animation: 150, handle: '.drag-handle', ghostClass: 'ghost', chosenClass: 'chosen' } } } } </script> <style> .list { display: flex; flex-direction: column; padding-left: 20px; } .item { display: flex; align-items: center; padding: 10px; border: 1px solid #ccc; } .folder { border: 1px solid #ccc; margin-bottom: 10px; padding: 10px; } .drag-handle { width: 20px; height: 20px; margin-right: 10px; background-color: #ccc; cursor: move; } .chosen { outline: 2px dashed #ccc; } </style> ``` 在以上代码中,我们采用了 `nested-draggable` 组件实现了多层级嵌套的拖拽排序功能。在 `nested-draggable` 组件中,我们绑定了 `list` 数组作为拖拽列表,设置了拖拽配置项 `draggableOptions`,并通过插槽自定义了拖拽元素的展示方式。 需要注意的是,`nested-draggable` 中嵌套的 `nested-draggable` 组件需要绑定子列表的数据,并设置对应的拖拽配置项。在样式方面,我们通过 CSS 对拖拽元素和容器进行了样式的设置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值