第二十一天 交叉验证

2019-01-22  本文已影响11人  未不明不知不觉

到目前为止,为了评估我们的监督模型,我们使用 train_test_split 函数将数据集划分为
训练集和测试集,在训练集上调用 fit 方法来构建模型,并且在测试集上用 score 方法来
评估这个模型——对于分类问题而言,就是计算正确分类的样本所占的比例。下面是这个
过程的一个示例

from sklearn.datasets import  make_blobs
from sklearn.linear_model import  LogisticRegression
from sklearn.model_selection import  train_test_split

X,y = make_blobs(random_state=0)

X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=0)

logreg = LogisticRegression().fit(X_train,y_train)
print("Test set score {:.2f}".format(logreg.score(X_test,y_test)))

输出为

0.88

请记住,之所以将数据划分为训练集和测试集,是因为我们想要度量模型对前所未见的新数据的泛化性能。我们对模型在训练集上的拟合效果不感兴趣,而是想知道模型对于训练
过程中没有见过的数据的预测能力

我们将从两个方面进行模型评估。我们首先介绍交叉验证,然后讨论评估分类和回归\性能的方法,其中前者是一种更可靠的评估泛化性能的方法,后者是在默认度量(score方法给出的精度和 R2)之外的方法。我们还将讨论网格搜索,这是一种调节监督模型参数以获得最佳泛化性能的有效方法。

交叉验证

交叉验证(cross-validation)是一种评估泛化性能的统计学方法,它比单次划分训练集和测试集的方法更加稳定、全面。在交叉验证中,数据被多次划分,并且需要训练多个模型。最常用的交叉验证是 k 折交叉验证(k-fold cross-validation),其中 k 是由用户指定的数字,通常取 5 或 10。在执行 5 折交叉验证时,首先将数据划分为(大致)相等的 5 部分,每一部分叫作折(fold)。接下来训练一系列模型。使用第 1 折作为测试集、其他折(2~5)作为训练集来训练第一个模型。利用 2~5 折中的数据来构建模型,然后在 1 折上评估精度。之后构建另一个模型,这次使用 2 折作为测试集,1、3、4、5 折中的数据作为训练集。利用 3、4、5 折作为测试集继续重复这一过程。对于将数据划分为训练集和测试集的这 5 次划分,每一次都要计算精度。最后我们得到了 5 个精度值

scikit-learn中的交叉验证

scikit-learn 是利用 model_selection 模块中的 cross_val_score 函数来实现交叉验证的。cross_val_score 函数的参数是我们想要评估的模型、训练数据与真实标签。我们在 iris数据集上对 LogisticRegression 进行评估

交叉验证的优点

使用交叉验证而不是将数据单次划分为训练集和测试集,这种做法具有下列优点。首先,train_test_split 对数据进行随机划分。想象一下,在随机划分数据时我们很“幸运”,所有难以分类的样例都在训练集中。在这种情况下,测试集将仅包含“容易分类的”样例,并且测试集精度会高得不切实际。相反,如果我们“不够幸运”,则可能随机地将所有难以分类的样例都放在测试集中,因此得到一个不切实际的低分数。但如果使用交叉验证,每个样例都会刚好在测试集中出现一次:每个样例位于一个折中,而每个折都在测试集中出现一次。因此,模型需要对数据集中所有样本的泛化能力都很好,才能让所有的交叉验证得分(及其平均值)都很高

对数据进行多次划分,还可以提供我们的模型对训练集选择的敏感性信息。对于 iris 数据集,我们观察到精度在 90% 到 100% 之间。这是一个不小的范围,它告诉我们将模型应用于新数据时在最坏情况和最好情况下的可能表现。

折交叉验证时,在每次迭代中我们可以使用 4/5(80%)的数据来拟合模型。在使用 10 折交叉验证时,我们可以使用 9/10(90%)的数据来拟合模型。更多的数据通常可以得到更为精确的模型。交叉验证的主要缺点是增加了计算成本。现在我们要训练 k 个模型而不是单个模型,所以交叉验证的速度要比数据的单次划分大约慢 k 倍

重要的是要记住,交叉验证不是一种构建可应用于新数据的模型的方法。交
叉验证不会返回一个模型。在调用 cross_val_score 时,内部会构建多个模
型,但交叉验证的目的只是评估给定算法在特定数据集上训练后的泛化性能
好坏

分层k折交叉验证和其他策略

将数据集划分为 k 折时,从数据的前 k 分之一开始划分(正如上一节所述),这可能并不总是一个好主意。例如,我们来看一下 iris 数据集

from sklearn.datasets import load_iris
iris = load_iris()
print("Iris labels:\n{}".format(iris.target))

输出

Iris labels:
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]

如你所见,数据的前三分之一是类别 0,中间三分之一是类别 1,最后三分之一是类别 2。想象一下在这个数据集上进行 3 折交叉验证。第 1 折将只包含类别 0,所以在数据的第一次划分中,测试集将只包含类别 0,而训练集只包含类别 1 和 2。由于在 3 次划分中训练集和测试集中的类别都不相同,因此这个数据集上的 3 折交叉验证精度为 0。这没什么帮助,因为我们在 iris 上可以得到比 0% 好得多的精度。由于简单的 k 折策略在这里失效了,所以 scikit-learn 在分类问题中不使用这种策略,而是使用分层 k 折交叉验证(stratified k-fold cross-validation)。在分层交叉验证中,我们划分数据,使每个折中类别之间的比例与整个数据集中的比例相同

举个例子,如果 90% 的样本属于类别 A 而 10% 的样本属于类别 B,那么分层交叉验证可以确保,在每个折中 90% 的样本属于类别 A 而 10% 的样本属于类别 B。

