背景

一般来说在训练时会将数据分为两部分,一部分用于训练,另一部分用于验证。这会引起以下几点问题:

  • 首先数据的切割是人为/随机的,不同切割可能会导致模型的不同精度。或者说人为切割的验证集有可能偏离了真实分布
  • 其次训练数据被分为两部分,验证集不参与训练,网络的数据来源缩水了,影响训练效果。虽然最后会将所有训练数据都丢入模型中来评估测试集,但是在这之前评估模型或者说模型调参还是只能使用一部分的训练数据

基本概念

K是一个指定的数字。执行K折交叉验证时,首先会将数据划分成(大致)相等的K个部分,每部分叫做一个。每次选取其中一个折作为验证集,由其他K-1个折作为训练集训练模型,训练好的模型用验证集评估正确性。

换句话说,K折交叉验证将会产生K个模型,最后得到的精度将是这K个模型精度的均值

交叉验证一般用于评估模型的泛化性能,也可以使用带交叉验证的网格搜索确定超参数

image-20230328104210900

优缺点

  • K折交叉验证的精度结果反映了模型对数据集中的所有样本都有较好的泛化性
  • 通过对数据的多次划分,提供了模型对于训练集选择的敏感性信息。即反映了数据在最好/最坏情况下模型的表现情况。
  • 交叉验证使数据使用更高效,K越大时,训练集的占比可以更大。更多的数据自然可以得到更精确的模型。

sklearn

from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression

iris = load_iris()
logreg = LogisticRegression(max_iter=1000)

scores = cross_val_score(logreg, iris.data, iris.target)
print("Cross-validation scores: {}".format(scores))

分层K折交叉验证 .etc

分层K折交叉验证

分层K折交叉验证保证了每个折中类别的比例和整个数据集中的相同。比如该数据集中90%的样本是A类,10%的样本是B类。那么K折中的每一折都是符合A占90%,B占10%。

分层K折避免这种情况发生:某一折里只包含A类的样本,导致验证精度很差,无法给出分类器的整体性能

随机K折交叉验证

为了避免标准K折交叉验证中折内类别比例异常的情况,还可以使用随机K折交叉验证。

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))

留一法交叉验证

留一法可以看作是每折只有单个样本的K折交叉验证.

from sklearn.model_selection import LeaveOneOut
loo = LeaveOneOut()
scores = cross_val_score(logreg, iris.data, iris.target, cv=loo)
print("Number of cv iterations: ", len(scores))
print("Mean accuracy: {:.2f}".format(scores.mean()))

打乱划分交叉验证

每次划分为训练集采样train_size个点,为测试集(或者说验证集)采样test_size个点.将这种方法重复n_iter次.

image-20230328110830120

from sklearn.model_selection import ShuffleSplit
shuffle_split = ShuffleSplit(test_size=.5, train_size=.5, n_splits=10)
scores = cross_val_score(logreg, iris.data, iris.target, cv=shuffle_split)
print("Cross-validation scores:\n{}".format(scores))

打乱划分交叉验证允许在训练集和测试集大小之外指定迭代次数.也可以允许每次迭代仅使用部分数据.对于二次采样大型数据的试验比较有效.

分组交叉验证

有时候数据是分组的,比如每个人有多个数据.那么测试集中的数据最好是整组出现的,不应该出现测试集中某个人的数据出现在训练集中,这会影响对模型的泛化能力的评估.

image-20230328111504785

from sklearn.model_selection import GroupKFold
# create synthetic dataset
X, y = make_blobs(n_samples=12, random_state=0)
# assume the first three samples belong to the same group,
# then the next four, etc.
groups = [0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3]
scores = cross_val_score(logreg, X, y, groups=groups, cv=GroupKFold(n_splits=3))
print("Cross-validation scores:\n{}".format(scores))

基本概念

K折交叉验证用于评估一个模型的泛化性能,那么网格搜索通过调参来提高模型的泛化性能.

简单的网格搜索实现

提前划分好训练集和测试集,简单通过两个for循环寻找最佳参数,评估效果直接使用模型自带的score.

# naive grid search implementation
from sklearn.svm import SVC
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, random_state=0)
print("Size of training set: {}   size of test set: {}".format(
      X_train.shape[0], X_test.shape[0]))

