1.使用交叉验证进行模型评估
在前面的内容中,我们常常使用train_test_split功能来讲数据集进行拆分成训练集和测试集,然后使用训练集来训练模型,再用模型去拟合测试数据集并对模型进行评分,来评估模型的准确度。除了这种方法之外,我们还可以用一种更加粗暴的方式来验证模型的表现,也就是本节要介绍的交叉验证法(cross validation)。
1.1交叉验证法的定义:
在统计学中,交叉验证法是一种非常常用的对于模型泛化性能进行评估的方法。和我们之前所用的train_test_split方法所不同的是,交叉验证法会反复地拆分数据集,并用来训练多个模型。所以我们说这种方法更加粗暴。
在scikit-learn中默认使用的是交叉验证法是K折叠交叉验证法(K-fold crossvalidation)。
1.2K折叠交叉验证法(K-fold crossvalidation)的定义:
它将数据集拆分成K个部分,再用K个数据集对模型进行训练和评分。例如我们令k=5,则数据集被拆分成5份,其中第一个子集会被作为测试数据集,另外四个用来训练模型。之后再用第2个子集作为测试集,而另外4个用来训练模型。以此类推,直到把5个数据集全部用完,这样我们就会得到5个模型的评分。
此外,交叉验证法中海油其他的方法,例如“随机拆分交叉验证法”(shuffle-split cross validation)和“挨个试试”(leave-one-out)法。
下面先了解一下交叉验证法的使用方法,输入代码如下:
#导入红酒数据集
from sklearn.datasets import load_wine
#导入交叉验证工具
from sklearn.model_selection import cross_val_score
#导入用于分类的支持向量机模型
from sklearn.svm import SVC
wine = load_wine()
svc = SVC(kernel='linear')
scores = cross_val_score(svc,wine.data,wine.target)
scores
运行代码,得到如下所示的结果:
array([ 0.83333333, 0.95 , 1. ])
结果分析:在这段代码中,我们先导入了scikit_learn的交叉验证评分类,然后使用SVC对酒的数据集进行分类,在默认情况下,cross_val_score会使用3个折叠,因此,我们得到3个分数。
那么究竟模型的得分是其中哪一个呢?这里我们一般使用3个得分的平均分来计算。
输入代码:
scores.mean()
结果:0.928
如果我们希望将数据集拆分成5个部分来评分,只要修改cross_val_score的cv参数就可以了,例如我们想要修改为6个,输入代码如下:
scores = cross_val_score(svc, wine.data, wine.target, cv=6)
print('交叉验证得分:\n{}'.format(scores))
运行代码,结果如下:
查看平均分:
print('交叉验证平均分:{:.3f}'.format(scores.mean()))
交叉验证平均分:0.944
结果分析:模型平均分0.944还是很不错的。需要注意的是,在scikit-learn中,cross_val_score对于分类模型默认使用的是k折叠交叉验证,而对于分类模型则默认使用分层k交叉验证法。
思考:什么是分层k折叠交叉验证法?我们先分析一下酒的数据集,先来看一下酒的分类标签,输入代码如下:
print('酒的分类标签:\n{}'.format(wine.target))
结果分析:从结果中可以看出,如果不分层的k折叠的交叉验证法,那么在拆分数据集的时候,有可能每个子集中都是同一个标签,这样的话,模型评分都不会太高。而分层k折叠交叉验证法的优势在于,它会在每个不同分类中进行拆分,确保每个子集中都有数量基本一致的不同分类标签。举例来说,加入你有一个人口性别数据集,其中有80%是“男性”,只有20%是“女性”,分层k折叠交叉验证法会保证在你的每个子集中,都有80%的男性,其余20%是女性。
1.3随机拆分交叉验证法的原理
随机交叉验证法,它的原理是,先从数据集中随机抽一部分数据集作为训练集,再从其余的部分随机抽取一部分作为测试集,进行评分后再迭代,重复上一步的动作,直到吧我们希望迭代的次数全部跑完。
为了能够更直观地了解,我们还是使用酒的数据集来进行实验,输入代码如下:
#导入随机拆分工具
from sklearn.model_selection import ShuffleSplit
#设置拆分的份数为10
shuffle_split = ShuffleSplit(test_size=.2, train_size=.7,
n_splits = 10)
#对拆分好的数据集进行交叉验证
scores = cross_val_score(svc, wine.data, wine.target, cv=shuffle_split)
print('随机拆分交叉验证模型得分:\n{}'.format(scores))
从代码中可以看到,我们把每次迭代的测试集设置为数据集的20%,而训练集设置为数据集的70%,并且把整个数据集拆分成10个子集。运行代码,结果如下图所示:
结果分析:从结果中可以看到,ShuffleSplit一共为SVC模型进行了10次评分。而模型最终的得分也就是10个分数的平均值。
1.4挨个儿试试法的原理
挨个儿试试法的原理就和名字一样搞笑---它其实有点像k折叠交叉验证,不同的是,它把每一个数据点都当成一个测试集,所以你的数据集里有多少个样本,他就要迭代多少次。如果数据集大的话,这个方法还真挺耗时的。但是如果数据集很小的话,它的评分准确度是最高的。
下面我们使用酒的数据集来进行实验,输入代码如下:
from sklearn.model_selection import LeaveOneOut
cv = LeaveOneOut()
scores = cross_val_score(svc, wine.data, wine.target, cv=cv)
print('迭代次数:{}'.format(len(scores)))
print("模型平均分:{:.3f}".format(scores.mean()))
经过漫长的等待之后,终于得到了如图所示的结果:
结果分析:由于酒的数据集一共有178个样本,这意味着“挨个儿试试”法迭代了178次,最后给出评分为0.955分。
2.使用网格搜索优化模型参数
在前面的内容中,我们接触了很多不同的算法,也初步学习了不同的算法模型中比较重要的参数。我们在实验的时候,会手动逐个尝试不同的参数对于模型泛化表现的影响,这种方法固然是有效的,不过我们还可以实用一点小技巧,能够让我们一次性找到相对更优的参数设置,也就是我们要介绍的网格搜索法。
2.1简单网格搜索
这里我们用Lasso算法为例,Lasso算法中,有两个参数比较重要,一个是正则化系数alpha,另一个是最大迭代次数max_iter。在默认的情况下,alpha的取值是1.0,而max_iter的默认值是1000,假设我们想试试当alpha分别取10.0,1.0,0.1,0.01这4个数值,而max_iter分别取100,1000,5000,10000这4个数值时,模型的表现会有什么差别
,如果按照我们之前所用的手动调整的话,要实验16次才可以找到最高分,入下表所列:
下面我们试试以酒的数据集为例,用网格搜索的方法,一次找到模型评分最高的参数,输入代码如下:
from sklearn.linear_model import Lasso
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test=train_test_split(wine.data,
wine.target,
random_state=38)
best_score = 0
for alpha in [0.01,0.1,1.0,10.0]:
for max_iter in [100,1000,5000,10000]:
lasso = Lasso(alpha=alpha,max_iter=max_iter)
lasso.fit(X_train, y_train)
score = lasso.score(X_test, y_test)
if score > best_score:
best_score = score
best_parameters={'alpha':alpha,'最大迭代次数':max_iter}
print("模型最高分为:{:.3f}".format(best_score))
print('最佳参数设置:{}'.format(best_parameters))
运行代码,如下图所示:
或许看到这里,会觉得我们找到了一个很好的调节参数的办法,但是这种办法是有局限的,因为我们所进行的16次评分都是基于同一个训练集和测试集,这只能代表模型在该训练集和测试集的得分情况,不能反映出新的数据集的情况,例如修改一下train_test_Split的random_State参数如下:
from sklearn.linear_model import Lasso
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test=train_test_split(wine.data,
wine.target,
random_state=0)
best_score = 0
for alpha in [0.01,0.1,1.0,10.0]:
for max_iter in [100,1000,5000,10000]:
lasso = Lasso(alpha=alpha,max_iter=max_iter)
lasso.fit(X_train, y_train)
score = lasso.score(X_test, y_test)
if score > best_score:
best_score = score
best_parameters={'alpha':alpha,'最大迭代次数':max_iter}
print("模型最高分为:{:.3f}".format(best_score))
print('最佳参数设置:{}'.format(best_parameters))
在这段代码中,我们把train_test_Split的参数从38改为0,运行代码,得到如下所示的结果:
结果分析:我们发现,稍微对train_test_split拆分数据集的方式做一点变更,模型的最高分就降到了0.83,而且此时,lasso模型的最佳alpha的参数也从0.01变成了0.1.。为了解决这个问题,我们可以利用前面介绍的交叉验证法和网格搜索法结合起来寻找最优参数。
如何将交叉验证法和网格搜索法结合起来找到模型的最优参数,输入代码如下:
import numpy as np
for alpha in [0.01,0.1,1.0,10.0]:
for max_iter in [100,1000,5000,10000]:
lasso = Lasso(alpha=alpha,max_iter=max_iter)
scores = cross_val_score(lasso, X_train, y_train, cv=6)
score = np.mean(scores)
if score > best_score:
best_score = score
best_parameters={'alpha':alpha, '最大迭代数':max_iter}
print("模型最高分为:{:.3f}".format(best_score))
print('最佳参数设置:{}'.format(best_parameters))
运行代码,得到如下图的结果:
结果分析:这里我们做了一点手脚,就是只用先前拆分好的X_train来进行交叉验证,以便于我们找到最佳参数之后,再用来拟合x_test。来看一下模型的得分输入代码如下:
lasso = Lasso(alpha=0.01,max_iter=100).fit(X_train,y_train)
lasso.score(X_test,y_test)
结果:
0.81933489191945297
结果分析:0.819的模型得分是比较差强人意。不过这不是参数的问题,而是lasso算法对样本的特征进行正则化,导致一些特征的系数变成了0,因此使用lasso来进行分类的话,得分还是相对低一些。
每次寻找最优参数都要靠代码来完成难免有些麻烦,scikit-lean中,内置了一个雷,称为GridSearchCV,有了这个类,我们进行参数调优的过程就会稍微简单一些。比如上面这个例子,我们用G日的SearchCV再来实验一下,输入代码如下:
#导入网格搜索工具
from sklearn.model_selection import GridSearchCV
#将需要遍历的参数定义为字典
params = {'alpha':[0.01,0.1,1.0,10.0],'max_iter':[100,1000,5000,10000]}
#将需要的模型和参数代入网格搜索工具
search = GridSearchCV(lasso,params,cv=6)
#使用网格搜索工具拟合数据
search.fit(X_train,y_train)
print('模型最高分:{:.3f}'.format(search.score(X_test,y_test)))
print('模型最优参数:{}'.format(search.best_params_))
可以看到,使用GridSearchCV写出的代码更加简洁,运行代码,得到如下图所示的结果:
结果分析:我们可以看到使用GridSearchCV得到的结果和我们上一步中用到的cross_Val_Score结合网格搜索得到的结果一致。但是需要说明的是,在GridSearchCV中,还有一个属性称为best_Score_,这个属性会存储模型在交叉验证中所得的最高分,而不是测试集上的得分。
输入一行代码如下:
print('交叉验证最高得分:{:.3f}'.format(grid_search.best_score_))
运行结果:交叉验证最高得分:0.865
结果分析:回过头看我们在使用cross_val_Score进行评分的步骤,大家会发现这里的份数和cross_Val_score的得分是完全一样的。这说明,GridSearchCV本身就是讲交叉验证和网络搜索封装在一起的方法。这样的话,我们完全可以采用这种方法,来对参数进行调节。
GridSearchCV的缺点
虽然它GridSearchCV是个非常强悍的功能呢,但是由于需要反复建模,因此所需要的计算时间往往更长。
3.分类模型的可信度评估
3.1分类模型中的预测准确率
在scikit-lean中,很多用于分类的模型都有一个predict_proba功能,这个功能就是用于计算模型在对数据集进行分类时,每个样本属于某个分类的可能性是多少。
下面用实例来进行展示,输入代码如下:
#导入数据集生成工具
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
X,y = make_blobs(n_samples=200,centers=2,cluster_std=5,random_state=1)
plt.scatter(X[:,0],X[:,1],c=y,edgecolor='k',cmap=plt.cm.cool)
plt.show()
在代码中,我们又使用了make_blobs来制作我们的数据集,为了给算法增加一点难度,我们故意把样本数据的方差设高一些,即cluster_Std=5.运行代码,结果如下图所示:
结果分析:
从图中,可以看到,两类样本在中间有一些重合,如果说深色圆点表示“好看”,而浅色圆点表示“不好看”,那么中间交叉的部分就是“还可以”。下面我们使用高斯朴素贝叶斯来进行分类,输入代码如下:
#导入高斯贝叶斯模型
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
#将数据集拆分为训练集和测试集
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=68)
gnb = GaussianNB()
gnb.fit(X_train,y_train)
predict_proba = gnb.predict_proba(X_test)
print('模型准确率形态:{}'.format(predict_proba.shape))
从代码中可以看到,模型预测准确率是存储在GaussainNB的predict_proba属性当中的,运行代码可以看到这个属性的形态,如下图:
结果分析:这说明,在predict_proba属性中存储了50个数组(也就是测试数据及的大小),每个数组中有2个元素,打印前五个看看是什么样子,输入代码入如下:
print(predict_proba[:5])
运行结果:
结果分析:这个结果反映的是所有测试数据集中前5个样本的分类准确率,例如第一个数据点,有98.8%的概率属于第一个分类,而只有不到1.2%的概率属于第二个分类,所以模型会将这个点归于第一个分类当中。后面4个数据点是一样的道理。
我们可以用图像更直观地观看一下predict_proba在分类过程中的表现,输入代码如下:
import numpy as np
#设定横纵轴的范围
x_min,x_max = X[:,0].min()-.5,X[:,0].max()+.5
y_min,y_max = X[:,1].min()-.5,X[:,1].max()+.5
xx,yy = np.meshgrid(np.arange(x_min,x_max,.01),np.arange(y_min,y_max,.01))
#用不同的颜色表示不同的分类
Z = gnb.predict_proba(np.c_[xx.ravel(),yy.ravel()])[:,1]
Z = Z.reshape(xx.shape)
#绘制等高线
plt.contourf(xx,yy,Z,cmap=plt.cm.summer,alpha=.8)
#绘制散点图
plt.scatter(X_train[:,0],X_train[:,1],c=y_train,cmap=plt.cm.cool)
plt.scatter(X_test[:,0],X_test[:,1],c=y_test,cmap=plt.cm.cool)
#设置横纵轴范围
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
#设置横纵轴的单位
plt.xticks(())
plt.yticks(())
plt.show()
运行代码,如下图所示:
结果分析:从图中可以看到,半透明的深色圆点和浅色圆点代表的是测试集中的样本数据。浅色区域代表第一个分类,而深色部分代表第二个分类。在两个区域中间,有一部分渐变色的区域,处于这个区域的数据点便是模型觉得“还可以”的那一部分。
注意:并不是每个分类算法都有predict_proba属性,不过我们还可以使用另外一种方式来检查分类的可信度,就是决定系数decision_function。
3.1分类模型中的决定系数
同预测准确率类似,决定系数decision_function也会给我们返回一些数值,告诉我们模型认为某个数据点处于某个分了的“把握”有多大。不同的是,在二元分类任务中,它只返回一个值,如果是正数,则代表该数据点属于分类1,;如果是负数,则代表属于分类2.
我们还是用干菜生成的数据集来进行实验,不过由于高斯朴素贝叶斯没有decision_function属性,我们换成支持向量机SVC算法来进行建模。输入代码如下:
from sklearn.svm import SVC
svc = SVC().fit(X_train, y_train)
dec_func = svc.decision_function(X_test)
print (dec_func[:5])
运行代码,得到如图所示:
结果分析:从结果可以看出,在5个数据点钟,有4个decision_function数值为正数,1个为负数。这说明decision_function为正的4个数据点属于分类1,而decision_function为负的那1个数据点数据属于分类2.
接下来也可以用图形化的方式来展示decision_function的工作原理,输入代码如下:
Z = svc.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, cmap=plt.cm.summer, alpha=.8)
plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=plt.cm.cool,
edgecolor='k')
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap=plt.cm.cool,
edgecolor='k', alpha=0.6)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title('SVC decision_function')
plt.xticks(())
plt.yticks(())
plt.show()
运行代码,得到如下图所示的结果:
结果分析:对比上面的高斯朴素贝叶斯的示意图,我们发现SVC的decision_function和GaussianNB的predict_proba有相似的地方,但也有很大差异。在高斯朴素贝叶斯的示意图中,分类同样是用浅色和深色区域来表示,如果某个数据点所处的区域颜色越明显,说明模型月确定这个数据点属于分类1,反之则属于分类2,而那些处于渐变色区域的数据点,则是模型觉得“模棱两可”的数据点。
注意
在本例中,我们使用的都是只有两个分类的数据集,即二元分类任务,但是predict_proba和decision_function同样适用于多元分类任务。