机器学习中的交叉验证

时间:2022-05-08
本文章向大家介绍机器学习中的交叉验证,主要内容包括前言、计算交叉验证指标、获取交叉验证预测结果、交叉验证迭代器、基于类标签的交叉验证迭代器、交叉验证在时间序列数据中应用、交叉验证实例、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

总第100篇

本篇讲讲机器学习中的交叉验证问题,并利用sklearn实现。

前言

在说交叉验证以前,我们先想一下我们在搭建模型时的关于数据切分的常规做法[直接利用train_test_split把所有的数据集分成两部分:train_datatest_data,先在train_data上进行训练,然后再在test_data上进行测试评估模型效果的好坏]。

因为我们训练模型时,不是直接把数丢进去就好了,而是需要对模型的不断进行调整(比如参数),使模型在测试集上的表现足够好,但是即使模型在测试集上效果好,不一定在其他数据集上效果好,因为这个模型是“专门”针对这个测试集而调整的,你为了测试这个模型真正的效果,你就得找另外的一部分数据,看模型在这些数据上的效果怎么样,只有模型在另外的数据上效果也好,那才可以说明的模型的效果是真的好(泛化能力不错,也可以理解成是举一反三的能力)。

其实这个和我们上学时候的上课—做练习题—考试的关系很像,我们在每上完每一节课的时候,就会去做练习题来检验这节课的知识点掌握的怎么样,你的练习题几乎全部能做对,可能是因为刚上完这个章节,你一看到题就知道用这节课的知识点,直接把知识点套进去就可以做出来,每章如此,但是,期末考试的时候是把所有的章节结合起来考,不会告诉你用哪个知识点,这个时候就是检验你是否真的把这个知识点学会了的时候,你只有在期末考试的时候考的好,才能说明你是真的学习好。

这样就需要把数据分成三份,一份训练、一份验证、一份测试,先在训练集上训练模型,然后验证模型结果,最后再在测试集上判断模型的真正效果,但是这样做的结果就是大幅降低了数据的使用率,因训练数据不够多而造成欠拟合,并且数据切分的随机性也会对模型的效果有影响,这两个问题可以通过交叉验证(CV)的方式解决。

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

  • 将 k-1 份训练集子集作为 training data (训练集)训练模型,
  • 将剩余的 1 份训练集子集作为验证集用于模型验证(也就是利用该数据集计算模型的性能指标,例如准确率)。

计算交叉验证指标

使用交叉验证最简单的方法是在估计器和数据集上调用cross_val_score辅助函数。

下面的例子展示了如何通过分割数据,拟合模型和计算连续 5 次的分数(每次不同分割)来估计 linear kernel 支持向量机在 iris 数据集上的精度:

>>> 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.        ])

默认情况下,每个 CV 迭代计算的分数是估计器的score方法。可以通过使用scoring参数来改变,scoring参数可选的值有“f1-score,neg_log_loss,roc_auc”等指标,具体值可看:

http://sklearn.apachecn.org/cn/0.19.0/modules/model_evaluation.html

设置方式,如下:

>>> 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.        ])

获取交叉验证预测结果

通过cross_val_predict方法得到交叉验证模型的预测结果, 对于每一个输入的元素,如果其在测试集合中,将会得到预测结果。交叉验证策略会将可用的元素提交到测试集合有且仅有一次(否则会抛出一个异常)

>>> from sklearn.model_selection import cross_val_predict
>>> predicted = cross_val_predict(clf, iris.data, iris.target, cv=10)
>>> metrics.accuracy_score(iris.target, predicted) 
0.973...

交叉验证迭代器

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

K折(KFold)

KFold 将所有的样例划分为 k 个组,称为折叠 (fold) (如果 k = n, 这等价于 Leave One Out(留一) 策略),都具有相同的大小(如果可能)。预测函数学习时使用 k - 1 个折叠中的数据,最后一个剩下的折叠会用于测试。

>>> 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]

每个折叠由两个 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]
留一交叉验证 (LOO)

LeaveOneOut (或 LOO) 是一个简单的交叉验证。每个学习集都是通过除了一个样本以外的所有样本创建的,测试集是被留下的样本。 因此,对于 n 个样本,我们有 n 个不同的训练集和 n 个不同的测试集。这种交叉验证程序不会浪费太多数据,因为只有一个样本是从训练集中删除掉的:

