一、单一验证
1、单一训练集和测试集
最简单的样本集划分就是只有训练集和测试集,而没有验证集,因此无法利用验证集反过来对模型参数进行调整。
只能先给定一组超参数C,然后训练得到训练参数W,f(C,W)就算是最终模型,再利用测试集计算一个准确率就结束了。
或者更复杂点的做法,先给定几组超参数【C1,C2,……Cm】,然后分别在训练集上训练得到不同训练参数【W1,W2,……Wm】,最后利用测试集得到准确率【P1,P2,……Pm】,最后挑选准确率最大值Pb对应的模型f(Cb,Wb)作为最终模型。如下图所示:
2、单一训练集测、验证集和测试集
将样本集划分为训练集、验证集和测试集,比例通常为8:1:1或6:2:2。
这种情况比第一种要好,多了一个验证集,至少让你可以根据验证集的结果来重新调超参数了。
整个过程变成了下面这样:
(1)先确定一组超参数C1,然后在训练集上训练得到参数W1,再用验证集得到准确率P1;
(2)如果对P1不满意,则更换超参数为C2,重新在训练集上训练得到参数W2;
……
(3)如此循环m次,最终得到了一系列超参数和训练参数对【(C1,W1),(C2,W2,……,(Cm,Wm)】;
(4)最后找出准确率最大值Pmax对应模型f(Cb,Wb),到这一步,我们已经基本找到了相对最优的超参数Cb以及对应的模型f(Cb,Wb);
(5)接下去既可以将f(Cb,Wb)作为最终模型;也可以利用Cb在“训练集+验证集”上重新训练一遍,得到一个新的训练参数Wbb,此时f(Cb,Wbb)就是最终模型;
(6)最后用测试集检验最终模型f(Cb,Wb)(或f(Cb,Wbb)),得到模型准确率Pb(或Pbb),好与不好都不再调整模型参数了。
如下图所示:
3、单一划分的缺陷
上述两种方式都有两个问题:
(1)把一个样本集划分成固定的训练集、(验证集)、测试集,由于每个样本大概率是不同的,因此即使子集内样本数每次都相同,最终的划分结果也不同,特别是对于小样本集。事实证明,在样本集规模较小的情况下,对于不同的子集划分(子集样本数相同),即使是完全相同的超参数C0,训练出来的模型也会出现很大波动(图中不同的accuracy),如下图所示:
(2)固定的训练集、(验证集)、测试集划分,模型的训练只利用到了训练集和验证集,而没有用到测试集,这是很浪费的,对于小样本集而言甚至是致命的。其次,训练好的模型在本来就不具代表性的测试集上做一次测试,就将其当做该模型的准确率是不合理的,只要测试集稍微发生变化,该模型的表现可能就变化很大,如下图所示:。
出现上述情况的本质原因是样本集分布不够充分(不能有效表征全局特征),导致划分后的子集更不充分,每一次划分后子集内样本的总体特征都会出现较大波动,自然训练和测试的结果也会波动较大;如果样本集足够大足够充分(足够表征全局特征),那么无论划分情况怎么变化,子集很大程度上也都足够充分,训练和测试出来的结果也就是全局结果,最优超参数和误差也会比较稳定。
二、交叉验证
2.1 k折交叉验证
针对前述“单一划分的缺陷”,“交叉验证”一定程度上能解决上述问题,其思路如下:
(1)先确定模型(例:SVM)和一组超参数(C0),然后在样本集(测试集除外)上做k折交叉验证,即独立训练了k个具体模型f(C0,W0)、f(C0,W1)……f(C0,Wk),并得到了每个模型在自己验证集上的准确率(P1、P2……Pk),然后求他们的平均准确率Pa,Pa就可以代表这组超参数的综合表现了(特别注意,交叉验证后,这k个具体模型将被丢)。交叉验证的唯一目的就是全面评价选定模型和超参数的综合表现,即得到Pa,而不是用交叉验证得到最终模型。相比于前面只固定一个验证集或测试集的情况,在交叉验证中,每个样本都有机会成为训练集和验证集,最后用多个模型的平均准确率来评价这组超参数的表现,结果自然更合理。
(2)第1步交叉验证我们知道了超参数C0的综合表现。然后利用C0在大训练集(测试集除外)上重新做一次训练(注意此时没有验证集了,一定要划分一个出来也行),得到最终训练参数Wb,此时的模型f(C0,Wb)就是我们要的最终模型,最后在测试集上得到Pb。f(C0,Wb)的准确率既可以用第1步得到的平均准确率Pa,也可以用Pb。
上面两步如下图所示:
注意1:实际中,对于小样本集而言,在交叉验证中,可以不用先划出一个测试集,而是在整个样本集上做交叉验证,得到平均准确率Pa,然后在整个样本集上重新做1次训练,得到最终模型f(C0,Wb),到此结束。并把交叉验证得到的平均准确率Pa作为f(C0,Wb)的准确率。
注意2:k折交叉验证之前,需要把大训练集分成k个子集,不重复地将每个子集作为验证集,而把剩余k-1个子集合起来作为训练集,从而可以得到k个模型(超参数都是C0);分子集时尽量保证每个子集都是均匀的。
2.2 k折交叉验证特例——留一法(LOOCV)
当k=1时,即所有训练样本都作为训练样本,这种情况相当于没有交叉验证,就是前面说的单一验证;
当k=N时(N是训练集的样本总数,不包括测试集),即每次只留1个样本作为验证集,N-1个作为训练集,这种方法称为留一法(LOOCV,Leave-one-out cross-validation);
当1
LOOCV的优点:首先它不受训训练集和验证集划分(这里指将大训练集划分为训练集和验证集)的影响,因为每一个样本都单独的做过验证集,同时,其用了N-1个样本训练模型,几乎用到了所有样本信息。
LOOCV缺点:计算量过大。
根据经验一般选择k=5或10。
下图展示了LOOCV和10折交叉验证的对比结果:
蓝色线表示真实的测试误差曲线;
黑色线表示LOOCV方法测试误差曲线;
橙色线表示10折交叉验证测试误差曲线;
可见LOOCV和10折交叉验证结果是很相似的,但后者的计算成本却小了很多。
2.3 利用k折交叉验证确定最优超参数(确定最优模型)
根据前面,交叉验证的目的是合理评价选择的模型和超参数的表现,而不是确定最优超参数,更不是得到最终模型。
但我们可以通过选择多个超参数C0,C1……Cm,分别进行交叉验证其表现,然后选择最优准确率Pax对应的超参数Cx,最后利用该超参数训练得到最终模型f(Cx,Wx),并在测试集上得到准确率px。(同前面一样,可以将Pax或Px)
如下图所示:
总结:
交叉验证的核心作用是全方位评价“给定模型和超参数C0”的表现(准确率),他会将训练集分成k份,每份分别作为验证集,剩下的作为训练集,并分别针对该模型和超参数训练得到k个模型,然后在各自的验证集上得到各自的准确率Pi,最后计算平均准确率Pa作为该模型和给定超参数的评价。到这里,交叉验证就结束了。交叉验证得到的平均准确率Pa相比固定划分情况下只在一个测试或验证集上得到的准确率要可信得多,因为交叉验证时所有的样本都当过训练样本和验证样本。
至于交叉验证后,再利用超参数C0重新训练得到最终的模型f(C0,Wb),或者利用k折交叉验证后选出最优超参数再训练最终模型,这些都已经与交叉验证无关了!
当整个训练集的样本数量较少时,可以不用事先留出测试集,而整个样本集上做交叉验证,得到模型及给定超参数C0的准确率Pa,然后在整个训练集上训练(无验证集和测试集)得到最终模型f(C0, W0),并将Pa作为他的准确率。
其他交叉验证方法:多次k折交叉验证(Repeated k-Fold Cross Validation)、蒙特卡洛交叉验证(Monte Carlo Cross Validation)
举例代码:
导入包:
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt
导入数据:
iris_dataset = load_iris()
iris=load_iris()
iris_X=iris.data
iris_y=iris.target
X_train, X_test, y_train, y_test = train_test_split(iris_X, iris_y, train_size=0.3, random_state=5)
采用k邻近模型,并指定超参数5:
my_neibs = 5
对于一次验证:
knn1 = KNeighborsClassifier(n_neighbors = my_neibs) # 生成一个指定超参数的k邻近模型
knn1.fit(X_train, y_train) # 在训练集上一次训练得到最终模型
print('在训练集上的一次验证模型准确率:',knn1.score(X_test,y_test)) #在测试集上测试模型的准确率
输出:
在训练集上的一次验证模型准确率: 0.9428571428571428
对于交叉验证:
knn2 = KNeighborsClassifier(n_neighbors = my_neibs) # 生成一个指定超参数的k邻近模型
# 交叉验证,确定超参数到底有多好
accuracy= cross_val_score(knn2, X_train, y_train, cv=8, scoring='accuracy') # 先做8折交叉验证,看看超参数5的表现
print("交叉验证后的平均准确率:", accuracy.mean())
输出:
交叉验证后的平均准确率: 0.9583333333333334
knn2.fit(iris_X, iris_y) # 在所有数据上训练得到最终模型,不再需要对该模型进行测试了,其准确率直接用交叉验证得到的平均准确率
# 可以直接拿这个模型去对其他没在样本集中的样本进行分类了!
说明:“一次验证”情况下,在训练集上直接得到的最终模型,在测试集的得分为0.9428571428571428(根据前面分析,这个值直接作为评价该模型(和超参数5)的准确率是不合理的(虽然实际中我们经常这么干),特别对于样本量较小的情况,换句话说光凭这个结果还没法相信这个模型(和超参数5),一次验证的缺陷就在这里;
于是我们在相同的测试集上做交叉验证得到平均准确率为0.9583333333333334,根据前面分析,这个值的可信度要更高,而且它比前面的值更大,这说明超参数5的效果在一次验证时被低估了。于是,我们自信满满地决定使用超参数5了,为了更多地使用样本数据,此时直接在全部样本上训练得到最终模型,显然,这个最终模型比一次验证的模型要更好,因为他用了更多的样本。此时我们就不再需要测试集了,可以直接用0.9583333333333334作为最终模型的准确率。
交叉验证确定最优超参数
neighbors_range = range(1,31) #超参数变化范围
neighbors_accur = [] # 存放每个超参数对应模型的交叉验证精度
for i in neighbors_range: #对参数进行控制,选择参数表现好的,可视化展示
knn = KNeighborsClassifier(n_neighbors=i) # 根据当前超参数i生成1个KNN模型
accuracy = cross_val_score(knn,iris_X,iris_y,cv=10,scoring='accuracy') #所有样本数据上通过交叉验证来判断该超参数的表现
print("第{}个超参数交叉验证后平均准确率:{}".format(i,accuracy.mean()))
neighbors_accur.append(accuracy.mean()) #计算均值得分
#绘图
plt.figure(figsize=(10,5))
plt.plot(neighbors_range,neighbors_accur)
plt.xlabel("Number of neighbors for KNN")
plt.ylabel("Cross-validates Accuracy")
输出:
第1个超参数交叉验证后平均准确率:0.96
第2个超参数交叉验证后平均准确率:0.9533333333333334
第3个超参数交叉验证后平均准确率:0.9666666666666666
第4个超参数交叉验证后平均准确率:0.9666666666666666
第5个超参数交叉验证后平均准确率:0.9666666666666668
第6个超参数交叉验证后平均准确率:0.9666666666666668
第7个超参数交叉验证后平均准确率:0.9666666666666668
第8个超参数交叉验证后平均准确率:0.9666666666666668
第9个超参数交叉验证后平均准确率:0.9733333333333334
第10个超参数交叉验证后平均准确率:0.9666666666666668
第11个超参数交叉验证后平均准确率:0.9666666666666668
第12个超参数交叉验证后平均准确率:0.9733333333333334
第13个超参数交叉验证后平均准确率:0.9800000000000001
第14个超参数交叉验证后平均准确率:0.9733333333333334
第15个超参数交叉验证后平均准确率:0.9733333333333334
第16个超参数交叉验证后平均准确率:0.9733333333333334
第17个超参数交叉验证后平均准确率:0.9733333333333334
第18个超参数交叉验证后平均准确率:0.9800000000000001
第19个超参数交叉验证后平均准确率:0.9733333333333334
第20个超参数交叉验证后平均准确率:0.9800000000000001
第21个超参数交叉验证后平均准确率:0.9666666666666666
第22个超参数交叉验证后平均准确率:0.9666666666666666
第23个超参数交叉验证后平均准确率:0.9733333333333334
第24个超参数交叉验证后平均准确率:0.96
第25个超参数交叉验证后平均准确率:0.9666666666666666
第26个超参数交叉验证后平均准确率:0.96
第27个超参数交叉验证后平均准确率:0.9666666666666666
第28个超参数交叉验证后平均准确率:0.9533333333333334
第29个超参数交叉验证后平均准确率:0.9533333333333334
第30个超参数交叉验证后平均准确率:0.9533333333333334
可见超参数13、18、20是比较好的,平均准确率达到了0.98。
利用超参数在全部样本数据中训练得到最终模型:
# 根据前面结果,可见超参数13、18、20是比较好的。
# 于是选择其中1个超参数,并在全部样本上训练得到最终模型:
knn3 = KNeighborsClassifier(n_neighbors=13)
knn3.fit(iris_X, iris_y) # 在所有数据上训练得到最终模型,不再需要对该模型进行测试了,其准确率直接用交叉验证得到的平均准确率
# 可以直接拿这个模型去对其他没在样本集中的样本进行分类了!