欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

交叉验证(Cross-Validation)

程序员文章站 2022-07-14 11:40:46
...

今天我们就来详细了解一下 "交叉验证" 这个概念!

首先来看一下,什么是交叉验证?

交叉验证,是我们在学习机器学习建模中经常能遇到的一种方法。顾名思义,它就是通过 "交叉" (将原始数据拆分成多种不同数据组合)的方式对模型对象进行 "验证" (精确度评估:损失函数,方差,偏差)的处理工具。

交叉验证(Cross-validation)主要用于建模应用中,例如PCR 、PLS 回归建模中。在给定的建模样本中,拿出大部分样本进行建模型,留小部分样本用刚建立的模型进行预报,并求这小部分样本的预报误差,记录它们的平方加和。
它的基本思想就是将原始数据(dataset)进行分组,一部分做为训练集(这里的训练集通常包含:训练集和验证集两部分)来训练模型,另一部分做为测试集来评价模型。

训练集 (Traning Set):用于训练模型;

验证集 (Validation Set):用于模型的参数选择配置;

测试集 (Test Set):用于评估模型的泛化能力。

交叉验证(Cross-Validation)

为什么要用交叉验证?

1. 减少过拟合现象,提高泛化能力。如果我们用全部的数据集作为训练数据进行建模,训练出来的模型很有可能对训练数据有较强的依赖性,即对训练数据的结果准确度很高,而对外来其他数据准确度较差。

2. 提高数据的使用价值。从有限数据中获取尽可能多的有效信息。将数据进行不同程度的拆分再组合,可以从不同较度发掘数据中隐含的信息或价值。

常用的验证方法有哪些?

根据数据的切分方式,交叉验证可以简单的分为以下3种:

1. 简单交叉验证或留出法(Holdout Validation)

随机的把数据分为训练集,验证集,测试集三类,通过3个数据集建模,验证和测试。然后,再打乱数据重复这个过程。最后选择合适的损失函数评估最优模型和参数。

缺点:随机产生的3个数据集可能跟原始数据集的分布不同。

2. S-折交叉验证(S-folder Cross Validation)

S-折会把数据丝巾分成大小相同的S份,每次随机的选择一份作为测试集,剩下的S-1份作为训练集。重复若干轮之后,选择损失函数评估最优的模型和参数。

S一般取10,数据量小的时候,S可以大一些;数据量大的时候,S就小一些。

交叉验证(Cross-Validation)

3. 留一交叉验证(Leave-one Cross Validation)

这是S-折交叉验证的特例,满足 S=N(样本数量),即每一份只包含一个数据。每次选取N-1个样本进行训练,留1个样本来预测。适用于样本量特别少的情况。对于一般问题,当N小于50时,一般就采用留一交叉验证,其实实际应用中出场率并不高。

4.Bootstrapping 

这是一种特殊的交叉验证方法:通过自助采样的方式,即从m个样本中,每次随机挑选一个样本,再放回到数据集中,有放回的进行抽样m次,组成新的数据集作为训练集。很明显,这个新数据集会存在重复的样本,也有可能存在一次都没有出现的样本,原数据集中大概有36.8%的样本不会出现在新数据集中。

优点:训练集中的样本总数和原始数据集大小相同都是m,并且有近1/3的数据不会出现在新数据集中,一定程度避免了过拟合。

缺点:新数据集的数据分布可能和原始数据集不同,会引入偏差。

此方法不是很常用,除非数据量真的特别少。慎用!

几种方法应用举例

1.留出法

下面例子,一共有 150 条数据:

>>> import numpy as np
>>> from sklearn.model_selection import train_test_split
>>> from sklearn import datasets
>>> from sklearn import svm

>>> iris = datasets.load_iris()
>>> iris.data.shape, iris.target.shape
结果: ((150, 4), (150,))

# 用 train_test_split 来随机划分数据集,其中 40% 用于测试集,有 60 条数据,60% 为训练集,有 90 条数据:

>>> X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.4, random_state=0)

>>> X_train.shape, y_train.shape
((90, 4), (90,))
>>> X_test.shape, y_test.shape
((60, 4), (60,))

