《Python机器学习基础教程》笔记
机器学习模型的泛化性能可以通过调参来提升,但找到一个模型的重要参数(提供最佳泛化性能的参数)的取值是一项棘手的任务,不过,Scikit-Learn中有一些标准方法可以帮助找到最佳参数,最常用的方法就是网格搜索,主要是尝试我们关心的参数的所有可能组合。
接下来先根据网格搜索的思路,用自己的代码实现最优参数值搜索,以便理解,然后再用Scikit-Learn中提供的GridSearchCV类简洁的实现,最后在用热图分析一下交叉验证的结果。
一、for循环实现的网格搜索(带交叉验证)
原理:在多个参数上使用for循环,对每种参数组合分别训练并评估一个分类器。
以SVC为例:
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
#加载数据
iris = load_iris()
#将数据划分为(训练+验证)集和测试集
X_trainval,X_test,y_trainval,y_test = train_test_split(iris.data,iris.target,random_state=0)
#将(训练+验证)集划分为训练集和验证集
X_train,X_valid,y_train,y_valid = train_test_split(X_trainval,y_trainval,random_state=1)
best_score = 0
for gamma in [0.001,0.01,0.1,1,10,100]:
for C in [0.001,0.01,0.1,1,10,100]:
#对每种参数组合都训练一个SVC
svm = SVC(gamma=gamma,C=C)
svm.fit(X_train,y_train)
#在验证集上评估SVC
score = svm.score(X_valid,y_valid)
#如果获得了更高的分数,则保持该分数和对应的参数
if score > best_score:
best_score = score
best_parameters = {'C': C,'gamma':gamma}
#在训练+验证集上重新构建一个模型,并在测试集上进行评估
svm = SVC(**best_parameters)
svm.fit(X_trainval,y_trainval)
test_score = svm.score(X_test,y_test)
print("Best score on validation set:{:.2f}".format(best_score))
print("Best parameters:",best_parameters)
print("Test set score with best parameters:{:.2f}".format(test_score))
结果如下:
注意,这里出现了验证集的概念,之前我们都是将数据集划分为训练集和测试集,这里解释一下验证集的由来。如果我们只是将数据分为训练集和测试集,那么我们就会用训练集进行模型构建,用测试集进行调参,但是这样就会导致一个问题:任何根据测试集精度所做的选择都会将测试集的信息“泄露”到模型中,而我们又用测试集进行模型评估,就会导致最终的测评结果过于乐观。因此,保留一个单独的测试集是很重要的,它仅用于最终评估。
总结一下训练集、验证集、测试集的作用:训练集用于构建模型,验证集用于选择模型的参数,测试集用于所选参数的性能测试。
二、用GridSearchCV类实现网格搜索
这里主要介绍三种网格搜索方式:网格空间中搜索、非网格空间中搜索以及嵌套交叉验证。
1.在网格空间中搜索
用法:先选择模型,再将参数转化为字典,然后对GridSearchCV进行示例化,接下来就是日常操作——训练模型,求测评分数。
以SVC为例:
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
param_grid = {'C':[0.001,0.01,0.1,1,10,100],'gamma':[0.001,0.01,0.1,1,10,100]}
grid_search = GridSearchCV(SVC(),param_grid,cv=5)
X_train,X_test,y_train,y_test = train_test_split(iris.data,iris.target,random_state=0)
grid_search.fit(X_train,y_train)
print("Test set score:{:.2f}".format(grid_search.score(X_test,y_test)))
结果如下:
2.在非网格的空间中搜索
说明:一般情况下,网格搜索尝试所有参数的所有可能组合,但有时候会遇到“条件”参数,例如SVC有一个kernel参数,如果kernel='linear',则模型只会用到C参数,如果kernel='rbf',则模型需要使用C和gamma两个参数。如果kernel='linear',那么gamma参数是用不到的,尝试gamma的不同取值将会浪费时间。因此,GridSearchCV的parm_grid可以是字典组成的列表,列表中的每个字典可扩展为一个独立的网格,例如:
#在非网格的空间中搜索
param_gird = [{'kernel':['rbf'],'C':[0.001,0.01,0.1,1,10,100],'gamma':[0.001,0.01,0.1,1,10,100]},{'kernel':['linear'],'C':[0.001,0.01,0.1,1,10,100]}]
grid_search = GridSearchCV(SVC(),param_grid,cv=5)
grid_search.fit(X_train,y_train)
print("Best parameters:{}".format(grid_search.best_params_))
print("Best cross-validation score:{:.2f}".format(grid_search.best_score_))
3.嵌套交叉验证
说明:GridSearchCV对分类问题默认使用分层k折交叉验证,对回归问题默认使用k折交叉验证。如果更深入一点,可以用嵌套交叉验证。在嵌套交叉验证中,有外层循环和内层循环,外层循环遍历将数据划分为训练集和测试集的所有划分(原始数据使用交叉验证进行多次划分),内层循环在划分好的训练集中使用交叉验证。使用方法如下:
scores = cross_val_score(GridSearchCV(SVC(),param_grid,cv=5),iris.data,iris.target,cv=5)
print("Test set score:{:.2f}".format(scores.mean()))
三、用热图分析交叉验证的结果
网格搜索的结果可以在cv.results_属性中找到,它是一个字典,其中保存了搜索的所有内容。其中每一行对应一种特定的参数设置,对于每种参数设置,交叉验证所有划分的结果都被记录下来,所有划分的平均值和标准差也被记录下来了,可用热图对其可视化。代码如下:
#用热图分析交叉验证的结果
import pandas as pd
import mglearn
#转换为DataFrame
results = pd.DataFrame(grid_search.cv_results_)
scores = np.array(results.mean_test_score).reshape(6,6)
#对交叉验证平均分数作图
mglearn.tools.heatmap(scores,xlabel='gamma',xticklabels=param_grid['gamma'],ylabel='C',yticklabels=param_grid['C'],cmap='viridis')
注:这里的mglearn包是《Python机器学习基础教程》这本书自己写的一个包,将很多书上需要用到的绘图代码都封装进去了,因此只需要调用里面的函数就可以画图了。这个包可以在GitHub上下载,链接如下:Python机器学习基础教程 附加代码,里面还包含了本书中其它很多代码,可以下载下来学习。
结果如下:
浅色表示高精度,深色表示低精度。从图中可以看出,我们调节的参数对于获得良好的性能非常重要,因为在我们选择的参数范围中可以看到输出发生了显著的变化。
需要注意的是,参数的范围要够大,每个参数的最佳取值不能位于图像边界上,下面举例几幅结果不理想的图进行说明:
第一个图没有显式任何变化,可能是参数范围不正确,也可能是参数未正确缩放,或者是参数并不重要。
第二个图呈现垂直条带模式,可能是C参数(纵轴)搜索范围不对,或者是C参数并不重要。
第三个图整个左下角无变化,则需要改变搜索范围。
生成以上三个图的代码如下:
import matplotlib.pyplot as plt
fig,axes = plt.subplots(1,3,figsize=(13,5))
param_grid_linear = {'C':np.linspace(1,2,6),'gamma':np.linspace(1,2,6)}
param_grid_one_log = {'C':np.linspace(1,2,6),'gamma':np.logspace(-3,2,6)}
param_grid_range = {'C':np.logspace(-3,2,6),'gamma':np.logspace(-7,-2,6)}
for param_grid,ax in zip([param_grid_linear,param_grid_one_log,param_grid_range],axes):
grid_search = GridSearchCV(SVC(),param_grid,cv=5)
grid_search.fit(X_train,y_train)
scores = grid_search.cv_results_['mean_test_score'].reshape(6,6)
#对交叉验证平均分数作图
scores_image = mglearn.tools.heatmap(scores,xlabel='gamma',xticklabels=param_grid['gamma'],ylabel='C',yticklabels=param_grid['C'],cmap='viridis',ax=ax)
plt.colorbar(scores_image,ax=axes.tolist())
其它重要知识点:
①用GridSearchCV类创建的对象就像是一个分类器,可以对它调用标准的fit、predict和score方法。
②用另一个估计器创建的Scikit-Learn估计器称为元估计器,GridSearchCV是最常用的元估计器。
③交叉验证最佳精度保存在best_score_中。
④在Scikit-Learn中实现嵌套交叉验证只需要调用cross_val_score,并用GridSearchCV的一个实例作为模型即可。
⑤虽然在许多参数上运行网格搜索和在大型数据集上运行网格搜索的计算量很大,但这些计算都是并行的,因此可以在多个CPU内核或集群上并行运算。
⑥Scikit-Learn不允许并行操作的嵌套。