十三、交叉验证和得分方法
在前面的章节和笔记本中,我们将数据集分为两部分:训练集和测试集。 我们使用训练集来拟合我们的模型,并且我们使用测试集来评估其泛化能力 - 它对新的,没见过的数据的表现情况。
然而,(标记的)数据通常是宝贵的,这种方法让我们只将约 3/4 的数据用于行训练。 另一方面,我们只会尝试将我们的 1/4 数据应用于测试。 使用更多数据来构建模型,并且获得更加鲁棒的泛化能力估计,常用方法是交叉验证。 在交叉验证中,数据被重复拆分为非重叠的训练和测试集,并为每对建立单独的模型。 然后聚合测试集的得分来获得更鲁棒的估计。
进行交叉验证的最常用方法是k
折交叉验证,其中数据首先被分成k
(通常是 5 或 10)个相等大小的折叠,然后对于每次迭代,使用k
折中的一个作为测试数据,其余作为训练数据:
这样,每个数据点只在测试集中一次,我们可以使用第k
个数据之外的所有数据进行训练。 让我们应用这种技术,在鸢尾花数据集上评估KNeighborsClassifier
算法:
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
iris = load_iris()
X, y = iris.data, iris.target
classifier = KNeighborsClassifier()
鸢尾花中的标签是有序的,这意味着如果我们像上面那样拆分数据,第一个折叠只有标签 0,而最后一个只有标签 2:
y
为了在评估中避免这个问题,我们首先将我们的数据打乱:
import numpy as np
rng = np.random.RandomState(0)
permutation = rng.permutation(len(X))
X, y = X[permutation], y[permutation]
print(y)
现在实现交叉验证很简单:
k = 5
n_samples = len(X)
fold_size = n_samples // k
scores = []
masks = []
for fold in range(k):
# 为此折叠中的测试集生成一个布尔掩码
test_mask = np.zeros(n_samples, dtype=bool)
test_mask[fold * fold_size : (fold + 1) * fold_size] = True
# 为可视化存储掩码
masks.append(test_mask)
# 使用此掩码创建训练和测试集
X_test, y_test = X[test_mask], y[test_mask]
X_train, y_train = X[~test_mask], y[~test_mask]
# 拟合分类器
classifier.fit(X_train, y_train)
# 计算得分并记录
scores.append(classifier.score(X_test, y_test))
让我们检查一下我们的测试掩码是否正确:
import matplotlib.pyplot as plt
%matplotlib inline
plt.matshow(masks, cmap='gray_r')
现在让我们看一下我们计算出的得分:
print(scores)
print(np.mean(scores))
正如你所看到的,得分广泛分布于 90% 正确到 100% 正确。 如果我们只进行一次分割,我们可能会得到任何答案。
由于交叉验证是机器学习中常见的模式,有个函数执行上面的操作,带有更多灵活性和更少代码。sklearn.model_selection
模块具有交叉验证相关的所有函数。 最简单的函数是cross_val_score
,它接受估计器和数据集,并将为你完成所有拆分:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(classifier, X, y)
print('Scores on each CV fold: %s' % scores)
print('Mean score: %0.3f' % np.mean(scores))
如你所见,该函数默认使用三个折叠。 你可以使用cv
参数更改折叠数:
cross_val_score(classifier, X, y, cv=5)
交叉验证模块中还有辅助对象,它们将为你生成各种不同交叉验证方法的索引,包括 k-fold:
from sklearn.model_selection import KFold, StratifiedKFold, ShuffleSplit
默认情况下,cross_val_score
将StratifiedKFold
用于分类,这可确保数据集中的类比例反映在每个折叠中。 如果你有一个二分类数据集,其中 90% 的数据点属于类 0,那么这意味着在每个折叠中,90% 的数据点将属于类 0。如果你只是使用KFold
交叉验证,你可能会生成一个只包含类 0 的分割。每当你进行分类时,通常最好使用StratifiedKFold
。
StratifiedKFold
也消除了我们打乱鸢尾花的需要。 让我们看看在未打乱的鸢尾花数据集上,它生成什么类型的折叠。 每个交叉验证类都是训练和测试索引的集合的生成器:
cv = StratifiedKFold(n_splits=5)
for train, test in cv.split(iris.data, iris.target):
print(test)
正如你所看到的,在每个折叠中,在开始,中间,和结束位置,都有一些样本。 这样,保留了类别比例。 让我们观察一下split
:
def plot_cv(cv, features, labels):
masks = []
for train, test in cv.split(features, labels):
mask = np.zeros(len(labels), dtype=bool)
mask[test] = 1
masks.append(mask)
plt.matshow(masks, cmap='gray_r')
plot_cv(StratifiedKFold(n_splits=5), iris.data, iris.target)
为了比较,仍旧是标准KFold
,忽略标签:
plot_cv(KFold(n_splits=5), iris.data, iris.target)
请记住,增加折叠数量会为你提供更大的训练数据集,但会导致更多重复,因此评估速度会变慢:
plot_cv(KFold(n_splits=10), iris.data, iris.target)
另一个有用的交叉验证生成器是ShuffleSplit
。 该生成器简单地重复分割数据的随机部分。 这允许用户独立指定重复次数和训练集大小:
plot_cv(ShuffleSplit(n_splits=5, test_size=.2), iris.data, iris.target)
如果你想要更鲁棒的估计,你可以增加分割数量:
plot_cv(ShuffleSplit(n_splits=20, test_size=.2), iris.data, iris.target)
你可以使用cross_val_score
方法来使用所有这些交叉验证生成器:
cv = ShuffleSplit(n_splits=5, test_size=.2)
cross_val_score(classifier, X, y, cv=cv)
练习
在鸢尾花数据集上,使用
KFold
类进行三折交叉验证,而不打乱数据。你能解释一下结果吗?
# %load solutions/13_cross_validation.py