# 用 train 来训练,用 test 来评价模型的分数。
>>> clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train)
>>> clf.score(X_test, y_test)                           
结果: 0.96...

2.S-折交叉验证

# 最简单的方法是直接调用 cross_val_score,这里用了 5 折交叉验证:
>>> from sklearn.model_selection import cross_val_score

>>> clf = svm.SVC(kernel='linear', C=1)
>>> scores = cross_val_score(clf, iris.data, iris.target, cv=5)
>>> scores                                              
array([ 0.96...,  1.  ...,  0.96...,  0.96...,  1.        ])
得到最后平均分为 0.98,以及它的 95% 置信区间:

>>> print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))
Accuracy: 0.98 (+/- 0.03)
我们可以直接看一下 K-fold 是怎样划分数据的:
X 有四个数据,把它分成 2 折,
结果中最后一个集合是测试集,前面的是训练集,
每一行为 1 折:

>>> import numpy as np
>>> from sklearn.model_selection import KFold

>>> X = ["a", "b", "c", "d"]
>>> kf = KFold(n_splits=2)
>>> for train, test in kf.split(X):
...     print("%s %s" % (train, test))
[2 3] [0 1]
[0 1] [2 3]
同样的数据 X,我们看 LeaveOneOut 后是什么样子,
那就是把它分成 4 折,
结果中最后一个集合是测试集,只有一个元素,前面的是训练集,
每一行为 1 折:

>>> from sklearn.model_selection import LeaveOneOut

>>> X = [1, 2, 3, 4]
>>> loo = LeaveOneOut()
>>> for train, test in loo.split(X):
...     print("%s %s" % (train, test))
[1 2 3] [0]
[0 2 3] [1]
[0 1 3] [2]
[0 1 2] [3]

如何解决样本各类别数据量不平衡的问题?

使用 StratifiedKFold StratifiedShuffleSplit 分层抽样。 一些分类问题在目标类别的分布上可能表现出很大的不平衡性:例如,可能会出现比正样本多数倍的负样本。在这种情况下,建议采用如 StratifiedKFold 和 StratifiedShuffleSplit 中实现的分层抽样方法,确保相对的类别频率在每个训练和验证 折叠 中大致保留。

StratifiedKFold 是 S-folder 的变种,会返回 stratified(分层) 的折叠:每个小集合中, 各个类别的样例比例大致和完整数据集中相同。

StratifiedShuffleSplit 是 ShuffleSplit 的一个变种,会返回直接的划分,比如: 创建一个划分,但是划分中每个类的比例和完整数据集中的相同。
 

from sklearn.model_selection import train_test_split,cross_val_score,cross_validate # 交叉验证所需的函数
from sklearn.model_selection import KFold,LeaveOneOut,LeavePOut,ShuffleSplit # 交叉验证所需的子集划分方法
from sklearn.model_selection import StratifiedKFold,StratifiedShuffleSplit # 分层分割
from sklearn.model_selection import GroupKFold,LeaveOneGroupOut,LeavePGroupsOut,GroupShuffleSplit # 分组分割
from sklearn.model_selection import TimeSeriesSplit # 时间序列分割
from sklearn import datasets  # 自带数据集
from sklearn import svm  # SVM算法
from sklearn import preprocessing  # 预处理模块
from sklearn.metrics import recall_score  # 模型度量

iris = datasets.load_iris()  # 加载数据集
print('样本集大小:',iris.data.shape,iris.target.shape)

# ===================================数据集划分,训练模型==========================
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.4, random_state=0)  # 交叉验证划分训练集和测试集.test_size为测试集所占的比例
print('训练集大小:',X_train.shape,y_train.shape)  # 训练集样本大小
print('测试集大小:',X_test.shape,y_test.shape)  # 测试集样本大小
clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train) # 使用训练集训练模型
print('准确率:',clf.score(X_test, y_test))  # 计算测试集的度量值(准确率)


#  如果涉及到归一化,则在测试集上也要使用训练集模型提取的归一化函数。
scaler = preprocessing.StandardScaler().fit(X_train)  # 通过训练集获得归一化函数模型。(也就是先减几,再除以几的函数)。在训练集和测试集上都使用这个归一化函数
X_train_transformed = scaler.transform(X_train)
clf = svm.SVC(kernel='linear', C=1).fit(X_train_transformed, y_train) # 使用训练集训练模型
X_test_transformed = scaler.transform(X_test)
print(clf.score(X_test_transformed, y_test))  # 计算测试集的度量值(准确度)