best_score = 0

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        # for each combination of parameters, train an SVC
        svm = SVC(gamma=gamma, C=C)
        svm.fit(X_train, y_train)
        # evaluate the SVC on the test set
        score = svm.score(X_test, y_test)
        # if we got a better score, store the score and parameters
        if score > best_score:
            best_score = score
            best_parameters = {'C': C, 'gamma': gamma}

print("Best score: {:.2f}".format(best_score))
print("Best parameters: {}".format(best_parameters))

验证集的引进

上节实现的简单网格搜索存在一个致命问题:将用来调整参数的测试集用于评估模型的好坏!

换句话说,我们是在测试集上选择出来的最佳参数,整个参数不能保证对于新数据也work well.所以需要引入一组新数据.那么数据总共就被分为三份:

  • 用于训练模型的训练集
  • 用于调整超参数的验证集
  • 用于评估模型的测试集

测试集模型训练阶段从未见过,只在评估时使用.

验证集用来衡量训练集训练出来的模型好坏

最后评估时,模型会选择表现最好的超参,用训练集+验证集重新训练,再用测试集进行评估.这一步主要是充分利用验证集数据.

from sklearn.svm import SVC
# split data into train+validation set and test set
X_trainval, X_test, y_trainval, y_test = train_test_split(
    iris.data, iris.target, random_state=0)
# split train+validation set into training and validation sets
X_train, X_valid, y_train, y_valid = train_test_split(
    X_trainval, y_trainval, random_state=1)
print("Size of training set: {}   size of validation set: {}   size of test set:"
      " {}\n".format(X_train.shape[0], X_valid.shape[0], X_test.shape[0]))

best_score = 0

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        # for each combination of parameters, train an SVC
        svm = SVC(gamma=gamma, C=C)
        svm.fit(X_train, y_train)
        # evaluate the SVC on the validation set
        score = svm.score(X_valid, y_valid)
        # if we got a better score, store the score and parameters
        if score > best_score:
            best_score = score
            best_parameters = {'C': C, 'gamma': gamma}

# rebuild a model on the combined training and validation set,
# and evaluate it on the test set
svm = SVC(**best_parameters)
svm.fit(X_trainval, y_trainval)
test_score = svm.score(X_test, y_test)
print("Best score on validation set: {:.2f}".format(best_score))
print("Best parameters: ", best_parameters)
print("Test set score with best parameters: {:.2f}".format(test_score))

带交叉验证的网格搜索

上一节中人为分出了测试集\训练集\验证集,这钟方法对于数据的划分也比较敏感.因此可以在划分训练集/验证集时使用交叉验证,更好的评估模型的泛化能力.

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        # for each combination of parameters,
        # train an SVC
        svm = SVC(gamma=gamma, C=C)
        # perform cross-validation
        scores = cross_val_score(svm, X_trainval, y_trainval, cv=5)
        # compute mean cross-validation accuracy
        score = np.mean(scores)
        # if we got a better score, store the score and parameters
        if score > best_score:
            best_score = score
            best_parameters = {'C': C, 'gamma': gamma}
# rebuild a model on the combined training and validation set
svm = SVC(**best_parameters)
svm.fit(X_trainval, y_trainval)

image-20230328113803191

GridSearchCV 热图可视化

from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100],
              'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
grid_search = GridSearchCV(SVC(), param_grid, cv=5,
                          return_train_score=True)
                          
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, random_state=0)
grid_search.fit(X_train, y_train)

scores = np.array(results.mean_test_score).reshape(6, 6)

# plot the mean cross-validation scores
mglearn.tools.heatmap(scores, xlabel='gamma', xticklabels=param_grid['gamma'],
                      ylabel='C', yticklabels=param_grid['C'], cmap="viridis")

image-20230328114043350

不同的交叉验证策略

嵌套交叉验证

在交叉验证的网格搜索中,我们使用交叉验证来完成训练集/验证集的划分,测试集的划分我们还是手动的.嵌套交叉验证让我们测试集的划分也使用交叉验证.这会使结果更合理,当然需要训练模型的个数也upup~(比如36个模型超参数组合,嵌套交叉验证都是5折,那就需要训练900个模型)

param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100],
              'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
scores = cross_val_score(GridSearchCV(SVC(), param_grid, cv=5),
                         iris.data, iris.target, cv=5)
print("Cross-validation scores: ", scores)
print("Mean cross-validation score: ", scores.mean())