Skip to content

参考 https://zhuanlan.zhihu.com/p/38121870

本文所研究的主题

  • 使用 训练集、测试集分割(train/test split)进行模型评估的缺点
  • K 折交叉验证算法(K-fold cross-validation)如何克服这些缺点
  • K 折交叉验证算法(K-fold cross-validation)如何用于参数调优以及选择模型和特征
  • K 折交叉验证算法(K-fold cross-validation)可能的改进措施

1. 使用训练集、测试集分割进行模型评估所带来的缺点

将原始数据划分为训练、测试集,避免了为了追求高准确率而在训练集上产生过拟合,从而使模型在样本外的数据上预测准确率高。

py
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)
x_train, x_test, y_train, y_test = train_test_split(
    X, y, random_state=4)
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(x_train, y_train)
print(knn.score(x_test, y_test))

输出

py
0.9736842105263158

random_state 设置为 3,修改如下代码

x_train, x_test, y_train, y_test = train_test_split(
    X, y, random_state=3)

输出

py
0.9473684210526315

为了消除这一变化因素,我们可以创建一系列训练集/测试集,计算模型在每个测试集上的准确率,然后计算平均值。

这就是 K 折交叉验证算法的本质。

2. 如何实现

  1. 将原始数据集划分为相等的 K 部分
  2. 将第一部分作为测试集,其余作为训练集
  3. 训练模型,计算模型在测试集上的准确率
  4. 每次用不同的部分作为测试集,重复步骤 (2) 和 (3) K
  5. 将平均准确率作为最终的模型准确率

演示:5-折交叉验证

py
from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=False, random_state=None)

split_data = kf.split(range(25))

print('{:^10} {:^61} {}'.format(
    'Index', 'Training set', 'Testing set'))
for i, data in enumerate(split_data):
    print('{:^9} {} {:^25}'.format(
        i, str(data[0]), str(data[1])))
  • 原始数据包含 25 个样本
  • 5 折交叉验证运行了 5 次迭代
  • 每次迭代,每个样本只在训练集中或者测试集中
  • 每个样本只在测试集中出现一次

K 折交叉验证对样本外的数据由更高的准确率。

3. 如何用于参数调优以及选择模型和特征

设置 K = 5 时的验证精度:

py
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
import numpy as np

X, y = load_iris(return_X_y=True)
knn = KNeighborsClassifier(n_neighbors=5)
scores = cross_val_score(knn, X, y, cv=10, scoring='accuracy')
print(scores)

assert isinstance(scores, np.ndarray)
print('mean:', scores.mean())

设置 K = 10 时的验证精度:

py
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier

X, y = load_iris(return_X_y=True)

k_scores = [None] * 30
for k in range(1, 31):
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X, y, cv=10,
                             scoring='accuracy')
    assert isinstance(scores, np.ndarray)
    k_scores[k - 1] = scores.mean()

print(k_scores)

plt.plot(np.arange(1, 31), k_scores)
plt.xlabel('Value of KNN')
plt.ylabel('Cross-Validated Accuracy')
plt.show()

在 KNN 模型中,通常建议选择使得模型最简单的 K 值,越高的 K 会使模型复杂性越低,因此此例中选择 K = 20 作为最好的 KNN 模型。

4. 改进措施

  • 重复利用不同的随机分组数据进行交叉验证
  • 降低交叉验证单一方案的方差来提高样本外的预测准确率
  • 将原始数据中的一部分数据设置为 "hold-out set"(即留出集),在其余部分进行交叉验证的整个过程,但模型最终准确率为模型在 "hold-out set" 上的准确率,因为 "hold-out set" 相当于样本外的数据

什么是 "hold-out set"?参考 知乎:为什么要分出一个 hold-out

你从 100 个例子里硬生生抽出 20 个例子把他们晾在一边,完完全全不参与模型训练。这 20 个例子我们称之为 "hold-out set"。然后剩下 80 个例子,等分成 4 份训练一个模型,等模型训练好后,用这个 "hold-out set" 来证明给别人看:我的模型是可以很好地工作在没有参与模型训练的例子上的!