接下来的部分列出了一些用于生成索引标号,用于在不同的交叉验证策略中生成数据划分的工具。
1. 交叉验证迭代器–循环遍历数据
假设一些数据是独立的和相同分布的 (i.i.d) 假定所有的样本来源于相同的生成过程,并假设生成过程没有记忆过去生成的样本。
在这种情况下可以使用下面的交叉验证器。
注意
尽管 i.i.d 数据是机器学习理论中的一个常见假设,但在实践中很少成立。如果知道样本是使用时间相关的过程生成的,则使用 time-series aware cross-validation scheme 更安全。 同样,如果我们知道生成过程具有 group structure (群体结构)(从不同 subjects(主体) , experiments(实验), measurement devices (测量设备)收集的样本),则使用 group-wise cross-validation 更安全。
1.1. K 折
KFold
将所有的样例划分为 个组,称为折叠 (fold) (如果 , 这等价于 Leave One Out(留一) 策略),都具有相同的大小(如果可能)。预测函数学习时使用 个折叠中的数据,最后一个剩下的折叠会用于测试。
在 4 个样例的数据集上使用 2-fold 交叉验证的示例:
>>> 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]
这个示例是关于交叉验证的可视化的。请注意KFold不被分类所影响.
每个折叠由两个 arrays 组成,第一个作为 training set ,另一个作为 test set 。 由此,可以通过使用 numpy 的索引创建训练/测试集合:
>>> 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]
1.2. 重复 K-折交叉验证
RepeatedKFold
重复 K-Fold n 次。当需要运行时可以使用它 KFold
n 次,在每次重复中产生不同的分割。
2折 K-Fold 重复 2 次的示例:
>>> 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
在每个重复中以不同的随机化重复 n 次分层的 K-Fold 。
1.3. 留一交叉验证 (LOO)
LeaveOneOut
(或 LOO) 是一个简单的交叉验证。每个学习集都是通过除了一个样本以外的所有样本创建的,测试集是被留下的样本。 因此,对于 个样本,我们有 个不同的训练集和 个不同的测试集。这种交叉验证程序不会浪费太多数据,因为只有一个样本是从训练集中删除掉的:
>>> 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 潜在的用户选择模型应该权衡一些已知的警告。 当与 折交叉验证进行比较时,可以从 样本中构建 模型,而不是 模型,其中 。 此外,每个在 个样本而不是在 上进行训练。在两种方式中,假设 不是太大,并且 , LOO 比 折交叉验证计算开销更加昂贵。
就精度而言, LOO 经常导致较高的方差作为测试误差的估计器。直观地说,因为 个样本中的 被用来构建每个模型,折叠构建的模型实际上是相同的,并且是从整个训练集建立的模型。
但是,如果学习曲线对于所讨论的训练大小是陡峭的,那么 5- 或 10- 折交叉验证可以泛化误差增高。
作为一般规则,大多数作者和经验证据表明, 5- 或者 10- 交叉验证应该优于 LOO 。
参考资料:
- http://www.faqs.org/faqs/ai-faq/neural-nets/part3/section-12.html;
- T. Hastie, R. Tibshirani, J. Friedman, The Elements of Statistical Learning, Springer 2009
- L. Breiman, P. Spector Submodel selection and evaluation in regression: The X-random case, International Statistical Review 1992;
- R. Kohavi, A Study of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection, Intl. Jnt. Conf. AI
- R. Bharat Rao, G. Fung, R. Rosales, On the Dangers of Cross-Validation. An Experimental Evaluation, SIAM 2008;
- G. James, D. Witten, T. Hastie, R Tibshirani, An Introduction to Statistical Learning, Springer 2013.
1.4. 留 P 交叉验证 (LPO)
LeavePOut
与 LeaveOneOut
非常相似,因为它通过从整个集合中删除 个样本来创建所有可能的 训练/测试集。对于 个样本,这产生了 个 训练-测试 对。与 LeaveOneOut
和 KFold
不同,当 时,测试集会重叠。
在有 4 个样例的数据集上使用 Leave-2-Out 的示例:
>>> 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]
1.5. 随机排列交叉验证 a.k.a. Shuffle & Split
ShuffleSplit
迭代器 将会生成一个用户给定数量的独立的训练/测试数据划分。样例首先被打散然后划分为一对训练测试集合。
可以通过设定明确的 random_state
,使得伪随机生成器的结果可以重复。
这是一个使用的小示例:
>>> 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不受分类的影响。
ShuffleSplit
可以替代 KFold
交叉验证,因为其提供了细致的训练 / 测试划分的 数量和样例所占的比例等的控制。
2. 基于类标签、具有分层的交叉验证迭代器
一些分类问题在目标类别的分布上可能表现出很大的不平衡性:例如,可能会出现比正样本多数倍的负样本。在这种情况下,建议采用如 StratifiedKFold
和 StratifiedShuffleSplit
中实现的分层抽样方法,确保相对的类别频率在每个训练和验证 折叠 中大致保留。
2.1. 分层 k 折
StratifiedKFold
是 k-fold 的变种,会返回 stratified(分层) 的折叠:每个小集合中, 各个类别的样例比例大致和完整数据集中相同。
在有 10 个样例的,有两个略不均衡类别的数据集上进行分层 3-fold 交叉验证的示例:
>>> 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]
下面是该交叉验证方法的可视化。
RepeatedStratifiedKFold
可用于在每次重复中用不同的随机化重复分层 K-Fold n 次。
2.2. 分层随机 Split
下面是该交叉验证方法的可视化。
StratifiedShuffleSplit
是 ShuffleSplit 的一个变种,会返回直接的划分,比如: 创建一个划分,但是划分中每个类的比例和完整数据集中的相同。
2.3. 用于分组数据的交叉验证迭代器
如果潜在的生成过程产生依赖样本的 groups ,那么 i.i.d. 假设将会被打破。
这样的数据分组是特定于域的。一个示例是从多个患者收集医学数据,从每个患者身上采集多个样本。而这样的数据很可能取决于个人群体。 在我们的示例中,每个样本的患者 ID 将是其 group identifier (组标识符)。
在这种情况下,我们想知道在一组特定的 groups 上训练的模型是否能很好地适用于看不见的 group 。为了衡量这一点,我们需要确保验证对象中的所有样本来自配对训练折叠中完全没有表示的组。
下面的交叉验证分离器可以用来做到这一点。 样本的 grouping identifier (分组标识符) 通过 groups
参数指定。
2.3.1. 组 k-fold
GroupKFold
是 k-fold 的变体,它确保同一个 group 在测试和训练集中都不被表示。 例如,如果数据是从不同的 subjects 获得的,每个 subject 有多个样本,并且如果模型足够灵活以高度人物指定的特征中学习,则可能无法推广到新的 subject 。 GroupKFold
可以检测到这种过拟合的情况。 Imagine you have three subjects, each with an associated number from 1 to 3:
>>> 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 都处于不同的测试阶段,同一个科目从来没有在测试和训练过程中。请注意,由于数据不平衡,折叠的大小并不完全相同。
下面是该交叉验证方法的可视化。
2.3.2. 留一组交叉验证
LeaveOneGroupOut
是一个交叉验证方案,它根据第三方提供的 array of integer groups (整数组的数组)来提供样本。这个组信息可以用来编码任意域特定的预定义交叉验证折叠。
每个训练集都是由除特定组别以外的所有样本构成的。
例如,在多个实验的情况下, LeaveOneGroupOut
可以用来根据不同的实验创建一个交叉验证:我们使用除去一个实验的所有实验的样本创建一个训练集:
>>> 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]
另一个常见的应用是使用时间信息:例如,组可以是收集样本的年份,从而允许与基于时间的分割进行交叉验证。
2.3.3. 留 P 组交叉验证
LeavePGroupsOut
类似于 LeaveOneGroupOut
,但为每个训练/测试集删除与 组有关的样本。
Leave-2-Group Out 的示例:
>>> 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]
2.3.4. Group Shuffle Split
GroupShuffleSplit
迭代器是 ShuffleSplit
和 LeavePGroupsOut
的组合,它生成一个随机划分分区的序列,其中为每个分组提供了一个组子集。
这是使用的示例:
>>> 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]
下面是该交叉验证方法的可视化。
当需要 LeavePGroupsOut
的操作时,这个类的信息是很有必要的,但是 组 的数目足够大,以至于用 组生成所有可能的分区将会花费很大的代价。在这种情况下, GroupShuffleSplit
通过 LeavePGroupsOut
提供了一个随机(可重复)的训练 / 测试划分采样。
2.4. 预定义的折叠 / 验证集
对一些数据集,一个预定义的,将数据划分为训练和验证集合或者划分为几个交叉验证集合的划分已经存在。 可以使用 PredefinedSplit
来使用这些集合来搜索超参数。
比如,当使用验证集合时,设置所有验证集合中的样例的 test_fold
为 0,而将其他样例设置为 -1 。
2.5. 交叉验证在时间序列数据中应用
时间序列数据的特点是时间 (autocorrelation(自相关性)) 附近的观测之间的相关性。 然而,传统的交叉验证技术,例如 KFold
和 ShuffleSplit
假设样本是独立的且分布相同的,并且在时间序列数据上会导致训练和测试实例之间不合理的相关性(产生广义误差的估计较差)。 因此,对 “future(未来)” 观测的时间序列数据模型的评估至少与用于训练模型的观测模型非常重要。为了达到这个目的,一个解决方案是由 TimeSeriesSplit
提供的。
2.5.1. 时间序列分割
TimeSeriesSplit
是 k-fold 的一个变体,它首先返回 折作为训练数据集,并且 折作为测试数据集。 请注意,与标准的交叉验证方法不同,连续的训练集是超越前者的超集。 另外,它将所有的剩余数据添加到第一个训练分区,它总是用来训练模型。
这个类可以用来交叉验证以固定时间间隔观察到的时间序列数据样本。
对具有 6 个样本的数据集进行 3-split 时间序列交叉验证的示例:
>>> 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]
3. A note on shuffling
如果数据的顺序不是任意的(比如说,相同标签的样例连续出现),为了获得有意义的交叉验证结果,首先对其进行 打散是很有必要的。然而,当样例不是独立同分布时打散则是不可行的。例如:样例是相关的文章,以他们发表的时间 进行排序,这时候如果对数据进行打散,将会导致模型过拟合,得到一个过高的验证分数:因为验证样例更加相似(在时间上更接近) 于训练数据。
一些交叉验证迭代器, 比如 KFold
,有一个内建的在划分数据前进行数据索引打散的选项。注意:
- 这种方式仅需要很少的内存就可以打散数据。
- 默认不会进行打散,包括设置
cv=some_integer
(直接)k 折叠交叉验证的cross_val_score
, 表格搜索等。注意train_test_split
会返回一个随机的划分。 - 参数
random_state
默认设置为None
,这意为着每次进行KFold(..., shuffle=True)
时,打散都是不同的。 然而,GridSearchCV
通过调用fit
方法验证时,将会使用相同的打散来训练每一组参数。 - 为了保证结果的可重复性(在相同的平台上),应该给
random_state
设定一个固定的值。
4. 交叉验证和模型选择
交叉验证迭代器可以通过网格搜索得到最优的模型超参数,从而直接用于模型的选择。 这是另一部分 调整估计器的超参数 的主要内容。