尽管将数据集划分为训练集、验证集和测试集的方法相对有用,可行性较高。但是这个方法对数据的划分比较敏感,且有时候泛化性能较低,为了得到更好的泛化性能的更好估计,我们可以通过交叉验证来评估每种组合的性能,而不是单纯的将数据单次划分为训练集与验证集。对应代码如下:
from sklearn.model_selection import cross_val_score
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)
#执行交叉验证
score = cross_val_score(svm, x_train_val, y_train_val, cv=5)
#计算交叉验证的平均值
score = np.mean(score)
#如果我们得到了更高的分数,那么就保留这个分数到best_score
if(score > best_score):
best_score = score
best_parameters = {'C': C, 'gamma': gamma}
#在训练集+验证集上重新构建一个模型,并在测试集上进行评估
svm = SVC(**best_parameters)
svm.fit(x_train_val, y_train_val)
print("best score of cross on validation is: {:.3f}".format(best_score))
print("Best parameters of cross are: {}".format(best_parameters))
运行上述代码结果为:
best score of cross on validation is: 0.973
Best parameters of cross are: {'C': 100, 'gamma': 0.01}
有运行结果可以看出,使用交叉验证后其训练精度有所提高,不过算法开销时间较大,这里需要训练36*5=180个模型,可以想想,使用交叉验证的主要缺点就是训练这些模型所耗费的时间。
下面我们看下上述代码是如何选择最佳设置的:
mglearn.plots.plot_cross_val_selection()
带交叉验证的网格搜索结果
交叉验证的每次划分都要计算一个精度值,然后对每个参数设置计算平均验证精度,最后选择平均验证精度的最高参数,用圆圈标记。由于交叉验证常常与网格搜索联合使用,所以很多人使用交叉验证这一俗语来通俗的指代交叉验证网格搜索。
划分数据、运行网络搜索并最终给出评估结果的过程如图所示:
mglearn.plots.plot_grid_search_overview()
由于带交叉验证的网格搜索是一种常用的调参方法,一次scikit-learn提供了GridSearchCV类,它以估计器(estimator)的形式实现了这种方法。要使用GridSearchCV类,首先需要用一个字典指定要搜索的参数名称(构建模型时给出,这个例子中是C和gamma),字典的值使我们想要尝试的参数设置。如果C和gamma想要尝试的取值为0.001,0.01,0.1,1,10和100,可以将其转化为下面这个字典。
param_grid = {'C': [0.001,0.01,0.1,1,10,100], 'gamma': [0.001,0.01,0.1,1,10,100]}
#将字典结果打印出来
print("Parameters grid: {}".format(param_grid))
运行结果为:
Parameters grid: {'C': [0.001, 0.01, 0.1, 1, 10, 100], 'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
先将GridSearchCV类实例化,接下来我们可以使用交叉验证来替代之前用过的划分训练集和测试集方法,仍然要将数据划分为训练集、测试集以免参数过拟合:
from sklearn.model_selection import GridSearchCV
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: {:.3f}".format(grid_search.score(x_test, y_test)))
上述代码运行后,其结果为:
Test set score: 0.974
利用交叉验证选择参数,我们实际上找到了一个在测试集精度为0.974的模型,重要的是,我们没有使用测试集来选择参数,我们找到的参数保存在best_params_属性中,而交叉验证最佳精度又保存在best_score_中。
print("Best parameters are : {}".format(grid_search.best_params_))
print("Best score is: {}".format(grid_search.best_score_))
运行后其结果为:
Best parameters are : {'C': 100, 'gamma': 0.01}
Best score is: 0.9732142857142857
这里需要注意的是,我们不能将best_scor_与模型在测试集上调用score方法计算得到的泛化性能弄混(使用score方法(或对predict方法的输出星星评估)采用的是在整个训练集上训练的模型,而best_score_属性保存的是交叉验证的平均精度,是在训练集上进行交叉验证得到的。
能够访问实际找到的模型,这个方法是很有用的,比如查看系数或特征重要性,可以用best_estimator_属性来访问对应的模型,它是在整个训练集上训练得到的。
print("Best estimator:{}".format(grid_search.best_estimator_))
打印出来的结果为:
Best estimator:
SVC(C=100, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma=0.01, kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
由于grid_search本身具有predict和score方法,所以不需要使用best_estimator_来进行评估预测模型。