机器学习-->sklearn.Cross-validation

本篇博文将主详细讲机器学习模型选择中的交叉验证,全文翻译自sklearn.Cross-Validation。并且加上了我自己的一些见解。
学习预测函数的参数并在相同的数据上进行测试是一个方法上的错误:一个在训练集上能取得很好的成绩的模型,但是在测试集上效果却很差,这种情况称为过拟合。为了避免这种情况,通常(监督)机器学习实验时将一部分可用数据保存为测试集X_test,y_test。

train_test_split
scikit-learn可以将数据随机分组为训练集和测试集,使用train_test_split帮助函数快速计算。我们加iris数据集进行试验。

>>> 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%的数据用于评价分类器效果好坏。

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

注意:这里划分是原始数据,不是划分数据索引。
当评价不同超参数对应的学习器时,例如必须为SVM手动设置参数C时,仍然有可能在测试集上过度拟合,因为可以调整参数直到学习器效果最佳。这样,关于测试集的知识可以“泄漏”到模型中,并且评估指标不再报告泛化性能。为了解决这个问题,数据集的另一部分可以被称为所谓的“验证集”:在训练集上进行模型训练,之后对验证集进行评估调参,当实验成功时,可以在测试集上进行最终评估。

然而,通过将可用数据分为三组,我们大大减少了可用于学习模型的样本数,结果可能取决于一对(训练,验证)集合的特定随机选择。也即数据样本没有充分用来训练模型。

解决这个问题的方法是一个称为交叉验证(简称CV)的过程。测试集仍是在最后用来评价模型好坏,但是在做CV时不再需要验证集。在基本方法中,称为k-fold CV,将训练集分为k个较小的集合(其他方法如下所述,但通常遵循相同的原则)。对于k“folds”中的每一个,遵循以下过程:
• 随机选取k-1折的数据进行模型训练。
• 剩余的一份数据用来验证模型的好坏。

然后,循环中计算的值的平均值即是k-fold交叉验证报告的性能指标。这种方法在计算上可能是昂贵的,但是不会浪费太多的数据(如固定任意测试集的情况),这是问题的主要优点,例如反向推理,其中样本数量非常小。

计算交叉验证的指标
使用交叉验证的最简单的方法是在学习器和数据集上调用cross_val_score函数。

>>> 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%置信区间由下式给出:

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

默认情况下,每个CV迭代计算的分数是由分类器的score函数得出的。可以通过使用scoring参数来改变它:

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

当cv是整数时,默认情况下使用KFold 或者 StratifiedKFold 策略 进行划分。
当然也可以利用其它策略

>>> from sklearn.model_selection import ShuffleSplit
>>> n_samples = iris.data.shape[0]
>##n_splits: 重新洗牌和分裂迭代次数
>>> cv = ShuffleSplit(n_splits=3, test_size=0.3, random_state=0)
>>> cross_val_score(clf, iris.data, iris.target, cv=cv)
...                                                     
array([ 0.97...,  0.97...,  1.        ])

通过交叉验证获取预测
函数cross_val_predict具有与cross_val_score类似的接口,但是对于输入中的每个元素,返回当该元素在测试集中时获得的预测。只有交叉验证策略才能将所有元素精确地分配给测试集一次(否则引发异常)。
这种预测可以被运用到评价分类器:

>>> 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.966...

注意,该计算的结果可能与使用cross_val_score获得的结果略有不同,因为元素以不同的方式分组。

K-Fold
KFold将数据样本k个小组,称为分组。预测函数使用其中的k-1组数据进行学习,然后在剩下的一组数据上进行测试。每次选取不同的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): ##将X的索引分为两份
...     print("%s %s" % (train, test))
##满分,即分成的所有train能组成原始的索引,test同理。
[2 3] [0 1] #(train, test)
[0 1] [2 3] #(train, test)

每个折叠由两个阵列组成:第一个与训练集相关,第二个与测试集相关。因此,可以使用numpy索引创建训练/测试集:

>>> X = np.array([[0., 0.], [1., 1.], [-1., -1.], [2., 2.]])
>>> y = np.array([0, 1, 0, 1])
## 此时train=[0 1],test=[2 3]
>>> X_train, X_test, y_train, y_test = X[train], X[test], y[train], y[test]

Leave One Out(LOO)(留一法)
上面K-Fold是将n个数据样本分为K份,这里我们之间将其分为n份,我们每次取不同的n-1份用来训练模型,用剩下的1份做测试集,这样就会有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]

