【译】交叉验证(Crossd_validation)

来源:互联网 发布:为啥叫程序员叫程序猿 编辑:程序博客网 时间:2024/06/05 14:28

按自己的理解对scikit-learn中的模型选择中的交叉验证部分,进行了简单的翻译,供大家参考、学习。

Cross validation:评估算法的表现

在同一个数据集上学习算法的参数和进行预测是方法性的错误。很有可能会导致Overfitting。避免过拟合的常用方法是将一部分数据作为测试集,用以对算法进行测试。

在scikit-learn中,可以用train_test_split快速地将数据集分为训练集和测试集。

import numpy as npfrom sklearn.model_selection import train_test_splitfrom sklearn import datasetsfrom sklearn import svmiris = datasets.load_iris()iris.data.shape, iris.target.shape
((150, 4), (150,))

利用train_test_split将数据分出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.96666666666666667

当评估不同的参数设置,对算法表现的影响时,仍然存在则过拟合的风险。因为在调整参数,优化测试集的算法表现时,测试集的信息已经泄漏进模型中了。

为了解决这个问题,仍需要一部分数据作为验证集(Validation set)。这样,用训练集(Train set)的数据训练模型;用验证集对模型参数调剂,如上述程序中的C值;最后,算法的评价在测试集(Test set)上完成。

这样的做法会带来两个新的问题,一是,将数据分成了三分,降低了训练模型的数据量;二是,最后算法的表现会较依赖三个数据集的划分。

可以采用交叉验证集(Cross validation set)来解决上述两个问题。测试集仍然划分出来,用以最后对算法进行评价。但单独的验证集不再需要了。将训练集划分为k个小的数据集,称之为k-fold CV。对每个fold进行下列过程:
* 1 用其他k-1 folds作为训练数据,训练模型
* 2 模型的结果用剩下的fold评价

模型的性能用上述循环中的k-fold交叉验证集的平均值表现。这种做法增加了计算量,但提高了数据的利用效率。

计算交叉验证的度量指标(metrics)

最为简单的方法是调用cross_val_score

from sklearn.model_selection import cross_val_scoreclf = svm.SVC(kernel = 'linear', C = 1)scores = cross_val_score(clf, iris.data, iris.target, cv = 5)scores
array([ 0.96666667,  1.        ,  0.96666667,  0.96666667,  1.        ])

默认给出的是estimator的score值,可以对其进行修改:

from sklearn import metricsscores = cross_val_score(clf, iris.data, iris.target, cv = 5, scoring = 'f1_macro')scores
array([ 0.96658312,  1.        ,  0.96658312,  0.96658312,  1.        ])

但cv的取值是整型时,cross_val_score默认采用KFoldStratifiedKFold str的策略。

也可以同通过传入交叉验证迭代器,以采用其他交叉验证的策略。

from sklearn.model_selection import ShuffleSplitn_samples = iris.data.shape[0]cv = ShuffleSplit(n_splits=3, test_size=0.3, random_state=0)cross_val_score(clf, iris.data, iris.target, cv=cv)
array([ 0.97777778,  0.97777778,  1.        ])

cross_validate函数

cross_val_score相比,cross_validate有两方面的不同:
* 1 允许指定多个评价指标;
* 2 除测试分数之外,还返回包括训练分数、拟合时间和评分时间的字典

from sklearn.model_selection import cross_validatefrom sklearn.metrics import recall_scorescoring = ['precision_macro', 'recall_macro']clf = svm.SVC(kernel='linear', C=1, random_state=0)scores = cross_validate(clf, iris.data, iris.target, scoring=scoring,                        cv=5, return_train_score=False)sorted(scores.keys())
['fit_time', 'score_time', 'test_precision_macro', 'test_recall_macro']
scores['test_recall_macro']
array([ 0.96666667,  1.        ,  0.96666667,  0.96666667,  1.        ])

cross_val_predict函数

cross_val_predictcross_val_validate的接口是相似的,但其返回的是所有输入的正确率。

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

交叉验证迭代器(iterators)

独立同分布数据的交叉验证迭代器

假设一些数据是独立的并且服从同样的分布(即数据由相同的过程产生,但其产生过程不含前数据集的信息)。这种数据是理论上的,在实际中,很难找到满足条件的真实数据。

K-fold

