模型评估方法主要有三种:留出法、交叉验证法、自助法
。这三个方法都需要将数据集分为测试集和训练集,用测试集的“测试误差”来近似模型的泛化误差。当然测试集和训练集应该尽可能做到互斥,这样得到的模型才具有较好的泛化性能。

1.留出法

留出法就是将数据集D直接划分为互斥的两个集合,分别作为训练集S和测试集T。用测试集T的测试误差来估计模型的泛化误差。

数据的划分比例一般是:2/3~4/5比例的样本用于训练,剩下的样本用于测试。

样本划分时要尽量保持数据分布的一致性,至少保留类别比例的相似,避免引入偏差。保留类别比例的采样通常称为“分层采样”(stratified sampling)。

给定样本划分比例后,数据集D的划分方式任然可以有多种,为了使得估计结果更加稳定可靠,应进行多次重复试验,取多次评估结果的平均值。

可以用sklearn中的StratifiedShuffleSplit函数进行实现,当然自己也可以简单地写一个。

#!/usr/bin/env python # -*- coding: utf-8 -*- from sklearn.model_selection
import StratifiedShuffleSplit import numpy as np X = np.array([[0, 1], [0, 2],
[0, 3], [1, 8], [1,9], [1,10]]) y = np.array([0, 0, 0, 1, 1, 1])
#测试集的比例为0.333,采用分层抽样。且随机进行5次 sss = StratifiedShuffleSplit(n_splits=5,
test_size=0.333) for train_index, test_index in sss.split(X, y):
print("TRAIN:", train_index, "TEST:", test_index) X_train, X_test =
X[train_index], X[test_index] y_train, y_test = y[train_index], y[test_index]

结果:

2.交叉验证法

交叉验证法是将数据集D划分为k份大小近似的互斥子集,同时尽可能做到数据分布的一致性(分层采样)。

每次用k-1个子集进行训练,然后用剩余的一份作为测试集,这样就可以进行k次的训练和测试,最终返回k个测试结果的平均值。这种交叉验证方法就叫做“k折交叉验证”(k-fold
cross validation)。常用的有10折交叉验证方法,特殊的有留一法(Leave- One-Out cross validation, LOOCV)。

(1)10折交叉验证

将数据集划分为10份,每次取其中9份用来训练,1份用于测试,最终得到十次测试结果的平均值(外层交叉验证)。

每次在用9分数据进行训练时,如果模型需要调参,则可进一步在训练过程当中进行交叉验证(内层)。比如:每次取9份中的8份数据用于训练,剩余的一份用于验证,用9次的均值来对当前参数做出评估。下图是一个3折交叉验证的列子,其中C为要优化的参数。

可以用sklearn中的StratifiedKFold函数来对数据集进行划分,实现10折交叉验证。这里以SVM分类器为例:

