交叉验证(Cross-Validation)

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

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

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

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

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

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

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

为什么要用交叉验证?

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

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

常用的验证方法有哪些?

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

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

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

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

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

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

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

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

  • 30
    点赞
  • 251
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值