使用分层 k 折交叉验证而不是 k 折交叉验证来评估一个分类器,这通常是一个好主意,因
为它可以对泛化性能做出更可靠的估计。在只有 10% 的样本属于类别 B 的情况下,如果
使用标准 k 折交叉验证,很可能某个折中只包含类别 A 的样本。利用这个折作为测试集的
话,无法给出分类器整体性能的信息

交叉验证的更多控制

可以利用 cv 参数来调节 cross_val_score 所使用的折数。但 scikit-learn允许提供一个交叉验证分离器(cross-validation splitter)作为 cv 参数,来对数据划分过程进行更精细的控制。对于大多数使用场景而言,回归问题默认的 k 折交叉验证与分类问题的分层 k 折交叉验证的表现都很好,但有些情况下你可能希望使用不同的策略。比如说,
我们想要在一个分类数据集上使用标准 k 折交叉验证来重现别人的结果。为了实现这一点,我们首先必须从 model_selection 模块中导入 KFold 分离器类,并用我们想要使用的折数来将其实例化

from sklearn.model_selection import KFold
kfold = KFold(n_splits=5)

然后我们可以将 kfold 分离器对象作为 cv 参数传入 cross_val_score

Cross-validation scores:
[1.         0.93333333 0.43333333 0.96666667 0.43333333]

通过这种方法,我们可以验证,在 iris 数据集上使用 3 折交叉验证(不分层)确实是一个非常糟糕的主意

这是因为在 iris 数据集中每个折对应一个类别,因此学不到任何内容。解决这个问题的另一种方法是将数据打乱来代替分层,以打乱样本按标签的排序。可以通过将 KFold 的shuffle 参数设为 True 来实现这一点。如果我们将数据打乱,那么还需要固定 random_state 以获得可重复的打乱结果。否则,每次运行 cross_val_score 将会得到不同的结果,因为每次使用的是不同的划分(这可能并不是一个问题,但可能会出人意料)。在划分数据之前将其打乱可以得到更好的结果

Kfold = Kfold(N_splits=3, Shuffle=True, Random_state=0)
Print("Cross-Validation Scores:\N{}".Format(
 Cross_val_score(Logreg, Iris.Data, Iris.Target, Cv=Kfold)))

输出为

Cross-validation scores:
[0.9  0.96 0.96]

留一法

另一种常用的交叉验证方法是留一法(leave-one-out)。你可以将留一法交叉验证看作是每折只包含单个样本的 k 折交叉验证。对于每次划分,你选择单个数据点作为测试集。这种方法可能非常耗时,特别是对于大型数据集来说,但在小型数据集上有时可以给出更好的估计结果:

from sklearn.model_selection import LeaveOneOut
loo = LeaveOneOut()
scores = cross_val_score(log,data.data,data.target,cv=loo)
print("mean accuracy :{:.2f}".format(scores.mean()))

输出为

mean accuracy 0.95

打乱划分交叉验证

另一种非常灵活的交叉验证策略是打乱划分交叉验证(shuffle-split cross-validation)。在打乱划分交叉验证中,每次划分为训练集取样 train_size 个点,为测试集取样 test_size 个(不相交的)点。将这一划分方法重复 n_iter 次。图 5-3 显示的是对包含 10 个点的数据集运行 4 次迭代划分,每次的训练集包含 5 个点,测试集包含 2 个点(你可以将 train_size和 test_size 设为整数来表示这两个集合的绝对大小,也可以设为浮点数来表示占整个数据集的比例):

分组交叉验证

另一种非常常见的交叉验证适用于数据中的分组高度相关时。比如你想构建一个从人脸图片中识别情感的系统,并且收集了 100 个人的照片的数据集,其中每个人都进行了多次拍摄,分别展示了不同的情感。我们的目标是构建一个分类器,能够正确识别未包含在数据集中的人的情感。你可以使用默认的分层交叉验证来度量分类器的性能。但是这样的话,同一个人的照片可能会同时出现在训练集和测试集中。对于分类器而言,检测训练集中出现过的人脸情感比全新的人脸要容易得多。因此,为了准确评估模型对新的人脸的泛化能力,我们必须确保训练集和测试集中包含不同人的图像。为了实现这一点,我们可以使用 GroupKFold,它以 groups 数组作为参数,可以用来说明照片中对应的是哪个人。这里的 groups 数组表示数据中的分组,在创建训练集和测试集的时候不应该将其分开,也不应该与类别标签弄混数据分组的这种例子常见于医疗应用,你可能拥有来自同一名病人的多个样本,但想要将其泛化到新的病人。同样,在语音识别领域,你的数据集中可能包含同一名发言人的多条记录,但你希望能够识别新的发言人的讲话。下面这个示例用到了一个由 groups 数组指定分组的模拟数据集。这个数据集包含 12 个数据点,且对于每个数据点groups 指定了该点所属的分组(想想病人的例子)。一共分成 4个组,前 3 个样本属于第一组,接下来的 4 个样本属于第二组,以此类推

from sklearn.model_selection import GroupKFold
# 创建模拟数据集
X, y = make_blobs(n_samples=12, random_state=0)
# 假设前3个样本属于同一组,接下来的4个属于同一组,以此类推
groups = [0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3]
scores = cross_val_score(logreg, X, y, groups, cv=GroupKFold(n_splits=3))
print("Cross-validation scores:\n{}".format(scores))

输出为

Cross-validation scores:
[0.75       0.8        0.66666667]

样本不需要按分组进行排序,我们这么做只是为了便于说明。基于这些标签计算得到的划分如图 所示。如你所见,对于每次划分,每个分组都是整体出现在训练集或测试集中

上一篇下一篇

猜你喜欢

热点阅读