#!/usr/bin/env python # -*- coding: utf-8 -*- import loadData import copy
import numpy as np from sklearn import svm from sklearn.model_selection import
StratifiedKFold from sklearn.metrics import accuracy_score feature ,label =
loadData.loadData() #自己写的函数,加载数据 smps = len(label) #样本个数 #用于保存每一折的样本索引
foldsList = [] ss = StratifiedKFold(n_splits=10, shuffle = True) for
train_index, test_index in ss.split(feature, label): print("TEST:",
test_index)#获得索引值 foldsList.append(test_index) test_accur_list = [] for i in
xrange(10):#外层循环,9份训练,一份测试 train_index = list(set(range(0, smps)) -
set(foldsList[i])) test_index = foldsList[i] train_fea, test_fea =
feature[train_index], feature[test_index] train_lab, test_lab =
label[train_index], label[test_index] foldLi = copy.deepcopy(foldsList)
#删除测试的那一份 del foldLi[i] #将foldLi里面是list合并 foldL = [x for p in foldLi for x in
p] print 'for %s time gridSearch process:' % i c_can = np.logspace(-15, 15, 10,
base = 2)#假定SVM中的参数C的取值 n_search = len(c_can) bestC = 0
#对于这次的9份训练数据,通过下面内层交叉验证确定的最佳C值 bestAccur = 0 #内层交叉验证的最佳测试精度,即C取bestC时 for j in
xrange(n_search):#对参数C挨个进行测试 Accur = 0 for n in xrange(9):#C取值为c_can[j]时的内层交叉验证
train_i = list(set(foldL) - set(foldLi[n])) test_i= foldLi[n] train_f, test_f =
feature[train_i], feature[test_i]#训练集对应的值 train_l, test_l = label[train_i],
label[test_i]#类别集对应的值 clf = svm.SVC(C = c_can[j], kernel='linear')
clf.fit(train_f, train_l) y_hat = clf.predict(test_f) Accur +=
accuracy_score(test_l, y_hat) / 9 print ' Accur:%s' % Accur if Accur >
bestAccur:#根据内层交叉验证结果,查找bestC bestAccur = copy.deepcopy(Accur) bestC =
copy.deepcopy(c_can[j]) print ' Best validation accuracy on current dataset
split:', bestAccur print ' Best para C:', bestC #找到bestC后,然后才是外层的一次:训练、测试。 clf
= svm.SVC(C = bestC, kernel='linear') clf.fit(train_fea, train_lab) y_hat =
clf.predict(test_fea) test_accur_list.append(accuracy_score(test_lab, y_hat))
print ' test accur:', test_accur_list[i] print '\n' #最终得到十折交叉验证的结果 print
'average test accur:', sum(test_accur_list) / len(test_accur_list)

上面实现的是一次10折交叉验证,为避免因样本划分不同而引入额外的误差,应重复执行上述过程,并取多次试验结果的均值,如10次10折交叉验证。

(2)留一法(LOOCV)

留一法是交叉验证中的一个特例。若数据集D包含m个样本,留一法就是将k取为m时的情况。留一法在划分样本时只有一种结果,就是每个子集包含一个样本,所以留一法的试验结果并不受样本随机划分的影响。另外,留一法使用的训练集和原始数据集D相比只少了一个样本,所以留一法得出的模型和期望模型很相似,评估结果往往被认为较为准确。但是在数据集D较大时,留一法也存在着计算复杂度高的缺点。在实践过程中,每次取一个样本作为测试集,剩余的作为训练集(外层)。如果模型需要调参,那么在训练过程中可以用10折交叉验证来进行参数的确定(内层)。这里任然以SVM分类器为例:

#!/usr/bin/env python # -*- coding: utf-8 -*- import loadData import copy
import numpy as np from sklearn import svm from sklearn.model_selection import
StratifiedKFold from sklearn.metrics import accuracy_score feature ,label =
loadData.loadData() #自己写的函数,加载数据 smps = len(label) #样本个数 test_accur_list = []
for i in xrange(smps):#外层循环,LOOCV:一个样本测试,剩余训练 train_index = list(set(range(0,
smps)) - set([i])) test_index = [i] train_fea, test_fea = feature[train_index],
feature[test_index] train_lab, test_lab = label[train_index], label[test_index]
foldLi = [] ss = StratifiedKFold(n_splits=10, shuffle = True) for train_i,
test_i in ss.split(train_fea, train_lab): print("TEST:", test_i)#获得索引值
foldLi.append(test_i) #将foldLi里面是list合并 foldL = [x for p in foldLi for x in p]
print 'for %s time gridSearch process:' % i c_can = np.logspace(-15, 15, 10,
base = 2)#假定SVM中的参数C的取值 n_search = len(c_can) bestC = 0
#对于这次的9份训练数据,通过下面内层交叉验证确定的最佳C值 bestAccur = 0 #内层交叉验证的最佳测试精度,即C取bestC时 for j in
xrange(n_search):#对参数C挨个进行测试 Accur = 0 for n in
xrange(10):#C取值为c_can[j]时的内层交叉验证 train_i = list(set(foldL) - set(foldLi[n]))
test_i= foldLi[n] train_f, test_f = feature[train_i], feature[test_i]#训练集对应的值
train_l, test_l = label[train_i], label[test_i]#类别集对应的值 clf = svm.SVC(C =
c_can[j], kernel='linear') clf.fit(train_f, train_l) y_hat =
clf.predict(test_f) Accur += accuracy_score(test_l, y_hat) / 10 print '
Accur:%s' % Accur if Accur > bestAccur:#根据内层交叉验证结果,查找bestC bestAccur =
copy.deepcopy(Accur) bestC = copy.deepcopy(c_can[j]) print ' Best validation
accuracy on current dataset split:', bestAccur print ' Best para C:', bestC
#找到bestC后,然后才是外层的一次:训练、测试。 clf = svm.SVC(C = bestC, kernel='linear')
clf.fit(train_fea, train_lab) y_hat = clf.predict(test_fea)
test_accur_list.append(accuracy_score(test_lab, y_hat)) print ' test accur:',
test_accur_list[i] print '\n' #LOOCV的结果 print 'average test accur:',
sum(test_accur_list) / len(test_accur_list)
3.自助法

