一、交叉验证介绍
交叉验证是在机器学习建立模型和验证模型参数时常用的办法。交叉验证,顾名思义,就是重复的使用数据,把得到的样本数据进行切分,组合为不同的训练集和测试集,用训练集来训练模型,用测试集来评估模型预测的好坏。在此基础上可以得到多组不同的训练集和测试集,某次训练集中的某样本在下次可能成为测试集中的样本,即所谓“交叉”。
那么什么时候才需要交叉验证呢?交叉验证用在数据不是很充足的时候。比如在我日常项目里面,对于普通适中问题,如果数据样本量小于一万条,我们就会采用交叉验证来训练优化选择模型。如果样本大于一万条的话,我们一般随机的把数据分成三份,一份为训练集(Training Set),一份为验证集(Validation Set),最后一份为测试集(Test Set)。用训练集来训练模型,用验证集来评估模型预测的好坏和选择模型及其对应的参数。把最终得到的模型再用于测试集,最终决定使用哪个模型以及对应参数。
1.1 简单交叉验证 train_test_split
第一种是简单交叉验证,所谓的简单,是和其他交叉验证方法相对而言的。首先,我们随机的将样本数据分为两部分(比如: 70%的训练集,30%的测试集),然后用训练集来训练模型,在测试集上验证模型及参数。接着,我们再把样本打乱,重新选择训练集和测试集,继续训练数据和检验模型。最后我们选择损失函数评估最优的模型和参数。
- 原始数据分成训练集、验证集和测试集,并且保持数据分布的一致性,可以使用shuffle
- 好处:处理简单,只需随机把原始数据分为两组即可
- 缺点:只进行了一次划分,数据结果具有偶然性,没有达到交叉的思想,由于是随机的将原始数据分组,所以最后验证集分类准确率的高低与原始数据的分组有很大的关系,得到的结果并不具有说服性。
from sklearn.model_selection import train_test_split
'''
(1)random_state不填或者为0时,每次都不同;其余值表示不同随机数
(2)shuffle表示是否在分割之前对数据进行洗牌(默认True)
'''
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.30,random_state=42,shuffle=True)
1.2 K折交叉验证 K-Folder Cross Validation
第二种是K折交叉验证(K-Folder Cross Validation)。和第一种方法不同,K折交叉验证会把样本数据随机的分成K份(一般是均分),每次随机的选择K-1份作为训练集,剩下的1份做测试集。当这一轮完成后,重新随机选择K-1份来训练数据。若干轮(小于K)之后,选择损失函数评估最优的模型和参数。
将每个子集数据分别做一次验证集,其余的K-1组子集数据作为训练集,这样会得到K个模型,用这K个模型最终的验证集的分类准确率的平均数作为此K-CV下分类器的性能指标。K一般大于等于2,实际操作时一般从3开始取,只有在原始数据集合数据量小的时候才会尝试取2。
应用最多,K-CV可以有效的避免过拟合与欠拟合的发生,最后得到的结果也比较具有说服性。
1.2.1 k-折交叉验证步骤
- 第一步,不重复抽样将原始数据随机分为 k 份。
- 第二步,每一次挑选其中 1 份作为测试集,剩余 k-1 份作为训练集用于模型训练。
- 第三步,重复第二步 k 次,这样每个子集都有一次机会作为测试集,其余机会作为训练集。
- 在每个训练集上训练后得到一个模型,
- 用这个模型在相应的测试集上测试,计算并保存模型的评估指标,
- 第四步,计算 k 组测试结果的平均值作为模型精度的估计,并作为当前 k 折交叉验证下模型的性能指标。
例如:
十折交叉验证
-
将数据集分成十份,轮流将其中9份作为训练数据,1份作为测试数据,进行试验。每次试验都会得出相应的正确率。
-
10次的结果的正确率的平均值作为对算法精度的估计,一般还需要进行多次10折交叉验证(例如10次10折交叉验证),再求其均值,作为对算法准确性的估计
-
模型训练过程的所有步骤,包括模型选择,特征选择等都是在单个折叠 fold 中独立执行的。
此外: -
多次 k 折交叉验证再求均值,例如:10 次10 折交叉验证,以求更精确一点。
-
数据量大时,k设置小一些 / 数据量小时,k设置大一些。
优点:降低由一次随机划分带来的偶然性,提高其泛化能力,提高对数据的使用效率。
缺点:可能存在一种情况:数据集有5类,抽取出来的也正好是按照类别划分的5类,也就是说第一折全是0类,第二折全是1类,等等;这样的结果就会导致,模型训练时。没有学习到测试集中数据的特点,从而导致模型得分很低,甚至为0,
from sklearn.model_selection import KFold
kf = KFold(n_splits=2)
for train_index, test_index in kf.split(X):
print('X_train:%s ' % X[train_index])
print('X_test: %s ' % X[test_index])
1.3 留一交叉验证 Leave-one-out Cross Validation
第三种是留一交叉验证(Leave-one-out Cross Validation)LOO-CV,在数据缺乏的情况下使用,如果设原始数据有N个样本,那么LOO-CV就是N-CV,即每个样本单独作为验证集,其余的N-1个样本作为训练集,故LOO-CV会得到N个模型,用这N个模型最终的验证集的分类准确率的平均数作为此下LOO-CV分类器的性能指标。
通过反复的交叉验证,用损失函数来度量得到的模型的好坏,最终我们可以得到一个较好的模型。那这三种情况,到底我们应该选择哪一种方法呢?一句话总结,如果我们只是对数据做一个初步的模型建立,不是要做深入分析的话,简单交叉验证就可以了。否则就用S折交叉验证。在样本量少的时候,使用S折交叉验证的特例留一交叉验证。
- 即K = 样本数
- 优点:不存在数据分布不一致,每一回合中几乎所有的样本皆用于训练模型,因此最接近原始样本的分布,这样评估所得的结果比较可靠。实验过程中没有随机因素会影响实验数据,确保实验过程是可以被复制的。
- 缺点:耗时,计算成本高,需要建立的模型数量与原始数据样本数量相同。当数据集较大时几乎不能使用。
from sklearn.model_selection import LeaveOneOut
loo = LeaveOneOut()
for train_index, test_index in loo.split(X):
print('X_train:%s ' % X[train_index])
print('X_test: %s ' % X[test_index])
1.4 自助法 Bootstrapping
此外还有一种比较特殊的交叉验证方式,也是用于样本量少的时候。叫做自助法(bootstrapping)。比如我们有m个样本(m较小),每次在这m个样本中随机采集一个样本,放入训练集,采样完后把样本放回。这样重复采集m次,我们得到m个样本组成的训练集。当然,这m个样本中很有可能有重复的样本数据。同时,用没有被采样到的样本做测试集。这样接着进行交叉验证。由于我们的训练集有重复数据,这会改变数据的分布,因而训练结果会有估计偏差,因此,此种方法不是很常用,除非数据量真的很少,比如小于20个。
- 优点:
在数据集较小、难以划分时很有用
能从D中产生不同的S,对集成学习等方法有好处
- 缺点:
产生的S改变了D的分布,会引入估计偏差
import numpy as np
import pandas as pd
import random
data = pd.DataFrame(np.random.rand(10,4),columns=list('ABCD'))
data['y'] = [random.choice([0,1]) for i in range(10)]
train = data.sample(frac=1.0,replace=True) # 有放回随机采样
test = data.loc[data.index.difference(train.index)].copy() # 将未采样的样本作为测试集
1.5 分层交叉验证 StratifiedKFold
通过指定分组,对测试集进行无放回抽样。
对非平衡数据可以用分层采样,就是在每一份子集中都保持和原始数据集相同的类别比例。StratifiedKFold() 各个类别的比例大致和完整数据集中相同,若数据集有4个类别,比例是2:3:3:2,则划分后的样本比例约是2:3:3:2,StratifiedShuffleSplit() 划分中每个类的比例和完整数据集中的相同,若数据集有4个类别,比例是2:3:3:2,则划分后的样本比例也是2:3:3:2。
ShuffleSplit允许更精细地控制迭代数和训练集/检验集的样本比例。
# ==================分层K折交叉验证、分层随机交叉验证===================
# 如何解决样本不平衡问题
skf = StratifiedKFold(n_splits=3)
#各个类别的比例大致和完整数据集中相同,若数据集有4个类别,比例是2:3:3:2,则划分后的样本比例约是2:3:3:2
for train, test in skf.split(iris.data, iris.target):
print("分层K折划分:%s %s" % (train.shape, test.shape))
# break
'''
分层K折划分:(99,) (51,)
分层K折划分:(99,) (51,)
分层K折划分:(102,) (48,)
'''
skf = StratifiedShuffleSplit(n_splits=3)
# 划分中每个类的比例和完整数据集中的相同,若数据集有4个类别,比例是2:3:3:2,则划分后的样本比例也是2:3:3:2
for train, test in skf.split(iris.data, iris.target):
print("分层随机划分:%s %s" % (train.shape, test.shape))
# break