机器学习:交叉验-- 评估预测性能

在同一个数据集上学习并测试一个预测模型犯了一个方法论错误:模型只是在看得见的样本上有一个很高的得分,而在未看见的样本上无法进行有效地预测。这种情形叫做过拟合(overfitting)。为了避免这种情形,一个常见的做法是:当做一个监督学习实验时,拿出一部分样本作为测试集X_test, y_test。记住“实验”并不打算仅表示学术用途,因为即使在商业环境中,机器学习通常实验性开始。

scikit-learn中,通过函数train_test_split可以快速地将数据集随机的分成训练集和测试集。让我们加载iris数据集去拟合一个SVM:

>>> import numpy as np
>>> from sklearn import cross_validation
>>> 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 = cross_validation.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...

当为预测器计算不同的设置的时候(像SVMC参数必须手动的设置),在测试集上任然有过拟合的风险,这是因为参数会被不停地调整直到预测器表现最优。这时候,测试集上的信息可能会“泄露”给模型,从而计算指标将不会再报告泛化表现。为了解决这个问题,再拿出数据集的一部分叫做“验证集”:在训练集上训练,然后在验证集上评测,然后在似乎快要成功的时候再在测试集上做最后的评估。

然而,把原始的数据集分成三个部分,会大幅地减少可以用来学习模型的样本数量,并且结果可能会依赖于(train_set,validation_set)的一个特定的随机选择。

解决这个问题的步骤叫交叉验证 cross-validation。一个测试集任然应该拿出类为最后的评估,但是当做交叉验证的时候将不再需要验证集。在基础的解决方案,K折交叉验证中,训练集被分裂成k个更小的集合(其他的解决方案会在下面描述,但是泛化来看都遵循相同的原则)。下面的步骤是为每个第k折的:
  1. 将k-1折作为训练集进行训练模型
  2. 结果模型在剩下的数据上进行验证(当做测试集来计算表现指标例如准确率)

最后,计算循环内所有计算出来的指标值的平均值作为K折交叉验证报告的指标。这个方案计算代价是昂贵的,但是并没有浪费太多的数据(因为它是这样固定一个任意测试集的),这个问题的一个主要的有点是:像逆向推理一样,样本的数量是很小的。

3.1.1. 计算交叉验证的指标

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

下面的例子是演示了如何通过分裂数据去估计一个线性kernel核的SVM在iris数据集上的准确率,连续5次拟合一个模型并且计算分数(每一次在一个不同的分裂数据集上):

>>> clf = svm.SVC(kernel='linear', C=1)
>>> scores = cross_validation.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_validation.cross_val_score(clf, iris.data, iris.target,
...     cv=5, scoring='f1_weighted')
>>> scores                                              
array([ 0.96...,  1.  ...,  0.96...,  0.96...,  1.        ])

iris这个例子中,目标类是均衡的所以F1-score和准确率是总是相等的。

cv参数是整数时,cross_val_score默认使用 KFold 或 StratifiedKFold策略,如果预测器是从ClassifierMixin中派生的话,后者将会被使用。

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

>>> n_samples = iris.data.shape[0]
>>> cv = cross_validation.ShuffleSplit(n_samples, n_iter=3,
...     test_size=0.3, random_state=0)

>>> cross_validation.cross_val_score(clf, iris.data, iris.target, cv=cv)
...                                                     
array([ 0.97...,  0.97...,  1.        ])
为拿出的数据进行数据转化

与在从训练集上拿出去的数据上测试预测器同等重要的是,预处理(例如,标准化,特征选择...)和相似的数据转化相似的从训练集上学习也应该应用到拿出去的数据上。

>>> from sklearn import preprocessing
>>> X_train, X_test, y_train, y_test = cross_validation.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 可以很容易地在交叉验证下提供这种行为:

