30.md 38.1 KB
Newer Older
L
loopyme 已提交
1
# 3.1. 交叉验证:评估估算器的表现
L
loopyme 已提交
2 3 4 5 6 7 8 9

校验者:
        [@想和太阳肩并肩](https://github.com/apachecn/scikit-learn-doc-zh)
        [@樊雯](https://github.com/apachecn/scikit-learn-doc-zh)
        [@Loopy](https://github.com/loopyme)
翻译者:
        [@\S^R^Y/](https://github.com/apachecn/scikit-learn-doc-zh)

片刻小哥哥's avatar
片刻小哥哥 已提交
10
学习预测函数的参数,并在相同数据集上进行测试是一种错误的做法: 一个仅给出测试用例标签的模型将会获得极高的分数,但对于尚未出现过的数据它则无法预测出任何有用的信息。 这种情况称为 **overfitting(过拟合)**. 为了避免这种情况,在进行(监督)机器学习实验时,通常取出部分可利用数据作为 **test set(测试数据集)** `X_test, y_test`。需要强调的是这里说的 “experiment(实验)” 并不仅限于学术(academic),因为即使是在商业场景下机器学习也往往是从实验开始的。下面是模型训练中典型的交叉验证工作流流程图。通过[网格搜索](31)可以确定最佳参数。
L
loopyme 已提交
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

![grid_search_workflow.png](img/grid_search_workflow.png)

利用 scikit-learn 包中的 [`train_test_split`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html#sklearn.model_selection.train_test_split "sklearn.model_selection.train_test_split") 辅助函数可以很快地将实验数据集划分为任何训练集(training sets)和测试集(test sets)。 下面让我们载入 iris 数据集,并在此数据集上训练出线性支持向量机:

```py
>>> import numpy as np
>>> from sklearn.model_selection import train_test_split
>>> from sklearn import datasets
>>> from sklearn import svm

>>> iris = datasets.load_iris()
>>> iris.data.shape, iris.target.shape
((150, 4), (150,))

```

我们能快速采样到原数据集的 40% 作为测试集,从而测试(评估)我们的分类器:

```py
>>> X_train, X_test, y_train, y_test = train_test_split(
...     iris.data, iris.target, test_size=0.4, random_state=0)

>>> X_train.shape, y_train.shape
((90, 4), (90,))
>>> X_test.shape, y_test.shape
((60, 4), (60,))

>>> clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train)
>>> clf.score(X_test, y_test)                           
0.96...

```

当评价估计器的不同设置(”hyperparameters(超参数)”)时,例如手动为 SVM 设置的 `C` 参数, 由于在训练集上,通过调整参数设置使估计器的性能达到了最佳状态;但 _在测试集上_ 可能会出现过拟合的情况。 此时,测试集上的信息反馈足以颠覆训练好的模型,评估的指标不再有效反映出模型的泛化性能。 为了解决此类问题,还应该准备另一部分被称为 “validation set(验证集)” 的数据集,模型训练完成以后在验证集上对模型进行评估。 当验证集上的评估实验比较成功时,在测试集上进行最后的评估。

然而,通过将原始数据分为3个数据集合,我们就大大减少了可用于模型学习的样本数量, 并且得到的结果依赖于集合对(训练,验证)的随机选择。

这个问题可以通过 [交叉验证(CV )](https://en.wikipedia.org/wiki/Cross-validation_(statistics) 来解决。 交叉验证仍需要测试集做最后的模型评估,但不再需要验证集。

最基本的方法被称之为,_k-折交叉验证_ 。 k-折交叉验证将训练集划分为 k 个较小的集合(其他方法会在下面描述,主要原则基本相同)。 每一个 _k_ 折都会遵循下面的过程:

*   将 ![k-1](img/51d052e3e4c7f694f3c05eb4159ba243.jpg) 份训练集子集作为 training data (训练集)训练模型,
K
KaiWangKW 已提交
54
*   将剩余的 1 份训练集子集用于模型验证(也就是把它当做一个测试集来计算模型的性能指标,例如准确率)。
L
loopyme 已提交
55 56 57 58 59

_k_-折交叉验证得出的性能指标是循环计算中每个值的平均值。 该方法虽然计算代价很高,但是它不会浪费太多的数据(如固定任意测试集的情况一样), 在处理样本数据集较少的问题(例如,逆向推理)时比较有优势。

![grid_search_cross_validation.png](img/grid_search_cross_validation.png)

L
loopyme 已提交
60
## 3.1.1. 计算交叉验证的指标
L
loopyme 已提交
61 62 63

使用交叉验证最简单的方法是在估计器和数据集上调用 [`cross_val_score`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html#sklearn.model_selection.cross_val_score "sklearn.model_selection.cross_val_score") 辅助函数。

片刻小哥哥's avatar
片刻小哥哥 已提交
64
下面的示例展示了如何通过分割数据,拟合模型和计算连续 5 次的分数(每次不同分割)来估计 linear kernel 支持向量机在 iris 数据集上的精度:
L
loopyme 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

```py
>>> from sklearn.model_selection import cross_val_score
>>> clf = svm.SVC(kernel='linear', C=1)
>>> scores = cross_val_score(clf, iris.data, iris.target, cv=5)
>>> scores                                              
array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ])
```

评分估计的平均得分和 95% 置信区间由此给出:

```py
>>> print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
Accuracy: 0.98 (+/- 0.03)

```

默认情况下,每个 CV 迭代计算的分数是估计器的 `score` 方法。可以通过使用 scoring 参数来改变计算方式如下:

```py
>>> from sklearn import metrics
>>> scores = cross_val_score(
...     clf, iris.data, iris.target, cv=5, scoring='f1_macro')
>>> scores                                              
array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ])

```

片刻小哥哥's avatar
片刻小哥哥 已提交
93
详情请参阅 [scoring 参数: 定义模型评估规则](33) 。 在 Iris 数据集的情形下,样本在各个目标类别之间是平衡的,因此准确度和 F1-score 几乎相等。
L
loopyme 已提交
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145

`cv` 参数是一个整数时, [`cross_val_score`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html#sklearn.model_selection.cross_val_score "sklearn.model_selection.cross_val_score") 默认使用 [`KFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold "sklearn.model_selection.KFold")[`StratifiedKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html#sklearn.model_selection.StratifiedKFold "sklearn.model_selection.StratifiedKFold") 策略,后者会在估计器派生自 [`ClassifierMixin`](https://scikit-learn.org/stable/modules/generated/sklearn.base.ClassifierMixin.html#sklearn.base.ClassifierMixin "sklearn.base.ClassifierMixin") 时使用。

也可以通过传入一个交叉验证迭代器来使用其他交叉验证策略,比如:

```py
>>> from sklearn.model_selection import ShuffleSplit
>>> n_samples = iris.data.shape[0]
>>> cv = ShuffleSplit(n_splits=5, test_size=0.3, random_state=0)
>>> cross_val_score(clf, iris.data, iris.target, cv=cv)  
array([0.977..., 0.977..., 1.  ..., 0.955..., 1.        ])
```
另外一种可选方案是使用一个可迭代生成器作为索引数组产生(train, test) 划分,比如:

```py
>>> def custom_cv_2folds(X):
...     n = X.shape[0]
...     i = 1
...     while i <= 2:
...         idx = np.arange(n * (i - 1) / 2, n * i / 2, dtype=int)
...         yield idx, idx
...         i += 1
...
>>> custom_cv = custom_cv_2folds(iris.data)
>>> cross_val_score(clf, iris.data, iris.target, cv=custom_cv)
array([1.        , 0.973...])
```

>**保留数据的数据转换**
>
>正如在训练集中保留的数据上测试一个 predictor (预测器)是很重要的一样,预处理(如标准化,特征选择等)和类似的 [data transformations](../data_transforms.html#data-transforms) 也应该从训练集中学习,并应用于预测数据以进行预测:
 ```py
  >>> from sklearn import preprocessing
  >>> X_train, X_test, y_train, y_test = train_test_split(
  ...     iris.data, iris.target, test_size=0.4, random_state=0)
  >>> scaler = preprocessing.StandardScaler().fit(X_train)
  >>> X_train_transformed = scaler.transform(X_train)
  >>> clf = svm.SVC(C=1).fit(X_train_transformed, y_train)
  >>> X_test_transformed = scaler.transform(X_test)
  >>> clf.score(X_test_transformed, y_test)  
  0.9333...
  ```
>[`Pipeline`](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html#sklearn.pipeline.Pipeline "sklearn.pipeline.Pipeline") 可以更容易地组合估计器,在交叉验证下使用如下:
  ```py
  >>> from sklearn.pipeline import make_pipeline
  >>> clf = make_pipeline(preprocessing.StandardScaler(), svm.SVC(C=1))
  >>> cross_val_score(clf, iris.data, iris.target, cv=cv)
  ...                                                 
  array([ 0.97...,  0.93...,  0.95...])
  ```
>可以参阅 [Pipeline(管道)和 FeatureUnion(特征联合): 合并的评估器](doc/36).

L
loopyme 已提交
146
### 3.1.1.1. cross_validate 函数和多度量评估
L
loopyme 已提交
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191

`cross_validate` 函数与 `cross_val_score` 在下面的两个方面有些不同 -

*   它允许指定多个指标进行评估.
*   除了测试得分之外,它还会返回一个包含训练得分,拟合次数, score-times (得分次数)的一个字典。 It returns a dict containing training scores, fit-times and score-times in addition to the test score.

对于单个度量评估,其中 scoring 参数是一个字符串,可以调用或 None , keys 将是 - `['test_score', 'fit_time', 'score_time']`

而对于多度量评估,返回值是一个带有以下的 keys 的字典 - `['test_<scorer1_name>', 'test_<scorer2_name>', 'test_<scorer...>', 'fit_time', 'score_time']`

`return_train_score` 默认设置为 `True` 。 它增加了所有 scorers(得分器) 的训练得分 keys 。如果不需要训练 scores ,则应将其明确设置为 `False`

你还可以通过设置`return_estimator=True`来保留在所有训练集上拟合好的估计器。

可以将多个测度指标指定为list,tuple或者是预定义评分器(predefined scorer)的名字的集合

```py
>>> from sklearn.model_selection import cross_validate
>>> from sklearn.metrics import recall_score
>>> scoring = ['precision_macro', 'recall_macro']
>>> clf = svm.SVC(kernel='linear', C=1, random_state=0)
>>> scores = cross_validate(clf, iris.data, iris.target, scoring=scoring,
...                         cv=5)
>>> sorted(scores.keys())
['fit_time', 'score_time', 'test_precision_macro', 'test_recall_macro']
>>> scores['test_recall_macro']                       
array([0.96..., 1.  ..., 0.96..., 0.96..., 1.        ])

```

或作为一个字典 mapping 得分器名称预定义或自定义的得分函数:

```py
>>> from sklearn.metrics.scorer import make_scorer
>>> scoring = {'prec_macro': 'precision_macro',
...            'rec_macro': make_scorer(recall_score, average='macro')}
>>> scores = cross_validate(clf, iris.data, iris.target, scoring=scoring,
...                         cv=5, return_train_score=True)
>>> sorted(scores.keys())                 
['fit_time', 'score_time', 'test_prec_macro', 'test_rec_macro',
 'train_prec_macro', 'train_rec_macro']
>>> scores['train_rec_macro']                         
array([0.97..., 0.97..., 0.99..., 0.98..., 0.98...])
```

片刻小哥哥's avatar
片刻小哥哥 已提交
192
这里是一个使用单一指标的 `cross_validate` 的示例:
L
loopyme 已提交
193 194 195 196 197 198 199 200 201

```py
>>> scores = cross_validate(clf, iris.data, iris.target,
...                         scoring='precision_macro', cv=5,
...                         return_estimator=True)
>>> sorted(scores.keys())
['estimator', 'fit_time', 'score_time', 'test_score']
```

L
loopyme 已提交
202
### 3.1.1.2. 通过交叉验证获取预测
L
loopyme 已提交
203 204 205 206 207 208 209 210 211 212 213 214 215

除了返回结果不同,函数 [`cross_val_predict`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_predict.html#sklearn.model_selection.cross_val_predict "sklearn.model_selection.cross_val_predict") 具有和 [`cross_val_score`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html#sklearn.model_selection.cross_val_score "sklearn.model_selection.cross_val_score") 相同的接口, 对于每一个输入的元素,如果其在测试集合中,将会得到预测结果。交叉验证策略会将可用的元素提交到测试集合有且仅有一次(否则会抛出一个异常)。

>**警告** :交叉预测可能使用不当
>
>`cross_val_predict`函数的结果可能会与`cross_val_score`函数的结果不一样,因为在这两种方法中元素的分组方式不一样。函数`cross_val_score`在所有交叉验证的折子上取平均。但是,函数`cross_val_predict`只是简单的返回由若干不同模型预测出的标签或概率。因此,`cross_val_predict`不是一种适当的泛化错误的度量。

函数`cross_val_predict`比较适合做下列事儿:
* 从不同模型获得的预测结果的可视化。
* 模型混合: 在集成方法中,当一个有监督估计量的预测被用来训练另一个估计量时

可用的交叉验证迭代器在下面的章节将提到。

L
loopyme 已提交
216
> **示例**
L
loopyme 已提交
217 218 219 220 221 222 223
>*   [Receiver Operating Characteristic (ROC) with cross validation](https://scikit-learn.org/stable/auto_examples/model_selection/plot_roc_crossval.html#sphx-glr-auto-examples-model-selection-plot-roc-crossval-py),
>*   [Recursive feature elimination with cross-validation](https://scikit-learn.org/stable/auto_examples/feature_selection/plot_rfe_with_cross_validation.html#sphx-glr-auto-examples-feature-selection-plot-rfe-with-cross-validation-py),
>*    [Parameter estimation using grid search with cross-validation](https://scikit-learn.org/stable/auto_examples/model_selection/plot_grid_search_digits.html#sphx-glr-auto-examples-model-selection-plot-grid-search-digits-py),
>*   [Sample pipeline for text feature extraction and evaluation](https://scikit-learn.org/stable/auto_examples/model_selection/grid_search_text_feature_extraction.html#sphx-glr-auto-examples-model-selection-grid-search-text-feature-extraction-py),
>*   [绘制交叉验证预测图](https://scikit-learn.org/stable/auto_examples/plot_cv_predict.html#sphx-glr-auto-examples-plot-cv-predict-py),
>*   [Nested versus non-nested cross-validation](https://scikit-learn.org/stable/auto_examples/model_selection/plot_nested_cross_validation_iris.html#sphx-glr-auto-examples-model-selection-plot-nested-cross-validation-iris-py).

L
loopyme 已提交
224
## 3.1.2. 交叉验证迭代器
L
loopyme 已提交
225 226 227

接下来的部分列出了一些用于生成索引标号,用于在不同的交叉验证策略中生成数据划分的工具。

L
loopyme 已提交
228
### 3.1.2.1. 交叉验证迭代器–循环遍历数据
L
loopyme 已提交
229 230 231 232 233 234 235 236 237

假设一些数据是独立的和相同分布的 (i.i.d) 假定所有的样本来源于相同的生成过程,并假设生成过程没有记忆过去生成的样本。

在这种情况下可以使用下面的交叉验证器。

>**注意**
>
>尽管 i.i.d 数据是机器学习理论中的一个常见假设,但在实践中很少成立。如果知道样本是使用时间相关的过程生成的,则使用 [time-series aware cross-validation scheme](#timeseries-cv) 更安全。 同样,如果我们知道生成过程具有 group structure (群体结构)(从不同 subjects(主体) , experiments(实验), measurement devices (测量设备)收集的样本),则使用 [group-wise cross-validation](#group-cv) 更安全。

L
loopyme 已提交
238
#### 3.1.2.1.1. K 折
L
loopyme 已提交
239 240 241

[`KFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold "sklearn.model_selection.KFold") 将所有的样例划分为 ![k](img/f93871977da52a6d11045d57c3e18728.jpg) 个组,称为折叠 (fold) (如果 ![k = n](img/2953c3498ec0877c5ebcc172050cce88.jpg), 这等价于 _Leave One Out(留一)_ 策略),都具有相同的大小(如果可能)。预测函数学习时使用 ![k - 1](img/79b52c5c00ce59ba04383f6a0d670c6d.jpg) 个折叠中的数据,最后一个剩下的折叠会用于测试。

片刻小哥哥's avatar
片刻小哥哥 已提交
242
在 4 个样例的数据集上使用 2-fold 交叉验证的示例:
L
loopyme 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255

```py
>>> import numpy as np
>>> from sklearn.model_selection import KFold

>>> X = ["a", "b", "c", "d"]
>>> kf = KFold(n_splits=2)
>>> for train, test in kf.split(X):
...     print("%s  %s" % (train, test))
[2 3] [0 1]
[0 1] [2 3]

```
片刻小哥哥's avatar
片刻小哥哥 已提交
256
这个示例是关于交叉验证的可视化的。请注意KFold不被分类所影响.
L
loopyme 已提交
257 258 259 260 261 262 263 264 265 266 267
![sphx_glr_plot_cv_indices_0041.png](img/sphx_glr_plot_cv_indices_0041.png)

每个折叠由两个 arrays 组成,第一个作为 _training set_ ,另一个作为 _test set_ 。 由此,可以通过使用 numpy 的索引创建训练/测试集合:

```py
>>> X = np.array([[0., 0.], [1., 1.], [-1., -1.], [2., 2.]])
>>> y = np.array([0, 1, 0, 1])
>>> X_train, X_test, y_train, y_test = X[train], X[test], y[train], y[test]

```

L
loopyme 已提交
268
#### 3.1.2.1.2. 重复 K-折交叉验证
L
loopyme 已提交
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291

[`RepeatedKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RepeatedKFold.html#sklearn.model_selection.RepeatedKFold "sklearn.model_selection.RepeatedKFold") 重复 K-Fold n 次。当需要运行时可以使用它 [`KFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold "sklearn.model_selection.KFold") n 次,在每次重复中产生不同的分割。

2折 K-Fold 重复 2 次的示例:

```py
>>> import numpy as np
>>> from sklearn.model_selection import RepeatedKFold
>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
>>> random_state = 12883823
>>> rkf = RepeatedKFold(n_splits=2, n_repeats=2, random_state=random_state)
>>> for train, test in rkf.split(X):
...     print("%s  %s" % (train, test))
...
[2 3] [0 1]
[0 1] [2 3]
[0 2] [1 3]
[1 3] [0 2]

```

类似地, [`RepeatedStratifiedKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RepeatedStratifiedKFold.html#sklearn.model_selection.RepeatedStratifiedKFold "sklearn.model_selection.RepeatedStratifiedKFold") 在每个重复中以不同的随机化重复 n 次分层的 K-Fold 。

L
loopyme 已提交
292
#### 3.1.2.1.3. 留一交叉验证 (LOO)
L
loopyme 已提交
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

[`LeaveOneOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeaveOneOut.html#sklearn.model_selection.LeaveOneOut "sklearn.model_selection.LeaveOneOut") ( LOO) 是一个简单的交叉验证。每个学习集都是通过除了一个样本以外的所有样本创建的,测试集是被留下的样本。 因此,对于 ![n](img/c87d9110f3d32ffa5fa08671e4af11fb.jpg) 个样本,我们有 ![n](img/c87d9110f3d32ffa5fa08671e4af11fb.jpg) 个不同的训练集和 ![n](img/c87d9110f3d32ffa5fa08671e4af11fb.jpg) 个不同的测试集。这种交叉验证程序不会浪费太多数据,因为只有一个样本是从训练集中删除掉的:

```py
>>> from sklearn.model_selection import LeaveOneOut

>>> X = [1, 2, 3, 4]
>>> loo = LeaveOneOut()
>>> for train, test in loo.split(X):
...     print("%s  %s" % (train, test))
[1 2 3] [0]
[0 2 3] [1]
[0 1 3] [2]
[0 1 2] [3]

```

LOO 潜在的用户选择模型应该权衡一些已知的警告。 当与 ![k](img/f93871977da52a6d11045d57c3e18728.jpg) 折交叉验证进行比较时,可以从 ![n](img/c87d9110f3d32ffa5fa08671e4af11fb.jpg) 样本中构建 ![n](img/c87d9110f3d32ffa5fa08671e4af11fb.jpg) 模型,而不是 ![k](img/f93871977da52a6d11045d57c3e18728.jpg) 模型,其中 ![n > k](img/5cc4d35f246f0aeb95f154a5343635c2.jpg) 。 此外,每个在 ![n - 1](img/06d3f93ccdf3b4b5cd0fea7225848848.jpg) 个样本而不是在 ![(k-1) n / k](img/b38aca53acb7894dca026d3325f61a00.jpg) 上进行训练。在两种方式中,假设 ![k](img/f93871977da52a6d11045d57c3e18728.jpg) 不是太大,并且 ![k < n](img/d6f34fca0b5561181aa5263dbb97df74.jpg) , LOO 比 ![k](img/f93871977da52a6d11045d57c3e18728.jpg) 折交叉验证计算开销更加昂贵。

就精度而言, LOO 经常导致较高的方差作为测试误差的估计器。直观地说,因为 ![n](img/c87d9110f3d32ffa5fa08671e4af11fb.jpg) 个样本中的 ![n - 1](img/06d3f93ccdf3b4b5cd0fea7225848848.jpg) 被用来构建每个模型,折叠构建的模型实际上是相同的,并且是从整个训练集建立的模型。

但是,如果学习曲线对于所讨论的训练大小是陡峭的,那么 5- 或 10- 折交叉验证可以泛化误差增高。

作为一般规则,大多数作者和经验证据表明, 5- 或者 10- 交叉验证应该优于 LOO 。

L
loopyme 已提交
318
> **参考资料**:
L
loopyme 已提交
319 320 321 322 323 324 325
>*   [http://www.faqs.org/faqs/ai-faq/neural-nets/part3/section-12.html](http://www.faqs.org/faqs/ai-faq/neural-nets/part3/section-12.html);
>*   T. Hastie, R. Tibshirani, J. Friedman, [The Elements of Statistical Learning](http://statweb.stanford.edu/~tibs/ElemStatLearn), Springer 2009
>*   L. Breiman, P. Spector [Submodel selection and evaluation in regression: The X-random case](http://digitalassets.lib.berkeley.edu/sdtr/ucb/text/197.pdf), International Statistical Review 1992;
>*   R. Kohavi, [A Study of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection](http://web.cs.iastate.edu/~jtian/cs573/Papers/Kohavi-IJCAI-95.pdf), Intl. Jnt. Conf. AI
>*   R. Bharat Rao, G. Fung, R. Rosales, [On the Dangers of Cross-Validation. An Experimental Evaluation](http://people.csail.mit.edu/romer/papers/CrossVal_SDM08.pdf), SIAM 2008;
>*   G. James, D. Witten, T. Hastie, R Tibshirani, [An Introduction to Statistical Learning](http://www-bcf.usc.edu/~gareth/ISL), Springer 2013.

L
loopyme 已提交
326
#### 3.1.2.1.4. 留 P 交叉验证 (LPO)
L
loopyme 已提交
327 328 329

[`LeavePOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeavePOut.html#sklearn.model_selection.LeavePOut "sklearn.model_selection.LeavePOut")[`LeaveOneOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeaveOneOut.html#sklearn.model_selection.LeaveOneOut "sklearn.model_selection.LeaveOneOut") 非常相似,因为它通过从整个集合中删除 ![p](img/e2f9b08680b30cfb80102f69264fdd5c.jpg) 个样本来创建所有可能的 训练/测试集。对于 ![n](img/c87d9110f3d32ffa5fa08671e4af11fb.jpg) 个样本,这产生了 ![{n \choose p}](img/c683d0fa5d21d783e383612dda8ecad3.jpg) 个 训练-测试 对。与 [`LeaveOneOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeaveOneOut.html#sklearn.model_selection.LeaveOneOut "sklearn.model_selection.LeaveOneOut")[`KFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold "sklearn.model_selection.KFold") 不同,当 ![p > 1](img/aaa84c285eb96ed446fd34be4b51bbec.jpg) 时,测试集会重叠。

片刻小哥哥's avatar
片刻小哥哥 已提交
330
在有 4 个样例的数据集上使用 Leave-2-Out 的示例:
L
loopyme 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347

```py
>>> from sklearn.model_selection import LeavePOut

>>> X = np.ones(4)
>>> lpo = LeavePOut(p=2)
>>> for train, test in lpo.split(X):
...     print("%s  %s" % (train, test))
[2 3] [0 1]
[1 3] [0 2]
[1 2] [0 3]
[0 3] [1 2]
[0 2] [1 3]
[0 1] [2 3]

```

L
loopyme 已提交
348
#### 3.1.2.1.5. 随机排列交叉验证 a.k.a. Shuffle & Split
L
loopyme 已提交
349 350 351 352 353 354 355

[`ShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.ShuffleSplit.html#sklearn.model_selection.ShuffleSplit "sklearn.model_selection.ShuffleSplit")

[`ShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.ShuffleSplit.html#sklearn.model_selection.ShuffleSplit "sklearn.model_selection.ShuffleSplit") 迭代器 将会生成一个用户给定数量的独立的训练/测试数据划分。样例首先被打散然后划分为一对训练测试集合。

可以通过设定明确的 `random_state` ,使得伪随机生成器的结果可以重复。

片刻小哥哥's avatar
片刻小哥哥 已提交
356
这是一个使用的小示例:
L
loopyme 已提交
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376

```py
>>> from sklearn.model_selection import ShuffleSplit
>>> X = np.arange(5)
>>> ss = ShuffleSplit(n_splits=3, test_size=0.25,
...     random_state=0)
>>> for train_index, test_index in ss.split(X):
...     print("%s  %s" % (train_index, test_index))
...
[1 3 4] [2 0]
[1 4 3] [0 2]
[4 0 2] [1 3]

```
下面是交叉验证行为的可视化。注意ShuffleSplit不受分类的影响。

![sphx_glr_plot_cv_indices_0061.png](img/sphx_glr_plot_cv_indices_0061.png)

[`ShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.ShuffleSplit.html#sklearn.model_selection.ShuffleSplit "sklearn.model_selection.ShuffleSplit") 可以替代 [`KFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold "sklearn.model_selection.KFold") 交叉验证,因为其提供了细致的训练 / 测试划分的 数量和样例所占的比例等的控制。

L
loopyme 已提交
377
### 3.1.2.2. 基于类标签、具有分层的交叉验证迭代器
L
loopyme 已提交
378 379 380

一些分类问题在目标类别的分布上可能表现出很大的不平衡性:例如,可能会出现比正样本多数倍的负样本。在这种情况下,建议采用如 [`StratifiedKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html#sklearn.model_selection.StratifiedKFold "sklearn.model_selection.StratifiedKFold")[`StratifiedShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedShuffleSplit.html#sklearn.model_selection.StratifiedShuffleSplit "sklearn.model_selection.StratifiedShuffleSplit") 中实现的分层抽样方法,确保相对的类别频率在每个训练和验证 折叠 中大致保留。

L
loopyme 已提交
381
#### 3.1.2.2.1. 分层 k 折
L
loopyme 已提交
382 383 384

[`StratifiedKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html#sklearn.model_selection.StratifiedKFold "sklearn.model_selection.StratifiedKFold") 是 _k-fold_ 的变种,会返回 _stratified(分层)_ 的折叠:每个小集合中, 各个类别的样例比例大致和完整数据集中相同。

片刻小哥哥's avatar
片刻小哥哥 已提交
385
在有 10 个样例的,有两个略不均衡类别的数据集上进行分层 3-fold 交叉验证的示例:
L
loopyme 已提交
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404

```py
>>> from sklearn.model_selection import StratifiedKFold

>>> X = np.ones(10)
>>> y = [0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
>>> skf = StratifiedKFold(n_splits=3)
>>> for train, test in skf.split(X, y):
...     print("%s  %s" % (train, test))
[2 3 6 7 8 9] [0 1 4 5]
[0 1 3 4 5 8 9] [2 6 7]
[0 1 2 4 5 6 7] [3 8 9]

```
下面是该交叉验证方法的可视化。
![sphx_glr_plot_cv_indices_0071.png](img/sphx_glr_plot_cv_indices_0071.png)

[`RepeatedStratifiedKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RepeatedStratifiedKFold.html#sklearn.model_selection.RepeatedStratifiedKFold "sklearn.model_selection.RepeatedStratifiedKFold") 可用于在每次重复中用不同的随机化重复分层 K-Fold n 次。

L
loopyme 已提交
405
### 3.1.2.2.2. 分层随机 Split
L
loopyme 已提交
406 407 408 409 410 411

下面是该交叉验证方法的可视化。
![sphx_glr_plot_cv_indices_0091.png](img/sphx_glr_plot_cv_indices_0091.png)

[`StratifiedShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedShuffleSplit.html#sklearn.model_selection.StratifiedShuffleSplit "sklearn.model_selection.StratifiedShuffleSplit") 是 _ShuffleSplit_ 的一个变种,会返回直接的划分,比如: 创建一个划分,但是划分中每个类的比例和完整数据集中的相同。

L
loopyme 已提交
412
### 3.1.2.3. 用于分组数据的交叉验证迭代器
L
loopyme 已提交
413 414 415

如果潜在的生成过程产生依赖样本的 groups ,那么 i.i.d. 假设将会被打破。

片刻小哥哥's avatar
片刻小哥哥 已提交
416
这样的数据分组是特定于域的。一个示例是从多个患者收集医学数据,从每个患者身上采集多个样本。而这样的数据很可能取决于个人群体。 在我们的示例中,每个样本的患者 ID 将是其 group identifier (组标识符)。
L
loopyme 已提交
417 418 419 420 421

在这种情况下,我们想知道在一组特定的 groups 上训练的模型是否能很好地适用于看不见的 group 。为了衡量这一点,我们需要确保验证对象中的所有样本来自配对训练折叠中完全没有表示的组。

下面的交叉验证分离器可以用来做到这一点。 样本的 grouping identifier (分组标识符) 通过 `groups` 参数指定。

L
loopyme 已提交
422
#### 3.1.2.3.1. 组 k-fold
L
loopyme 已提交
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446

[`GroupKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupKFold.html#sklearn.model_selection.GroupKFold "sklearn.model_selection.GroupKFold") 是 k-fold 的变体,它确保同一个 group 在测试和训练集中都不被表示。 例如,如果数据是从不同的 subjects 获得的,每个 subject 有多个样本,并且如果模型足够灵活以高度人物指定的特征中学习,则可能无法推广到新的 subject 。 [`GroupKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupKFold.html#sklearn.model_selection.GroupKFold "sklearn.model_selection.GroupKFold") 可以检测到这种过拟合的情况。 Imagine you have three subjects, each with an associated number from 1 to 3:

```py
>>> from sklearn.model_selection import GroupKFold

>>> X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 8.8, 9, 10]
>>> y = ["a", "b", "b", "b", "c", "c", "c", "d", "d", "d"]
>>> groups = [1, 1, 1, 2, 2, 2, 3, 3, 3, 3]

>>> gkf = GroupKFold(n_splits=3)
>>> for train, test in gkf.split(X, y, groups=groups):
...     print("%s  %s" % (train, test))
[0 1 2 3 4 5] [6 7 8 9]
[0 1 2 6 7 8 9] [3 4 5]
[3 4 5 6 7 8 9] [0 1 2]

```

每个 subject 都处于不同的测试阶段,同一个科目从来没有在测试和训练过程中。请注意,由于数据不平衡,折叠的大小并不完全相同。

下面是该交叉验证方法的可视化。
![sphx_glr_plot_cv_indices_0051.png](img/sphx_glr_plot_cv_indices_0051.png)

L
loopyme 已提交
447
#### 3.1.2.3.2. 留一组交叉验证
L
loopyme 已提交
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471

[`LeaveOneGroupOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeaveOneGroupOut.html#sklearn.model_selection.LeaveOneGroupOut "sklearn.model_selection.LeaveOneGroupOut") 是一个交叉验证方案,它根据第三方提供的 array of integer groups (整数组的数组)来提供样本。这个组信息可以用来编码任意域特定的预定义交叉验证折叠。

每个训练集都是由除特定组别以外的所有样本构成的。

例如,在多个实验的情况下, [`LeaveOneGroupOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeaveOneGroupOut.html#sklearn.model_selection.LeaveOneGroupOut "sklearn.model_selection.LeaveOneGroupOut") 可以用来根据不同的实验创建一个交叉验证:我们使用除去一个实验的所有实验的样本创建一个训练集:

```py
>>> from sklearn.model_selection import LeaveOneGroupOut

>>> X = [1, 5, 10, 50, 60, 70, 80]
>>> y = [0, 1, 1, 2, 2, 2, 2]
>>> groups = [1, 1, 2, 2, 3, 3, 3]
>>> logo = LeaveOneGroupOut()
>>> for train, test in logo.split(X, y, groups=groups):
...     print("%s  %s" % (train, test))
[2 3 4 5 6] [0 1]
[0 1 4 5 6] [2 3]
[0 1 2 3] [4 5 6]

```

另一个常见的应用是使用时间信息:例如,组可以是收集样本的年份,从而允许与基于时间的分割进行交叉验证。

L
loopyme 已提交
472
#### 3.1.2.3.3. 留 P 组交叉验证
L
loopyme 已提交
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492

[`LeavePGroupsOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeavePGroupsOut.html#sklearn.model_selection.LeavePGroupsOut "sklearn.model_selection.LeavePGroupsOut") 类似于 [`LeaveOneGroupOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeaveOneGroupOut.html#sklearn.model_selection.LeaveOneGroupOut "sklearn.model_selection.LeaveOneGroupOut") ,但为每个训练/测试集删除与 ![P](img/4bfe956324cef23278c5192b0fb8029b.jpg) 组有关的样本。

Leave-2-Group Out 的示例:

```py
>>> from sklearn.model_selection import LeavePGroupsOut

>>> X = np.arange(6)
>>> y = [1, 1, 1, 2, 2, 2]
>>> groups = [1, 1, 2, 2, 3, 3]
>>> lpgo = LeavePGroupsOut(n_groups=2)
>>> for train, test in lpgo.split(X, y, groups=groups):
...     print("%s  %s" % (train, test))
[4 5] [0 1 2 3]
[2 3] [0 1 4 5]
[0 1] [2 3 4 5]

```

L
loopyme 已提交
493
#### 3.1.2.3.4. Group Shuffle Split
L
loopyme 已提交
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519

[`GroupShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupShuffleSplit.html#sklearn.model_selection.GroupShuffleSplit "sklearn.model_selection.GroupShuffleSplit") 迭代器是 [`ShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.ShuffleSplit.html#sklearn.model_selection.ShuffleSplit "sklearn.model_selection.ShuffleSplit")[`LeavePGroupsOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeavePGroupsOut.html#sklearn.model_selection.LeavePGroupsOut "sklearn.model_selection.LeavePGroupsOut") 的组合,它生成一个随机划分分区的序列,其中为每个分组提供了一个组子集。

这是使用的示例:

```py
>>> from sklearn.model_selection import GroupShuffleSplit

>>> X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 0.001]
>>> y = ["a", "b", "b", "b", "c", "c", "c", "a"]
>>> groups = [1, 1, 2, 2, 3, 3, 4, 4]
>>> gss = GroupShuffleSplit(n_splits=4, test_size=0.5, random_state=0)
>>> for train, test in gss.split(X, y, groups=groups):
...     print("%s  %s" % (train, test))
...
[0 1 2 3] [4 5 6 7]
[2 3 6 7] [0 1 4 5]
[2 3 4 5] [0 1 6 7]
[4 5 6 7] [0 1 2 3]

```
下面是该交叉验证方法的可视化。
![sphx_glr_plot_cv_indices_0081.png](img/sphx_glr_plot_cv_indices_0081.png)

当需要 [`LeavePGroupsOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeavePGroupsOut.html#sklearn.model_selection.LeavePGroupsOut "sklearn.model_selection.LeavePGroupsOut") 的操作时,这个类的信息是很有必要的,但是 组 的数目足够大,以至于用 ![P](img/4bfe956324cef23278c5192b0fb8029b.jpg) 组生成所有可能的分区将会花费很大的代价。在这种情况下, [`GroupShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupShuffleSplit.html#sklearn.model_selection.GroupShuffleSplit "sklearn.model_selection.GroupShuffleSplit") 通过 [`LeavePGroupsOut`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.LeavePGroupsOut.html#sklearn.model_selection.LeavePGroupsOut "sklearn.model_selection.LeavePGroupsOut") 提供了一个随机(可重复)的训练 / 测试划分采样。

L
loopyme 已提交
520
### 3.1.2.4. 预定义的折叠 / 验证集
L
loopyme 已提交
521 522 523 524 525

对一些数据集,一个预定义的,将数据划分为训练和验证集合或者划分为几个交叉验证集合的划分已经存在。 可以使用 [`PredefinedSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.PredefinedSplit.html#sklearn.model_selection.PredefinedSplit "sklearn.model_selection.PredefinedSplit") 来使用这些集合来搜索超参数。

比如,当使用验证集合时,设置所有验证集合中的样例的 `test_fold` 为 0,而将其他样例设置为 -1 。

L
loopyme 已提交
526
### 3.1.2.5. 交叉验证在时间序列数据中应用
L
loopyme 已提交
527 528 529

时间序列数据的特点是时间 (_autocorrelation(自相关性)_) 附近的观测之间的相关性。 然而,传统的交叉验证技术,例如 [`KFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold "sklearn.model_selection.KFold")[`ShuffleSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.ShuffleSplit.html#sklearn.model_selection.ShuffleSplit "sklearn.model_selection.ShuffleSplit") 假设样本是独立的且分布相同的,并且在时间序列数据上会导致训练和测试实例之间不合理的相关性(产生广义误差的估计较差)。 因此,对 “future(未来)” 观测的时间序列数据模型的评估至少与用于训练模型的观测模型非常重要。为了达到这个目的,一个解决方案是由 [`TimeSeriesSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.TimeSeriesSplit.html#sklearn.model_selection.TimeSeriesSplit "sklearn.model_selection.TimeSeriesSplit") 提供的。

L
loopyme 已提交
530
#### 3.1.2.5.1. 时间序列分割
L
loopyme 已提交
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553

[`TimeSeriesSplit`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.TimeSeriesSplit.html#sklearn.model_selection.TimeSeriesSplit "sklearn.model_selection.TimeSeriesSplit") 是 _k-fold_ 的一个变体,它首先返回 ![k](img/f93871977da52a6d11045d57c3e18728.jpg) 折作为训练数据集,并且 ![(k+1)](img/b25f834ac79280901c702fb1449740a3.jpg) 折作为测试数据集。 请注意,与标准的交叉验证方法不同,连续的训练集是超越前者的超集。 另外,它将所有的剩余数据添加到第一个训练分区,它总是用来训练模型。

这个类可以用来交叉验证以固定时间间隔观察到的时间序列数据样本。

对具有 6 个样本的数据集进行 3-split 时间序列交叉验证的示例:

```py
>>> from sklearn.model_selection import TimeSeriesSplit

>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4], [1, 2], [3, 4]])
>>> y = np.array([1, 2, 3, 4, 5, 6])
>>> tscv = TimeSeriesSplit(n_splits=3)
>>> print(tscv)  
TimeSeriesSplit(max_train_size=None, n_splits=3)
>>> for train, test in tscv.split(X):
...     print("%s  %s" % (train, test))
[0 1 2] [3]
[0 1 2 3] [4]
[0 1 2 3 4] [5]

```

L
loopyme 已提交
554
## 3.1.3. A note on shuffling
L
loopyme 已提交
555 556 557 558 559 560 561 562 563 564

如果数据的顺序不是任意的(比如说,相同标签的样例连续出现),为了获得有意义的交叉验证结果,首先对其进行 打散是很有必要的。然而,当样例不是独立同分布时打散则是不可行的。例如:样例是相关的文章,以他们发表的时间 进行排序,这时候如果对数据进行打散,将会导致模型过拟合,得到一个过高的验证分数:因为验证样例更加相似(在时间上更接近) 于训练数据。

一些交叉验证迭代器, 比如 [`KFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold "sklearn.model_selection.KFold") ,有一个内建的在划分数据前进行数据索引打散的选项。注意:

*   这种方式仅需要很少的内存就可以打散数据。
*   默认不会进行打散,包括设置 `cv=some_integer` (直接)k 折叠交叉验证的 [`cross_val_score`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html#sklearn.model_selection.cross_val_score "sklearn.model_selection.cross_val_score") , 表格搜索等。注意 [`train_test_split`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html#sklearn.model_selection.train_test_split "sklearn.model_selection.train_test_split") 会返回一个随机的划分。
*   参数 `random_state` 默认设置为 `None` ,这意为着每次进行 `KFold(..., shuffle=True)` 时,打散都是不同的。 然而, `GridSearchCV` 通过调用 `fit` 方法验证时,将会使用相同的打散来训练每一组参数。
*   为了保证结果的可重复性(在相同的平台上),应该给 `random_state` 设定一个固定的值。

L
loopyme 已提交
565
## 3.1.4. 交叉验证和模型选择
L
loopyme 已提交
566

片刻小哥哥's avatar
片刻小哥哥 已提交
567
交叉验证迭代器可以通过网格搜索得到最优的模型超参数,从而直接用于模型的选择。 这是另一部分 [调整估计器的超参数](31) 的主要内容。