>>> 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]
留 P 交叉验证 (LPO)

LeavePOut 与 LeaveOneOut 非常相似,因为它通过从整个集合中删除 p 个样本来创建所有可能的 训练/测试集。对于 n 个样本,这产生了 {n choose p} 个 训练-测试 对。与 LeaveOneOut 和 KFold 不同,当 p > 1 时,测试集会重叠。

>>> 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]
随机排列交叉验证(Shuffle)

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可以替代 KFold交叉验证,因为其提供了细致的训练 / 测试划分的数量和样例所占的比例等的控制。

基于类标签的交叉验证迭代器

一些分类问题在目标类别的分布上可能表现出很大的不平衡性:例如,可能会出现比正样本多数倍的负样本。可以采用 StratifiedKFoldStratifiedShuffleSplit中实现的分层抽样方法,确保相对的类别频率在每个训练和验证折叠中大致保留。

分层k折

StratifiedKFoldk-fold的变种,会返回stratified(分层)的折叠:每个小集合中,各个类别的样例比例大致和完整数据集中相同。

>>> 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]
分层随机Split

StratifiedShuffleSplitShuffleSplit的一个变种,会返回直接的划分,比如:创建一个划分,但是划分中每个类的比例和完整数据集中的相同。

>>> from sklearn.model_selection import StratifiedShuffleSplit
>>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
>>> y = np.array([0, 0, 1, 1])
>>> sss = StratifiedShuffleSplit(n_splits=3, test_size=0.5, random_state=0)
>>> sss.get_n_splits(X, y)
3
>>> print(sss)       
StratifiedShuffleSplit(n_splits=3, random_state=0, ...)
>>> for train_index, test_index in sss.split(X, y):
...    print("TRAIN:", train_index, "TEST:", test_index)
...    X_train, X_test = X[train_index], X[test_index]
...    y_train, y_test = y[train_index], y[test_index]
TRAIN: [1 2] TEST: [3 0]
TRAIN: [0 2] TEST: [1 3]
TRAIN: [0 2] TEST: [3 1]

交叉验证在时间序列数据中应用

时间序列数据的特点是时间 (autocorrelation(自相关性))之间是具有相关性的。然而,传统的交叉验证技术,例如 KFoldShuffleSplit假设样本是独立的且分布相同的,并且在时间序列数据上会导致训练和测试实例之间不合理的相关性(产生广义误差的估计较差)。 因此,对 “future(未来)” 观测的时间序列数据模型的评估至少与用于训练模型的观测模型非常重要。为了达到这个目的,一个解决方案是由TimeSeriesSplit提供的。

时间序列分割

TimeSeriesSplitk-fold的一个变体,它首先返回k折作为训练数据集,并且 (k+1) 折作为测试数据集。请注意,与标准的交叉验证方法不同,连续的训练集是超越前者的超集。另外,它将所有的剩余数据添加到第一个训练分区,它总是用来训练模型。

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

对具有 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]

交叉验证实例

上面提到的几种方法都是用来生成train和test的索引编号,而不像train_test_split方法直接可以生成训练集和数据集,我们只需要利用索引的方式去把对应的train和test索引出来即可,拿最简单的Kfold为例,具体的实现方式如下:

#导入所需要的库
>>> from sklearn import svm  
>>> from sklearn.model_selection import KFold

>>> digits = datasets.load_digits()   #load scikit-learn库里的实验数据集:digits
>>> X_digits = digits.data  #获得样本特征数据部分
>>> y_digits = digits.target  #获得样本label
>>> svc = svm.SVC(C=1, kernel='linear')  #初始化svm分类器

>>> kf = KFold(n_splits=3)
>>> for train, test in kf.split(X):
>>>     #此处train、test里有交叉验证对象中已经初始化好的3组训练样本和测试样本所需的位置标号
>>>     [svc.fit(X_digits[train], y_digits[train]).score(X_digits[test], y_digits[test]) for train, test in kfold]  

你还可以看:

机器学习模型效果评估

机器学习中非平衡数据处理

机器学习中的特征选择