>>> from sklearn.pipeline import make_pipeline
>>> clf = make_pipeline(preprocessing.StandardScaler(), svm.SVC(C=1))
>>> cross_validation.cross_val_score(clf, iris.data, iris.target, cv=cv)
...                                                 
array([ 0.97...,  0.93...,  0.95...])
3.1.1.1. 通过交叉验证获取预测

cross_val_predictcross_val_score有一个相似的借口,但是返回的是,为输入中的每一个元素,如果该元素在测试集中元素就获得一个预测。仅当交叉验证策略精确地分配所有的元素到一个测试集中才可以使用(否则,将会引发一个异常)。

这些预测可以被用作评估分类器:

>>> predicted = cross_validation.cross_val_predict(clf, iris.data,
...                                                iris.target, cv=10)
>>> metrics.accuracy_score(iris.target, predicted) 
0.966...

记住,这种计算方式下的结果可能会与使用cross_val_score以一组一组元素获得的有略微的差异。

3.1.2. 交叉验证迭代器

下面的章节列出了根据不同的交叉验证策略来分裂数据集的工具。

3.1.2.1 K-fold

KFold将所有的样本分成k组(如果k=n,等价于留一验证),每一组大小相等。然后利用k-1折进行学习,剩下一折用于测试。

下面是一个4个样本的2折交叉验证:

>>> import numpy as np
>>> from sklearn.cross_validation import KFold

>>> kf = KFold(4, n_folds=2)
>>> for train, test in kf:
...     print("%s %s" % (train, test))
[2 3] [0 1]
[0 1] [2 3]

每一折都有两个数组:第一个与训练集相关,第二个则是测试。因此,可以使用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]
Stratified(分层) k-fold

StratifiedKFoldk-fold的变种,它返回分层折:每一个集合都包含大约同等百分比的样本标签。

一个有10个样本的非均衡数据集的3折分层交叉验证:

>>> import numpy as np
>>> from sklearn.cross_validation import StratifiedKFold

>>> labels = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1])
>>> skf = StratifiedKFold(labels, 3)
>>> for train, test in skf:
...    print("%s %s"%(train,test))
...    print("%s %s" % (labels[train], labels[test]))
...    print("- - - - - - - - - - - - - - - - - - - - - - - - - -")
[2 3 6 7 8 9] [0 1 4 5]
[0 0 1 1 1 1] [0 0 1 1]
- - - - - - - - - - - - 
[0 1 3 4 5 8 9] [2 6 7]
[0 0 0 1 1 1 1] [0 1 1]
- - - - - - - - - - - - 
[0 1 2 4 5 6 7] [3 8 9]
[0 0 0 1 1 1 1] [0 1 1]
- - - - - - - - - - - - 
Label k-fold

LabelKFoldk-fold的变种,它确保同样的标签不会同时出现在测试和训练数据集中。如果你的数据是从不同的主题获取的并且你希望在不同的主题上进行训练和测试来避免过拟合的话,这是很有必要的。

想象一下,你有3个主题,每一个与1、2、3相关联:

>>> from sklearn.cross_validation import LabelKFold

>>> labels = np.array([1, 1, 1, 2, 2, 2, 3, 3, 3, 3])

>>> lkf = LabelKFold(labels,n_folds=3)
>>> for train, test in lkf:
...    print("%s %s"%(train,test))
...    print("%s %s" % (labels[train], labels[test]))
...    print("- - - - - - - - - - - - ")
[0 1 2 3 4 5] [6 7 8 9]
[1 1 1 2 2 2] [3 3 3 3]
- - - - - - - - - - - - 
[0 1 2 6 7 8 9] [3 4 5]
[1 1 1 3 3 3 3] [2 2 2]
- - - - - - - - - - - - 
[3 4 5 6 7 8 9] [0 1 2]
[2 2 2 3 3 3 3] [1 1 1]
- - - - - - - - - - - - 

每一个主题都在一个不同的测试折上,并且相同的主题永远不会同时出现在训练和测试集上。注意,由于数据的不平衡,每一折不会精确的有相同的大小。