选择LOO赢应该注意一些已知的问题。与k折交叉验证相比,一个构造n个样本的n个模型,而不是k个模型,其中n>k。另外,每次n-1个样本进行模型训练。
就准确率来说,LOO可能会导致高方差,直观的看,每次在n个样本中选不同的n-1个样本建立模型,一共训练n个模型,因为每次选择的训练集都差不多,每次选择的训练集不具有独立性,导致实际构建的模型都很相似,最后并没有把方差降下来。
如果LOO与K-fold CV比较,LOO在N个样本上建立N个模型而不是k个,更进一步,N个模型的每一个都是在N-1个样本上训练的,而不是(k-1)*n/k。两种方法中,假定k不是很大,LOO比k-fold CV更耗时。

Leave P Out(LPO)(留P法)
LeavePOut与LeaveOneOut非常相似,因为它通过从完整数据样本中拿取p个样本创建测试集,剩余n-p个样本做训练集。对于n个样本,这产生{n \ choose p}训练测试对。与LeaveOneOut和KFold不同,测试集样本个数p> 1。当P>1时,测试集将会发生重叠,当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]

Random permutations cross-validation a.k.a Shuffle & Split
ShuffleSplit
ShuffleSplit迭代器产生指定数量的独立的train/test数据集划分,首先对样本全体随机打乱,然后再划分出train/test对,可以使用随机数种子random_state来控制数字序列发生器使得讯算结果可重现
ShuffleSplit是KFlod交叉验证的比较好的替代,他允许更好的控制迭代次数和train/test的样本比例
注意这里划分的不是数据,而是数据对应的索引

>>> from sklearn.model_selection import ShuffleSplit
>>> X = np.arange(5)
>>##n_splits: 重新洗牌和分裂迭代次数
>>> 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]

一些分类问题可能会在目标类别的分布上表现出很大的不平衡:例如,负样本数量可能是正样本数量的数倍,在这种情况下,建议使用StratifiedKFold和StratifiedShuffleSplit中实施的分层采样,以确保采样出的数据样本保持每个类原本所占的百分比。
Stratified K-fold
StratifiedKFold是k-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]

StratifiedShuffleSplit和ShuffleSplit的一个变体,返回分层划分,也就是在创建划分的时候要保证每一个划分中类的样本比例与整体数据集中的原始比例保持一致

Cross-Validation iteration for grouped data
如果潜在的生成过程产生相互依赖样本集,那么独立同分布的假设就不复存在。

这样的数据分组,其域特定的。举个例子,一个关于医学的数据集,这个数据集是从多个患者收集到的。从一个患者取出多个样本。而这些数据很可能取决于个别群体。在我们的示例中,每个样本的患者ID是其组标识符。

在这种情况下,我们想知道一个针对特定组的训练模型,是否在其他的组上有好效果。
我的理解:数据样本除了对应的标签类别以外,还有组group的分别,而相同的组之间特征依赖性又很高,如果不考虑组这个因素,直接拿进模型训练,那么这个模型的泛化能力肯定很弱,即是可能会有过拟合情况。GroupKFold可以检测到这种过度拟合情况。
其实就是按照groups(n组)对数据样本分割成k(k<=n)份,然后每次选择不同的k-1份做训练集,剩余的一份做测试集。
注意这里划分的不是数据,而是数据对应的索引

>>> 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]
##n_splits=3是按groups进行分割,表示按groups将样本分为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]
若:
>>> gkf = GroupKFold(n_splits=2)##表示按groups将数据样本分为2份
>>> 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]
[6 7 8 9] [0 1 2 3 4 5]

由以上结果可以看出,同一组的数据样本不可能同时出现在训练集和测试集。

Leave One Group Out
其实就是按照groups(n组)对数据样本分割成n份,然后每次选择不同的n-1份做训练集,剩余的一份做测试集。
注意这里划分的不是数据,而是数据对应的索引

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

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

Leave P Groups Out
类似于Leave One Group Out,只不过这里不是只选一组数据做测试集,而是选p组。(p>1)
注意这里划分的不是数据,而是数据对应的索引

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

Group Shuffle Split
Group Shuffle Split 结合了ShffleSplit和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]
## n_splits=4:表示按组将数据样本分组,test_size就是LeavePGroupsOut中的p
>>> 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]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值