 $$ \newcommand{\R}{\mathbb{R}} \newcommand{\E}{\mathbb{E}} \newcommand{\x}{\mathbf{x}} \newcommand{\y}{\mathbf{y}} \newcommand{\wv}{\mathbf{w}} \newcommand{\av}{\mathbf{\alpha}} \newcommand{\bv}{\mathbf{b}} \newcommand{\N}{\mathbb{N}} \newcommand{\id}{\mathbf{I}} \newcommand{\ind}{\mathbf{1}} \newcommand{\0}{\mathbf{0}} \newcommand{\unit}{\mathbf{e}} \newcommand{\one}{\mathbf{1}} \newcommand{\zero}{\mathbf{0}} \newcommand\rfrac[2]{^{#1}\!/_{#2}} \newcommand{\norm}[1]{\left\lVert#1\right\rVert} $$

# 交叉验证

## 描述

当使用机器学习算法时，一个普遍存在的问题是_过度拟合_，或者当一个算法“记住了”训练数据，但在推断样本外情况时却做得很差。处理过拟合问题的一种常见方法是从原始训练算法中保留一些数据子集，然后测量拟合算法在这个保留集上的性能。这通常称为_交叉验证_。模型在数据的一个子集上进行训练，然后在另一组数据上进行验证。

## 交叉验证策略


有如下几种策略提供数据。FlinkML有方便的方法

*   Train-Test切分
*   Train-Test-Holdout切分
*   K-Fold切分
*   Multi-Random切分


### Train-Test 切分

最简单的切分方法是“trainTestSplit”。这个切分接受一个数据集和一个参数_fraction_。_fraction_表示应该分配给训练集的数据集的一部分。这个切分还需要两个额外的可选参数_precise_和_seed_。

默认情况下，切分是通过随机决定是否将一个观察值分配给训练数据集(probability = _fraction_)来完成的。然而，当_precise_为“true”时，需要采取额外的步骤来确保训练集尽可能接近数据集$\cdot$ _fraction_的长度。

该方法返回一个新的'TrainTestDataSet'对象，该对象包含'.training'属性的训练数据集和'.testing'属性的测试数据集。

### Train-Test-Holdout切分

在某些情况下，算法已经“学习”过测试集。为了解决这个问题，需要在训练-测试-保持策略引入了第二个保持集，这个保持集被恰当地称为_holdout_。

传统上，训练和测试是对算法进行正常的训练，然后在保持集上对算法进行最后的测试。理想情况下，预测误差/模型的得分在保留集中不会比在测试集中观察到的结果有显著差异。

在训练-测试-保持策略中，我们牺牲了初始拟合算法的样本量，以增加模型不过度拟合的信心。

当使用'trainTestHoldout'切分时，'Double'类型的_分数_被长度为3的_分数_数组替换。第一个元素对应于用于训练的部分，第二个对应测试，第三对应保持。这个数组的权重是相对的，例如数组'Array(3.0, 2.0, 1.0)'会导致大概50%的观测值在训练集，33%的观测值在测试集，17%的观测值在保持集。

### K-Fold切分

在_k-fold_策略中，数据集被分割为_k_份相等子集。然后为每个_k_子集创建一个'TrainTestDataSet'，其中子集是'.training' 数据集，其余子集为'.testing'集。

对于每个训练集，训练算法，然后基于相关测试集的预测来评估算法。当在所保持的数据集上具有一致等级（例如:预测误差）的算法时，我们可以确信我们的方法（例如，算法/算法参数的选择/迭代次数）对于过度拟合是鲁棒的。

[K-Fold 交叉验证](https://en.wikipedia.org/wiki/Cross-validation_(statistics)#k-fold_cross-validation)

### 多随机切分

可以认为_多随机_策略是_train-test-holdout_策略更通用的形式。事实上,'.trainTestHoldoutSplit'是'multiRandomSplit'的简单包装器，它还将数据集打包到一个"trainTestHoldoutDataSet"对象中。

第一个主要区别是'multiRandomSplit'接受任意长度的分数数组。例如，可以创建多个holdout集。或者，可以将'kFoldSplit'看作是'multiRandomSplit'的包装器(确实如此)，不同之处在于'kFoldSplit'创建大小大致相同的子集，而'multiRandomSplit'将创建任何大小的子集。

第二个主要区别是'multiRandomSplit'返回一个数据集数组，其大小和比例与作为参数传递的_分数数组_相同。

## 参数

各种各样的'Splitter'方法共享很多参数。

| 参数 | 类型 | 描述 | 使用方法 |
| --- | --- | --- | --- |
| `input` | `DataSet[Any]` | 切分的数据集 | `randomSplit`
`multiRandomSplit`
`kFoldSplit`
`trainTestSplit`
`trainTestHoldoutSplit` |
| `seed` | `Long` | 用于播种随机数生成器，该生成器将数据集分类为其他数据集。 | `randomSplit`
`multiRandomSplit`
`kFoldSplit`
`trainTestSplit`
`trainTestHoldoutSplit` |
| `precise` | `Boolean` | 当为真时，要使数据集尽可能接近规定的比例。 | `randomSplit`
`trainTestSplit` |
| `fraction` | `Double` | 分配给第一个'输入'部分或训练数据集。必须在(0,1)范围内 | `randomSplit`
`trainTestSplit` |
| `fracArray` | `Array[Double]` | 一个数组，规定了输出数据集的比例（比例不需要总和为1或者在范围（0,1）内） | `multiRandomSplit`
`trainTestHoldoutSplit` |
| `kFolds` | `Int` | 将“输入”数据集分成的子集的数目。 | `kFoldSplit` |

## 例子


```
// An input dataset- does not have to be of type LabeledVector val data: DataSet[LabeledVector] = ...

// A Simple Train-Test-Split val dataTrainTest: TrainTestDataSet = Splitter.trainTestSplit(data, 0.6, true)

// Create a train test holdout DataSet val dataTrainTestHO: trainTestHoldoutDataSet = Splitter.trainTestHoldoutSplit(data, Array(6.0, 3.0, 1.0))

// Create an Array of K TrainTestDataSets val dataKFolded: Array[TrainTestDataSet] =  Splitter.kFoldSplit(data, 10)

// create an array of 5 datasets val dataMultiRandom: Array[DataSet[T]] = Splitter.multiRandomSplit(data, Array(0.5, 0.1, 0.1, 0.1, 0.1))
```