Leave-One-Out - LOO

LeaveOneOut (LOO)是一个简单的交叉验证。每一个学习集合将会用剔除一个样本的所有的样本进行构造,测试集合就是那个剔除的样本。因此,对于n个样本,我们有n个不同的训练集合n个不同的测试集。这个交叉验证步骤不会浪费太多的数据因为仅有一个样本从训练集被移除了:

>>> from sklearn.cross_validation import LeaveOneOut

>>> loo = LeaveOneOut(4)
>>> for train, test in loo:
...     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个,这里

。此外,每一个模型都是有

而非

个样本训练的,。总的来说,假设k不是很大并且

,LOO要比k折交叉验证计算要昂贵的多。

在准确率方面,LOO的模型对于测试误差通常会导致高方差。直观的看,因为n个样本中

个被用来建造模型,每一折构造的模型实质上是彼此相同的,并和从整个训练集的建立的模型一样。

然而,如果问题的学习曲线对于训练集大小是陡峭的,那么5折或10折交叉验证可能会高估了泛化误差。

作为一个一般规则,大多数的作者和经验证据表明,5折或10折交叉验证要比LOO好。

3.1.2.5. Leave-P-Out - LPO

LeavePOut 和 LeaveOneOut是相似的,因为它创建训练/测试集通过从完整的数据集中移除P个样本。对n对样本来说,会产生

个训练-测试对。不像LeaveOneOut KFold,对于

测试集将会重叠。

4样本的Leave-2-Out:

>>> from sklearn.cross_validation import LeavePOut

>>> lpo = LeavePOut(4, p=2)
>>> for train, test in lpo:
...     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]
3.1.2.6. Leave-One-Label-Out - LOLO

LeaveOneLabelOut (LOLO)交叉验证方案会根据第三方提供的整数标签数组来拿出一部分样本。这个标签信息可以被用于编码任意域特定预先定义的交叉验证。

因此,每个训练组由除了特定标签的那些样本构成。

例如,在多个实验的情况下, LOLO可用于创建基于不同实验的交叉验证:我们使用除了某一实验的所有实验样本创建训练集:

>>> from sklearn.cross_validation import LeaveOneLabelOut

>>> labels = [1, 1, 2, 2]
>>> lolo = LeaveOneLabelOut(labels)
>>> for train, test in lolo:
...     print("%s %s" % (train, test))
[2 3] [0 1]
[0 1] [2 3]

另一个常见的应用是使用时间信息:例如这些标签可能是按年分进行收集的,因此允许针对基于时间的分割交叉验证。

警告:与StratifiedKFold相反,LeaveOneLabelOut类的labels不应该编码目标类去进行预测:StratifiedKFold是为了重新平衡因为分割后
数据集,确保训练/测试集中的样本有大约相同比重的类。而LeaveOneLabelOut将会做相反的事,它确保训练和测试集中不会共享相同的标签。
3.1.2.7. Leave-P-Label-Out

LeavePLabelOut与Leave-One-Label-Out相似,但是它从样本中移除P个标签。

一个Leave-2-Label Out的例子:

>>> from sklearn.cross_validation import LeavePLabelOut

>>> labels = np.array([1, 1, 2, 2, 3, 3])
>>> lplo = LeavePLabelOut(labels, p=2)
>>> for train, test in lplo:
...    print("%s %s" % (train, test))
...    print("%s %s" % (labels[train], labels[test]))
...    print("- - - - - - - - - - - - ")    
[4 5] [0 1 2 3]
[3 3] [1 1 2 2]
- - - - - - - - - - - - 
[2 3] [0 1 4 5]
[2 2] [1 1 3 3]
- - - - - - - - - - - - 
[0 1] [2 3 4 5]
[1 1] [2 2 3 3]
- - - - - - - - - - - - 
3.1.2.8. Random permutations cross-validation a.k.a. Shuffle & Split