KFold将所有的样本分为k组(fold),模型用k-1组学习参数,用剩下的一组测试。下面是一个4个样本的数据集,将其分为2个folds。

import numpy as npfrom sklearn.model_selection import KFoldX = ['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]

每个fold由两个数组构成,第一个是与训练集相关,第二个这是与测试集相关。因此可以通过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]
X_train
array([[ 0.,  0.],       [ 1.,  1.]])
y_train
array([0, 1])
X_test
array([[-1., -1.],       [ 2.,  2.]])
y_test
array([0, 1])

Repeated K-Fold

Repeated K-Fold重复**K-Fold**n次,每次重复产生不同的划分方式。

import numpy as npfrom sklearn.model_selection import RepeatedKFoldX = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])random_state = 12883823rkf = 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]

去掉一个样本的策略(Leave One Out, LOO)

from sklearn.model_selection import LeaveOneOutX = [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比KFold的计算成本高。

LOO策略经常会导致模型会有较高的反差。从直觉上来看,每次用n-1的样本去学习模型参数,每次的训练集几乎是相同的,与在整个训练集上训练模型没有太大的差别。

通常来说,分为5-10个fold较为可靠。

去掉P个样本的策略(Leave P Out, LPO)

LPOLOO相似,从整个数据集中,移去p个样本,产生训练、测试样本对。

from sklearn.model_selection import LeavePOutX = 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]

随机排列策略

from sklearn.model_selection import ShuffleSplitX = np.arange(5)ss = ShuffleSplit(n_splits=5, test_size=0.40,    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][2 4 0] [3 1][3 1 0] [4 2]

ShuffleSplitKFold一个良好的替代策略,可以控制迭代的次数和训练集、测试集的比例。

不平衡数据集的交叉验证迭代器

一些分类问题的数据集是不平衡的,例如肿瘤良性、恶性分类问题,肿瘤是恶性的样本量占总的样本量的比例很低。此时就需要使用StratifiedKFold* StratifiedShuffleKFold*,使得在每个训练、测试fold中,各类样本的比例大致相同。

StratifiedKFold

from sklearn.model_selection import StratifiedKFoldX = 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]

StratifiedShuffleKFold

是由ShuffleSplit演变而来。

分组数据的交叉验证迭代器

如果数据是分组的,在同一个组中的样本彼此不独立,而在不同组的样本是独立的。例如从患者身上采集的医疗数据,在同一个患者上采集的多个样本是不独立的,属于同一组,在不同患者上采集的数据则是独立的。这类数据应有组别的属性信息。

对于这种数据,就需要保证所有在测试集中的样本的所属的组别不来自于该训练集样本所属组别。

Group k-fold

样本的组别信息以groups体现。

from sklearn.model_selection import GroupKFoldX = [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]

GroupKFold,就不会有同一组别的样本既出现在训练集中,又出现在测试集中的情况。

去除p组的策略

from sklearn.model_selection import LeavePGroupsOutX = 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]

随机分组的策略

from sklearn.model_selection import GroupShuffleSplitX = [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.2, random_state=0)for train, test in gss.split(X, y, groups=groups):    print("%s %s" % (train, test))
[0 1 2 3 6 7] [4 5][2 3 4 5 6 7] [0 1][0 1 2 3 4 5] [6 7][0 1 4 5 6 7] [2 3]

时间序列数据的交叉验证

TimeSeriesSplit

from sklearn.model_selection import TimeSeriesSplitX = 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)  for train, test in tscv.split(X):    print("%s %s" % (train, test))
TimeSeriesSplit(max_train_size=None, n_splits=3)[0 1 2] [3][0 1 2 3] [4][0 1 2 3 4] [5]

打乱注意点

当样本是独立同分布时,随机打乱是可以得到更有意义的正交验证结果。但当样本不服从独立同分布时,可能会导致相反的结果。

一些交叉验证迭代器,例如KFold,内部有在划分数据之前是否打乱数据顺序的选项。

note:
* 这样比直接打乱,要节省内存;
* 默认没有打乱,但train_test_split返回随机的划分结果
* random_state默认是None,意味着每次打乱都是不同的(KFold(…, shuffle=True)), 但网格搜索将会通过仅调用fit方法,使用的是同样的划分
* 如果需要每次划分相同,将random_state设为一个整数