一方面,留一法存在计算复杂度高的缺点;另一方面,若保留较多的测试集,则训练出来的模型和期望模型又存在着一定的偏差。自助法就是一个较好的解决方法。假定数据集D有m个样本,每次从D中随机有放回地挑选一个样本,执行m次,得到包含m个样本的数据集D'。显然,D中有一部分样本会在D'中多次出现,而另外一部分则不会出现。某样本在这m次采样中不被采到的概率约为0.368,所以初始数据集D中约有36.8%的样本未出现在D'中。这样,实际评估的模型与期望评估的模型都会使用m个训练样本,而我们任有1/3的样本可用于训练。

自助法常用于数据集较小、难以有效划分训练/测试集时。由于自助法每次能够从原始数据集中产生不同的训练集,这些训练集存在着相似性但又不是完全不同,所以可以用于集成学习等算法。但自助法采样会改变初始数据集的分布,引入估计偏差,所以在数据充足时,尽量使用留出法和交叉验证法。

示例代码:

#!/usr/bin/env python # -*- coding: utf-8 -*- import loadData import copy
import numpy as np from sklearn import svm from sklearn.model_selection import
StratifiedKFold from sklearn.metrics import accuracy_score feature ,label =
loadData.loadData() #自己写的函数,加载数据 testAccur_list = [] samps = len(label)
#使用自助采样法,重复执行10次 for i in xrange(10): train_index = [] #自助法抽样,随机有放回地抽取samps个样本
for i in xrange(int(1.0 * samps)): train_index.append(random.randint(0,samps -
1)) test_index = list(set(list(range(0, samps))) - set(train_index))
print("TRAIN:", train_index, "TEST:", test_index)#获得索引值 train_fea, test_fea =
feature[train_index], feature[test_index]#训练集对应的值 train_lab, test_lab =
label[train_index], label[test_index]#类别集对应的值 ''' ''' 利用训练样本进行交叉验证,获取参数:C =
bestC ''' ''' clf = svm.SVC(C = bestC, kernel='linear') clf.fit(train_fea,
train_lab) y_hat = clf.predict(test_fea) accur = accuracy_score(test_lab,
y_hat) print i, ' time run testAccuracy:', accur testAccur_list.append(accur)
print '10 times run testAccur_list:', testAccur_list print 'average testAccur
value:', sum(testAccur_list) / 10

参考文献:

1.《机器学习》,周志华。

2.Statnikov A, Aliferis C F, Tsamardinos I, et al. A comprehensive evaluation
of multicategory classification methods for microarray gene expression cancer
diagnosis[J]. Bioinformatics, 2004, 21(5): 631-643.

技术
©2019-2020 Toolsou All rights reserved,
关于Navicat for mysql 的2003错误指定位置输出字符串(详细解析)ajax get请求中文参数乱码解决(精华)2020年7月15日 微信小程序 import和include差别(精华)2020年6月26日 C#类库 异常处理帮助类MySql语句 递归寻找某输入部门的所有下级部门Spark SQL-编程关于Bellman-Ford算法的个人理解Vue页面跳转传递参数及接收Thread.getContextClassLoader与Thread.getClassLoader()区别