机器学习模型评估与改进:网格化调参(grid search)


在交叉验证部分我们知道了如何科学地评估算法模型的泛化能力,那么我们可以进一步看看,如何对模型进行调参,以达到改进模型效果的目的。

首先,在调参之前,必须对算法参数的意义有清楚的理解。对那些重要参数“调试”是一个比较trick的任务,但却又必不可少。好在scikit learn提供了很多工具来辅助这个让人头疼的过程。其中,最常用的方法就是网格化搜索(grid search),也就是对所有可能的参数取值和不同参数取值的组合逐一尝试,最后确定最佳参数。

以SVM(RBF核)为例, 有两个非常重要的参数:

  1. kernel bandwidth : gamma
  2. 正则化参数: C

为了得到参数gamma,C的最佳设置,我们依次从[0.001, 0.001, 0.01, 0.1, 1, 10, 100]这些数字选择一个组合,作为最优的SVM参数,显然,这样的组合有6*6=36种。

简单网格化搜索

from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state = 0)
print(“Size of training set: { }   size of test set: { }.format(
    X_train.shape[0], X_test.shape[0]))
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]:
        # for each combination of parameters, train an SVC
        svm = SVC(gamma=gamma, C=C)
        svm.fit(X_train, y_train)
        # evaluate the SVC on the test set
        score = svm.score(X_test, y_test)
        # if we got better score, store it
        if score > best_score:
            best_score=score
            best_parameters = {‘C’:C, ‘gamma’:gamma}
print(‘Best score: {:.2f}.format(best_score))
print("Best parameters: {}".format(best_parameters) )

得到:

Size of training set: 112   size of test set: 38
    Best score: 0.97
    Best parameters: {'C': 100, 'gamma': 0.001}

参数过拟合的风险

上面的例子给出了“最好”的参数组合下模型在测试集合上的得分为97%, 似乎模型优化已经完成了。

其实则不然。因为这里给出的正确率仅仅是在参数网格化搜索时候用到的测试数据上模型的表现,换句话说,我们是用了一个特定的测试集合衡量模型的表现,我们不能用同一组数据来测试模型的泛化能力。也就是说,97%的表现,在新的数据集合上模型的正确率是不是还是这样呢?答案是不一定。

这里一定要非常小心,因为一不小心就会中了循环论证的圈套。比较正确的方法应该是:将数据集合分为训练集、验证集和测试集三部分。

训练集合 — 训练模型
验证集合 — 模型参数选择
测试集合. — 测试模型性能

接着上面的例子,我们用最佳的参数组合作为模型参数,在原来的训练数据和验证数据上对模型重新训练,然后在测试集合上对模型的性能进行测试。

from sklearn.svm import SVC
 # split data into train+validation set and test set 
X_trainval, X_test, y_trainval, y_test = train_test_split( iris.data, iris.target, random_state=0)
 # split train+validation set into training and validation sets X_train, X_valid, y_train, y_valid = train_test_split( 
X_trainval, y_trainval, random_state=1)
 print("Size of training set: {} size of validation set: {} size of test set:" 
" {}\n".format(X_train.shape[0], X_valid.shape[0], X_test.shape[0])) 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]: 
        # for each combination of parameters, train an SVC
        svm = SVC(gamma=gamma, C=C)
         svm.fit(X_train, y_train)
         # evaluate the SVC on the test set
         score = svm.score(X_valid, y_valid)
        # if we got a better score, store the score and parameters 
        if score > best_score:
                best_score = score
                best_parameters = {'C': C, 'gamma': gamma}

# rebuild a model on the combined training and validation set, # and evaluate it on the test set
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)) 

得到输出:

Size of training set: 84   size of validation set: 28   size of test set: 38
Best score on validation set: 0.96
Best parameters:  {'C': 10, 'gamma': 0.001}
Test set score with best parameters: 0.92

正如前面的分析, 在验证集上模型的正确率是96%,但是在测试集上,模型的正确率只有92%。也就是模型在新数据上的表现并没有验证集上那么好,泛化能力有限。在报道模型的性能时,虽然96%是比较诱人的结果,但是实事求是地说,应该是92%。

将数据划分为训练集合、验证集合和测试集合,对于机器学习模型的实际应用非常重要,特别是测试集合的数据不能“泄露”到训练和验证集合中,只有这样才能对模型的表现合理评估。

网格搜索与交叉验证

前面的例子中,在验证集合上选择得到模型的最佳参数C=10,gamma=0.001。但这个模型在测试集上的表现只有92%。如何优化网格搜索呢?

事实上,我们将全部数据拆分为训练、验证和测试三个部分的时候,我们只用了验证集合上的表现来确定参数。固定的验证集合,必然会给模型评估带来bias。这里我们可以用交叉验证来改进。

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]: 
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        # for each combination of parameters, # train an SVC
         svm = SVC(gamma=gamma, C=C)
         # perform cross-validation     
        scores = cross_val_score(svm, X_trainval, y_trainval, cv=5) 
        # compute mean cross-validation accuracy
        score = np.mean(scores)
        # if we got a better score, store the score and parameters 
        if score > best_score:
            best_score = score
            best_parameters = {'C': C, 'gamma': gamma}
        # rebuild a model on the combined training and validation set 
        svm = SVC(**best_parameters)
        svm.fit(X_trainval, y_trainval) 

模型调参接口: GridSearchCV函数

整体流程