# ===================================直接调用交叉验证评估模型==========================
clf = svm.SVC(kernel='linear', C=1)
scores = cross_val_score(clf, iris.data, iris.target, cv=5)  #cv为迭代次数。
print(scores)  # 打印输出每次迭代的度量值(准确度)
print("Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))  # 获取置信区间。(也就是均值和方差)

# ===================================多种度量结果======================================
scoring = ['precision_macro', 'recall_macro'] # precision_macro为精度,recall_macro为召回率
scores = cross_validate(clf, iris.data, iris.target, scoring=scoring,cv=5, return_train_score=True)
sorted(scores.keys())
print('测试结果:',scores)  # scores类型为字典。包含训练得分,拟合次数, score-times (得分次数)


# ==================================K折交叉验证、留一交叉验证、留p交叉验证、随机排列交叉验证==========================================
# k折划分子集
kf = KFold(n_splits=2)
for train, test in kf.split(iris.data):
    print("k折划分:%s %s" % (train.shape, test.shape))
    break

# 留一划分子集
loo = LeaveOneOut()
for train, test in loo.split(iris.data):
    print("留一划分:%s %s" % (train.shape, test.shape))
    break

# 留p划分子集
lpo = LeavePOut(p=2)
for train, test in loo.split(iris.data):
    print("留p划分:%s %s" % (train.shape, test.shape))
    break

# 随机排列划分子集
ss = ShuffleSplit(n_splits=3, test_size=0.25,random_state=0)
for train_index, test_index in ss.split(iris.data):
    print("随机排列划分:%s %s" % (train.shape, test.shape))
    break

# ==================================分层K折交叉验证、分层随机交叉验证==========================================
skf = StratifiedKFold(n_splits=3)  #各个类别的比例大致和完整数据集中相同
for train, test in skf.split(iris.data, iris.target):
    print("分层K折划分:%s %s" % (train.shape, test.shape))
    break

skf = StratifiedShuffleSplit(n_splits=3)  # 划分中每个类的比例和完整数据集中的相同
for train, test in skf.split(iris.data, iris.target):
    print("分层随机划分:%s %s" % (train.shape, test.shape))
    break


# ==================================组 k-fold交叉验证、留一组交叉验证、留 P 组交叉验证、Group Shuffle Split==========================================
X = [0.1, 0.2, 2.2, 2.4, 2.3, 4.55, 5.8, 8.8, 9, 10]
y = ["a", "b", "b", "b", "c", "c", "c", "d", "d", "d"]
groups = [1, 1, 1, 2, 2, 2, 3, 3, 3, 3]

# k折分组
gkf = GroupKFold(n_splits=3)  # 训练集和测试集属于不同的组
for train, test in gkf.split(X, y, groups=groups):
    print("组 k-fold分割:%s %s" % (train, test))

# 留一分组
logo = LeaveOneGroupOut()
for train, test in logo.split(X, y, groups=groups):
    print("留一组分割:%s %s" % (train, test))

# 留p分组
lpgo = LeavePGroupsOut(n_groups=2)
for train, test in lpgo.split(X, y, groups=groups):
    print("留 P 组分割:%s %s" % (train, test))

# 随机分组
gss = GroupShuffleSplit(n_splits=4, test_size=0.5, random_state=0)
for train, test in gss.split(X, y, groups=groups):
    print("随机分割:%s %s" % (train, test))


# ==================================时间序列分割==========================================
tscv = TimeSeriesSplit(n_splits=3)
TimeSeriesSplit(max_train_size=None, n_splits=3)
for train, test in tscv.split(iris.data):
    print("时间序列分割:%s %s" % (train, test))

参考:

https://baike.baidu.com/item/%E4%BA%A4%E5%8F%89%E9%AA%8C%E8%AF%81/8543100

https://www.cnblogs.com/pinard/p/5992719.html

https://www.cnblogs.com/sddai/p/8379452.html

https://blog.csdn.net/luanpeng825485697/article/details/79836262