十三、交叉验证和得分方法

优质
小牛编辑
133浏览
2023-12-01

在前面的章节和笔记本中,我们将数据集分为两部分:训练集和测试集。 我们使用训练集来拟合我们的模型,并且我们使用测试集来评估其泛化能力 - 它对新的,没见过的数据的表现情况。

然而,(标记的)数据通常是宝贵的,这种方法让我们只将约 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_scoreStratifiedKFold用于分类,这可确保数据集中的类比例反映在每个折叠中。 如果你有一个二分类数据集,其中 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