使用sklearn实现交叉验证
1.交叉验证
如果我们使用模型在一个数据集上面进行测试是不对的,因为只要我们的模型不断进行训练样本,最终肯定会拟合所有的数据,但是这样来说该模型会对在训练过程中没有见过的数据失去作用,这种情况就是过拟合。
我们为了要避免这种情况,就要在训练开始将数据分为训练集和测试集x_train,x_test,一般有的还分成验证集x_val
训练集:用于去训练模型参数
验证集:用于验证模型每次更新参数是否正确,帮忙选择模型在训练集上最优参数
测试集:用于检验最终训练好的模型的泛化效果
scikit-learn非常友好已经帮我们实现好了分割方法(train_test_split),我们现在测试一下采用鸢尾花数据集并简单的用树模型进行示例:
from sklearn.model_selection import train_test_split from sklearn import datasets from sklearn.tree import DecisionTreeClassifier X,y=datasets.load_iris(return_X_y=True) print(X.shape,y.shape) # (150,4) (150,) # 测试集数据占40%,随机数种子为2021 X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.4,random_state=2021) print(X_train.shape,y_train.shape) # (90,4) (90) print(X_test.shape,y_test.shape) # (60,4) (60) clf=DecisionTreeClassifier().fit(X_train,y_train) print(clf.score(X_train,y_train)) print(clf.score(X_test,y_test)) >1.0 >0.9666666666666667
我们的目标就是不断调整参数使之模型在训练集上的表现非常好,但是这样就会导致模型过拟合的风险,此时我们可以采用将数据分成训练集、验证集、测试集,用验证集帮助模型调整参数,判断模型的好坏,最终用测试集判断模型的泛化能力。
但是这样也会存在问题,如果我们的数据集很大好说,如果数据量很少的话,会丢失很多数据。
所以产生了一种方法叫做交叉验证(cross-validation)简称(CV)。交叉验证不再需要验证集。
常用的交叉验证有KFlods,它的原理就是将所有的数据集分成训练集和测试集,其中训练集会被分成k份,我们把其中的k-1份用作训练集去训练模型,用剩余的一份来进行验证模型(也就是说用剩下的一份充当验证集)。
下面我们用一段简单的代码来演示一下:
from sklearn.model_selection import cross_val_score clf=DecisionTreeClassifier() scores=cross_val_score(clf,X,y,cv=5) print(scores) print(scores.mean()) [0.96666667 0.96666667 0.9 0.93333333 1. ] out:0.9533333333333334
上述代码的意思就是将数据集分成5份,那么我们就会产生5个数据集,分别进行训练,然后在同一的测试集上进行测试获取训练分数。
还有一点需要指名,我们在进行交叉验证时,默认使用的是模型默认的评估标准,我们可以手动传参进行指定评估指标。
scores=cross_val_score(clf,X,y,scoring='accuracy') # scoring:各种评估标准,有兴趣可以参考sklearn.metrics里面含有各种评估指标
交叉验证还有一种传参方式就是不传入cv=5,而是传入一个迭代器,代码演示一下:
from sklearn.model_selection import ShuffleSplit cv=ShuffleSplit(n_splits=5,test_size=0.2,random_state=2021) cross_val_score(clf,X,y,cv=cv)
2.K-fold交叉验证
所谓的K-fold交叉验证就是将我们所有的样本分成k组,每一份就是一个folds,这里如果我们的k=样本数,这时我们的方法就变成了Leave One Out,我们会用其中k-1个folds进行学习,用剩余的一个进行测试。
我们用代码演示一下:
import numpy as np from sklearn.model_selection import KFold X=['9','8','7','6','5'] kf=KFold(n_splits=2) for train_index,test_index in kf.split(X): print(train_index,test_index)
输出结果:
[3 4] [0 1 2] [0 1 2] [3 4]
其实kf.split(X)返回的是训练集和测试集的索引,我特意将数据集变成的字符测试,n_splits就是将数据分成几折,你会发现每个训练集和测试集是不重合的,这也就对应了k-1折和剩余的1折。下面我们会说到ShuffleSplit它是会进行重复采样的。
3.留一法交叉验证(Leave One Out)
Loo是最简单的交叉验证,它的原理就是将我们数据集分成n份(对应n个样本),我们将n-1个样本进行训练,另外一个用于测试,这时我们就会产生n个数据集,该方法的优点就是不会浪费数据,训练集和原数据集能够最大程度还原。但是该方法可能会产生高方差,为什么呢?我们有n个数据集,用n-1份训练,剩余的1个用于测试,这是就会产生n种结果,而且用1个进行测试,很有可能出现该测试数据是异常数据,会导致结果方差很大,而且消耗计算资源较大。
我查阅里百度,现在不推荐使用留一法,尝试用5折或10折交叉验证进行代替。
但是我也用代码简单演示一下:
from sklearn.model_selection import LeaveOneOut X=['9','8','7','6','5'] loo=LeaveOneOut() for train_index,test_index in loo.split(X): print(train_index,test_index)
输出结果:
[1 2 3 4] [0] [0 2 3 4] [1] [0 1 3 4] [2] [0 1 2 4] [3] [0 1 2 3] [4]
4.留P法交叉验证
该方法和K-fold以及留一法类似就不说了
from sklearn.model_selection import LeavePOut X=['9','8','7','6','5'] loo=LeavePOut(p=3) for train_index,test_index in loo.split(X): print(train_index,test_index)
输出结果:
[3 4] [0 1 2] [2 4] [0 1 3] [2 3] [0 1 4] [1 4] [0 2 3] [1 3] [0 2 4] [1 2] [0 3 4] [0 4] [1 2 3] [0 3] [1 2 4] [0 2] [1 3 4] [0 1] [2 3 4]
5.随机排列交叉验证(ShuffleSplit)
所谓ShuffleSplit就是将数据集进行洗牌随机打乱,然后根据随机数来生成训练集和测试集,注意它和K-fold不一样,该方式又可能重复,不会像K折那样按折来区分。
下面用代码进行演示:
from sklearn.model_selection import ShuffleSplit X=['9','8','7','6','5'] ss=ShuffleSplit(n_splits=5,test_size=0.2,random_state=2021) for train_index,test_index in ss.split(X): print(train_index,test_index)
输出结果:
[0 3 1 4] [2] [1 2 3 4] [0] [3 2 4 0] [1] [0 3 4 1] [2] [3 4 0 1] [2]
从该输出结果我们确实可以看到确实产生了重复的数据集。
6.时间序列分割(Time Series Split)
上述的分割算法我们都是假设原数据是独立同分布的,而我们的时序数据不满足该假设,比如股票价格,显然明天的股票价格和今天以及昨天或者更久的时间有关,每条数据之间都存在着联系,所以我们不能随意切分数据集。
我们需要的训练数据应为连续的,就是说数据应为日期连续的数据,而测试数据也应该为训练数据紧随其后的。
我们用代码实现一下:
from sklearn.model_selection import TimeSeriesSplit X=['9','8','7','6','5'] tss=TimeSeriesSplit(n_splits=3) for train_index,test_index in tss.split(X): print(train_index,test_index)
输出结果:
[0 1] [2] [0 1 2] [3] [0 1 2 3] [4]
我们可以观察输出结果,训练集的索引都是连续的,而且测试数据集的数据是紧挨着训练数据的。
7.数据集的归一化
就是我们可能看到过一些数据处理就是将训练集和测试机进行标准化处理,当用scikit实现时你会发现创建scaler实类时是用的是训练集,训练集和测试集进行transform时都是用的该实类,有人会问,为什么不创建两个实类分别处理呢?这是我们将训练集转化后进行训练,模型会对其进行拟合,然后模型学习到的是该数据分布下的内容,所有进行测试数据时,我们也要将测试数据转化到相应的分布下, 对其进行相同的处理。
下面我用代码演示一下:
from sklearn.preprocessing import StandardScaler # 创建标准化实类 scaler=StandardScaler().fit(X_train) # 数据标准化 X_train_trans=scaler.transform(X_train) X_test_trans=scaler.transform(X_test) clf=DecisionTreeClassifier() clf.fit(X_train_trans,y_train) clf.score(X_test_trans,y_test) 0.9666666666666667 tandardScaler().fit(X_train) # 数据标准化 X_train_trans=scaler.transform(X_train) X_test_trans=scaler.transform(X_test) clf=DecisionTreeClassifier() clf.fit(X_train_trans,y_train) clf.score(X_test_trans,y_test) 0.9666666666666667