ShuffleSplit迭代器将生成用户定义个数的独立的训练/测试数据集。样品先混洗,然后分成一对训练和测试集。

可以通过random_state来设置随机种子来控制结果的可重复性随机性。

一个Leave-2-Label Out的例子:

>>> ss = cross_validation.ShuffleSplit(5, n_iter=3, test_size=0.25,
...     random_state=0)
>>> for train_index, test_index in ss:
...     print("%s %s" % (train_index, test_index))
...
[1 3 4] [2 0]
[1 4 3] [0 2]
[4 0 2] [1 3]

ShuffleSplit是KFold的一个好的替代,因为它在迭代次数和训练集与测试集的比例上的控制更精细。

3.1.2.9. Label-Shuffle-Split

LabelShuffleSplit 是 ShuffleSplit 和 LeavePLabelsOut的结合。

这是一个使用例子:

>>> from sklearn.cross_validation import LabelShuffleSplit

>>> labels = np.array([1, 1, 2, 2, 3, 3, 4, 4])
>>> slo = LabelShuffleSplit(labels, n_iter=4, test_size=0.5,
                       random_state=0)
>>> for train, test in slo:
...    print("%s %s" % (train, test))
...    print("%s %s" % (labels[train], labels[test]))
...    print("- - - - - - - - - - - - ")  
[0 1 2 3] [4 5 6 7]
[1 1 2 2] [3 3 4 4]
- - - - - - - - - - - - 
[2 3 6 7] [0 1 4 5]
[2 2 4 4] [1 1 3 3]
- - - - - - - - - - - - 
[2 3 4 5] [0 1 6 7]
[2 2 3 3] [1 1 4 4]
- - - - - - - - - - - - 
[4 5 6 7] [0 1 2 3]
[3 3 4 4] [1 1 2 2]
- - - - - - - - - - - -  

LeavePLabelsOut这个行为被希望,而因为标签数太大导致P个标签的所有可能分区的代价昂贵时,LeavePLabelsOut就是有用的。在这种情况下,LabelShuffleSplit提供一个随机的由LeavePLabelsOut生成的训练/测试样本分裂。

3.1.2.10. Predefined Fold-Splits / Validation-Sets

对于某些数据集,一个关于数据分裂成训练和验证折或者几个交叉验证折的预先定义已经存在了。使用PredefinedSplit,当搜索 超参数时,它可能会使用这些折。

3.1.2.11. StratifiedShuffleSplit

StratifiedShuffleSplitShuffleSplit的变种,它返回分层分割,其通过保持每个目标类在完整集合中的百分比来创建分割。

3.1.3. A note on shuffling

如果数据排序不是任意的(例如,具有相同标签的样品是连续的),第一时间把它打乱也许为得到一个有意义的交叉验证结果而言是重要的。然而,相反如果样本不独立同分布也可能是对的。例如,例如,如果采样对应于新闻文章,并通过其出版的时间进行排序过,那么混洗数据将有可能导致一个模型过拟合并且得到一个膨胀的验证分数:它将会被在一个人为的与训练样本相似(时间相近)的样本上进行测试。

一些交叉验证迭代器如KFold有一个内建的选项使得在分裂数据前打乱数据。记住:
  1. 这比直接打乱数据消耗更少的内存。
  2. 默认情况下不会出现洗牌,包括对(分层)K倍交叉通过指定cv = some_integercross_val_score ,网格搜索等。但是记住train_test_split仍会返回一个随机分裂。
  3. random_state设为None意味着每次KFold(..., shuffle=True)迭代洗牌都将不同。然而GridSearchCV 单独调用它的fit方法将会为设置每一个参数集合验证使用相同的洗牌。
  4. 确保在相同的平台上结果是可重复的,为random_state使用一个固定的值。

3.1.4. Cross validation and model selection

交叉验证迭代器也可以使用网格搜索最佳的模型参数来进行模型选择。参见 Grid Search: Searching for estimator parameters

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaoshun007~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值