ML–模型评估与优化
主要涉及的知识点有:
- 使用交叉验证对模型进行评估
- 使用网络搜索寻找模型的最优参数
- 对分类模型的可信度进行评估
一.使用交叉验证进行模型评估
在前面的内容中,我们常常使用scikit-learn
中的train_test_split
功能来将数据集拆分成训练数据集和测试数据集,然后使用训练集来训练模型,再用模型去拟合测试数据集并对模型进行评分,来评估模型的准确度。除了这种方法之外,我们还可以用一种更加粗暴的方式来验证模型的表现,也就是要介绍的交叉验证法(Cross Validation)
1.scikit-learn中的交叉验证法
在统计学中,交叉验证法是一个非常常用的对于模型泛化性能进行评估的方法。他和train_test_split
方法不同的是,交叉验证法会反复地拆分数据集,并用来训练多个模型。所以我们说这种方法更加粗暴
在scikit-learn
中默认使用的交叉验证法是K折叠交叉验证法(k-fold cross validation)
。这种方法很容易理解–它将数据集拆分为k个部分,再用k个数据集对模型进行训练和评分例如我们令k等于5,则数据集被拆分成5个,其中第1个子集会被作为测试数据集,另外4个用来训练模型。之后再用第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)
print('交叉验证得分:{}'.format(scores))
E:Anacondaenvsmytensorflowlibsite-packagessklearnmodel_selection\_split.py:2053: FutureWarning: You should specify a value for 'cv' instead of relying on the default value. The default value will change from 3 to 5 in version 0.22.
warnings.warn(CV_WARNING, FutureWarning)
交叉验证得分:[ 0.83333333 0.95 1. ]
[结果分析] 我们先导入了scikit-learn
的交叉验证评分类,然后使用SVC
对酒的数据集进行分类,在默认情况下,cross_val_score
会使用3个折叠,因此,我们会得到3个分数
# 使用mean()来获得分数平均值
print('交叉验证平均分:{:.3f}'.format(scores.mean()))
交叉验证平均分:0.928
如果我们希望能够将数据集拆成5个部分来评分,只要修改cross_val_score
的cv
参数就可以了,例如我们想要修改为6个
# 设置cv参数为6
scores=cross_val_score(svc,wine.data,wine.target,cv=6)
print('交叉验证得分:
{}'.format(scores))
交叉验证得分:
[ 0.86666667 0.9 0.93333333 0.96666667 1. 1. ]
接下来我们依然可以使用scores.mean()
来获取分数平均值
# 使用mean()来获得分数平均值
print('交叉验证平均分:{:.3f}'.format(scores.mean()))
交叉验证平均分:0.944
注意 在scikit-learn中,cross_val_score对于分类模型默认使用的是k折叠交叉验证,而对于分类模型则默认使用分层k交叉验证法
要解释清楚什么是分层k折叠交叉验证法,我们需要先分析一个酒的数据集,我们使用下面这行代码来看一下酒的分类标签
# 红酒数据集的分类标签
print('酒的分类标签:
{}'.format(wine.target))
酒的分类标签:
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2]
[结果分析] 从结果中可以看出,如果用不分层的k折叠的交叉验证法,那么在拆分数据集的时候,有可能每个字集中都是同一个标签,这样的话模型评分都不会太高。而分层k折叠交叉验证法的优势在于,它会在每个不同分类中进行拆分,确保每个字集中都有数量基本一致的不同分类标签。举例来说,假如你有一个人口性别数据集,其中有80%是"男性",只有20%是"女性",分层k折叠交叉验证法会保证在你的每个子集中,都有80%的男性,其余20%是女性
2.随机拆分和"挨个儿试试"
随机拆分交叉验证法的原理是先从数据集中随机抽一部分数据集作为训练集,再从其中的部分随机抽一部分作为测试集,进行评分后再迭代,重复上一步的动作,直到把我们希望迭代的次数全部跑完
# 导入随机拆分工具
from sklearn.model_selection import ShuffleSplit
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('随机拆分交叉验证模型得分:
{}'.format(scores))
随机拆分交叉验证模型得分:
[ 0.97222222 0.91666667 0.91666667 0.94444444 0.97222222 0.91666667
0.94444444 0.94444444 0.94444444 0.97222222]
从代码中,我们把每次迭代的测试集设置为数据集的20%,而训练集设置为数据集的70%,并且把整个数据集拆分成10个子集
挨个儿试试
它其实有点像k折叠交叉验证,不同的是,它把每一个数据点都当成一个测试集,所以你的数据集里有多少样本,它就要迭代多少次。如果数据集大的话,这个方法还是真耗时的。但是如果数据集很小的话,它的评分准确度是最高的
# 导入LeaveOneOut
from sklearn.model_selection import LeaveOneOut
# 设置cv参数为LeaveOneOut
cv=LeaveOneOut()
scores=cross_val_score(svc,wine.data,wine.target,cv=cv)
print('迭代次数:{}'.format(len(scores)))
print('模型平均分:{}'.format(scores.mean()))
迭代次数:178
模型平均分:0.9550561797752809
3.为什么要使用交叉验证法
原因是这样的:当我们使用train_test_split
方法进行数据集的拆分时,train_test_split
用的是随机拆分的方法,万一我们拆分的时候,测试集中都是比较容易进行分类或者回归的数据,而训练集中都比较难,那么模型的得分就会偏高,反之模型的得分就会偏低。我们又不太可能把所有的random_state
遍历一遍。而交叉验证法正好弥补了这个缺陷,它的工作原理导致它要对多次拆分进行评分再取平均值
此外,train_test_split
总是按照25%-75%的比例来拆分训练集与测试集(默认情况下),但当我们使用交叉验证法的时候,可以更加灵活地指定训练集和测试集的大小,比如当cv参数为10的时候,训练集就会占整个数据集的90%,测试集占10%;cv参数为20的时候,训练集的占比就会达到95%,而测试集占比5%。这意味着训练集会更大,对于模型的准确率也有促进的作用
二.使用网络搜索优化模型参数
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次才可以找到最高分
alpha=0.01 | alpha=0.1 | alpha=1.0 | alpha=10.0 | |
---|---|---|---|---|
max_iter=100 | 1 | 2 | 3 | 4 |
max_iter=1000 | 5 | 6 | 7 | 8 |
max_iter=5000 | 9 | 10 | 11 | 12 |
max_iter=10000 | 13 | 14 | 15 | 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)
# 设置初始分数为0
best_score=0
#设置alpha参数遍历0.01,0.1,1,10
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))
模型最高分为:0.889
最佳参数设置:{'alpha': 0.01, '最大迭代次数': 100}
修改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)
# 设置初始分数为0
best_score=0
#设置alpha参数遍历0.01,0.1,1,10
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))
模型最高分为:0.830
最佳参数设置:{'alpha': 0.1, '最大迭代次数': 100}
2.与交叉验证结合的网格搜索
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))
模型最高分为:0.865
最佳参数设置:{'alpha': 0.01, '最大迭代次数': 100}
使用上面最佳参数模型拟合数据
# 用最佳参数模型拟合数据
lasso=Lasso(alpha=0.01,max_iter=100).fit(X_train,y_train)
print('测试数据集得分:{:.3f}'.format(lasso.score(X_test,y_test)))
测试数据集得分:0.819
接下来介绍GridSearchCV
,这个类是scikit-learn
中内置了一个类,使用这个类,我们进行参数调优的过程就会稍微简单一些
from sklearn.model_selection import GridSearchCV
params={'alpha':[0.01,0.1,1.0,10.0],
'max_iter':[100,1000,5000,10000]
}
grid_search=GridSearchCV(lasso,params,cv=6)
grid_search.fit(X_train,y_train)
print('模型最高分:{:.3f}'.format(grid_search.score(X_test,y_test)))
print('最优参数:{}'.format(grid_search.best_params_))
模型最高分:0.819
最优参数:{'alpha': 0.01, 'max_iter': 100}
在GridSearchCV
中,还有一个属性称为best_score_
,这个属性会存储模型在交叉验证中所得的最高分,而不是再测试数据集上的得分
print('交叉验证最高分:{:.3f}'.format(grid_search.best_score_))
交叉验证最高分:0.865
三.分类模型的可信度评估
1.分类模型中的预测准确率
在scikit-learn
中,很多用分类的模型都有一个predict_proba
功能,这个功能就是用于计算模型在对数据集进行分类时,每个样本属于不同分类的可能性是多少
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
# 生成样本数为200,分类为2,标准差为5的数据集
X,y=make_blobs(n_samples=200,random_state=1,centers=2,cluster_std=5)
plt.scatter(X[:,0],X[:,1],c=y,cmap=plt.cm.cool,edgecolor='k')
plt.show()
下面我们使用高斯朴素贝叶斯进行分类
from sklearn.naive_bayes import GaussianNB
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))
预测准确率形态:(50, 2)
# 打印准确率的前5个
print(predict_proba[:5])
[[ 0.98849996 0.01150004]
[ 0.0495985 0.9504015 ]
[ 0.01648034 0.98351966]
[ 0.8168274 0.1831726 ]
[ 0.00282471 0.99717529]]
我们可以用图像更直观地看一下predict_proba
在分类过程中的表现
# 设定横纵轴的范围
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,0.2),np.arange(y_min,y_max,0.2))
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,edgecolors='k')
plt.scatter(X_test[:,0],X_test[:,1],c=y_test,cmap=plt.cm.cool,edgecolors='k',alpha=0.6)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.xticks(())
plt.yticks(())
plt.show()
2.分类模型中的决定系数
决定系数(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])
[ 0.02082432 0.87852242 1.01696254 -0.30356558 0.95924836]
接下来我们也可以用图形化的方式来展示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,edgecolors='k')
plt.scatter(X_test[:,0],X_test[:,1],c=y_test,cmap=plt.cm.cool,edgecolors='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()
注意 predict_proba
和decision_function
同样适用于多元分类任务,感兴趣的可以调整make_blobs
的centers
参数进行实验