首先数据集合应该划分成训练集、验证集和测试集,测试集一定要单独留出。
之后设置参数网格,基于交叉验证对不同参数组合下的模型进行评估。
在得到最佳参数组合后,重新训练模型,最后在对留出的测试数据对模型进行验证。
在这里插入图片描述

GridSearchCV( )函数

scikit learn提供了网格化调参的接口GridSearchCV( ), 加载model_selection模块,就可以调用该函数。

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))) 
# 最佳正确率下的模型参数,也就是param_grid中的组合
print("Best parameters: {}".format(grid_search.best_params_))
# 模型调参时的最佳正确率 
print("Best cross-validation score: {:.2f}".format(grid_search.best_score_)) 
# 最佳模型
print("Best estimator:\n{}".format(grid_search.best_estimator_)) 

上面例子中, grid_search函数内部会自动将数据分为训练数据和验证数据,所以我们只需要调用train_test_split( )函数一次,将测试数据与训练数据(其中有用来验证的数据)分开就好。

我们虽然打印了最佳模型(grid_search.best_estimator_),由于grid_search有fit( ),predict( ),score( ) 方法, 通常并不会用grid_search.best_estimator_得到的模型做后续的分析和预测,这个参数的作用仅仅是为了便于查看最佳模型的参数情况。用grid_search.fit( )在训练(包括验证)数据集上进行训练和调参后, 对于新的数据,直接用predict( )或者score( )方法就可以。

对交叉验证进一步分析

我们可以注意到上面例子中参数gamma和C的设置,以10为倍数分别尝试了从0.001到100的可能配对。由于调参是一个非常漫长耗时的过程,所以参数范围的设置非常关键。通常选择一个比较粗的网格比较好。

下面我们看看网格化调参的详细情况。接上面的例子, grid_search对象内置参数cv_results_中以字典类型纪录了不同参数组合下,交叉验证正确率情况。

import pandas as pd
# convert to DataFrame
results = pd.DataFrame(grid_search.cv_results_) # show the first 5 rows 
display(results.head())
scores = np.array(results.mean_test_score).reshape(6, 6)
# plot the mean cross-validation scores
mglearn.tools.heatmap(scores, xlabel='gamma', xticklabels=param_grid['gamma'],
                          ylabel='C', yticklabels=param_grid['C'], cmap="viridis")

在这里插入图片描述以gamma和C的取值分别作为x,y轴,以交叉验证的平均正确率作为热度值绘制热度图(heatmap),可见,参数对组合在什么情况下交叉验证正确率最高,什么组合情况下结果不好,一目了然。
在这里插入图片描述
网格化调参的时候,参数范围的选择如果不合适,那么就可能得不到很好的模型调试结果,例如下图中左图和中图,很显然参数范围设置的不好。

在这里插入图片描述

不同核方法的情况

对于SVM模型来说,内部核方法的选择非常重要。如果要同时对线性核核高斯核的参数进行搜索,该怎么办呢?

param_grid = [{'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_))

网格化搜索中应用其他交叉验证策略

上面例子中提到的cross_val_score, GridSearchCV 都是采用了分层的k折交叉验证方法。其他的交叉验证方法也可以,例如可以用n_iter=1的ShuffleSplit或者StratifiedShuffleSplit方法。

嵌套交叉验证

上文中提到, 在网格化调参搜索之前,需要将数据分为训练集合和测试集合,此时测试集合的选择对模型性能的评估至关重要,由于测试集合生成的随机性和唯一性,会使结果稳定性变差。因此,我们可以考虑在网格化搜索的时候就采用交叉验证的方式。

from sklearn.model_selection import GridSearchCV 
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score
 param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100],
                  'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
scores = cross_val_score(GridSearchCV(SVC(), param_grid, cv=5),
                             iris.data, iris.target, cv=5)
print("Cross-validation scores: ", scores) 
print("Mean cross-validation score: ", scores.mean()) 

由于调用了cross_val_score( )函数, 那么此时scores就是这一系列交叉验证上的正确率,scores.mean( ) 得到的就是交叉验证正确率的平均值。上面的例子中,内部循环和外部循环都用了5折交叉验证,意味着整个运算要训练3655=900个SVC模型。

并行化

并行化总是一个好主意,在GridSearchCV和cross_val_score函数中,通过设置 n_jobs 可以实现并行的目的, 还可以将n_jobs=-1 ,自动在所有可利用的CPU核上并行。

总结

网格化搜索调参,scikit learn提供了很好的工具:from sklearn.model_selection import GridSearchCV

具体来说, 首先给定参数网格。

param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100],
                  'gamma': [0.001, 0.01, 0.1, 1, 10, 100]}

或者

   param_grid = [{'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]}]

之后对数据划分,分为训练集合和测试集合。加载train_test_split( )函数。
from sklearn.model_selection import train_test_split
第三步,实施网格化搜索

from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(SVC(), param_grid, cv=5)
grid_search.fit(train_X, train_y)
grid_search.score(test_X, test_y)

如果想要嵌套交叉验证, 那么在GridSearchCV 外面在嵌套一层 cross_val_score:

scores = cross_val_score(GridSearchCV(SVC(), param_grid, cv=5),
                             iris.data, iris.target, cv=5)

附注:mglearn工具包

github的下载地址: https://github.com/amueller/introduction_to_ml_with_python
使用:

import mglearn
mglearn.plots.plot_cross_validation()

mglearn工具包介绍: https://blog.csdn.net/az9996/article/details/86490496

发布了111 篇原创文章 · 获赞 118 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/happyhorizion/article/details/89529651