sklearn基础篇(八)-- 网格搜索与随机搜索

        网格搜索适用于三四个(或者更少)的超参数(当超参数的数量增长时,网格搜索的计算复杂度会呈现指数增长,这时候则使用随机搜索),用户列出一个较小的超参数值域,这些超参数至于的笛卡尔积(排列组合)为一组组超参数。网格搜索算法使用每组超参数训练模型并挑选验证集误差最小的超参数组合。

        考虑一个具有RBF(径向基函数)核的核SVM的例子,它在SVC类中实现。它有2个重要参数:核宽度gamma和正则化参数C。假设我们希望尝试C的取值为0.001、0.01、0.1、1、10和100,gamma也取这6个值。由于我想要尝试的C和gamma都有6个不同的取值,所以总共有36种参数组合。所有可能的组合组成了SVM的参数设置表(网格),如下所示。

sklearn基础篇(八)-- 网格搜索与随机搜索_第1张图片

1 简单的网格搜索

        我们可以实现一个简单的网格搜索,在2个参数上使用for循环,对每种参数组合分别训练并评估一个分类器:

import mglearn
from sklearn.datasets import load_iris
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC 

iris = load_iris()
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]:
        # 对每种参数组合训练一个SVC
        svm = SVC(gamma=gamma, C=C)
        svm.fit(X_train, y_train)
        # 在测试集上评估SVC
        score = svm.score(X_test, y_test)
        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}


2 参数过拟合的风险与验证集

        由上面的结果,我们可能会想我们找到了一个在数据集上精度达到97%的模型。然而,这种说法可能过于乐观了(或者就是错的),其原因如下:我们尝试了许多不同的参数,并选择了在测试集上精度最高的那个,但这个精度不一定能推广到新数据上。由于我们使用测试数据进行调参,所以不能再用它来评估模型的好坏。 我们最开始需要将数据划分为训练集和测试集也是因为这个原因。我们需要一个独立的数据集来进行评估,一个在创建模型时没有用到的数据集。

        为了解决这个问题,一种方法是再次划分数据,这样我们得到3个数据集:用于构建模型的训练集,用于选择模型参数的验证集(开发集),用于评估所选参数性能的测试集。下图给出了这3个集合的图示:

mglearn.plots.plot_threefold_split()

        利用验证集选定最佳参数之后,我们可以利用找到的参数设置重新构建一个模型,但是要同时在训练数据和验证数据上进行训练。这样我们可以利用尽可能多的数据来构建模型。其实现如下所示:

# 将数据划分为训练+验证集与测试集
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)
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]:
        # 对每种参数组合都训练一个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))

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


3 带交叉验证的网格搜索

        虽然将数据划分为训练集、验证集和测试集的方法(如上所述)是可行的,也相对常用,但这种方法对数据的划分方法相当敏感。从上面代码片段的输出中可以看出,网格搜索选择'C': 10, 'gamma': 0.001作为最佳参数,而上一节的代码输出选择'C': 100, 'gamma': 0.001作为最佳参数。为了得到对泛化性能的更好估计,我们可以使用交叉验证来评估每种参数组合的性能,而不是仅将数据单次划分为训练集与验证集。这种方法用代码表示如下:

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]:
        svm = SVC(gamma=gamma, C=C)
        scores = cross_val_score(svm, X_trainval, y_trainval, cv=5)
        score = np.mean(scores)
        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))

Best score on validation set: 0.97
Best parameters: {‘C’: 10, ‘gamma’: 0.1}
Test set score with best parameters: 0.97

        要想使用5折交叉验证对C和gamma特定取值的SVM的精度进行评估,需要训练36×5=180个模型。你可以想象,使用交叉验证的主要缺点就是训练所有这些模型所需花费的时间。

        下图说明了上述代码如何选择最佳参数设置:
sklearn基础篇(八)-- 网格搜索与随机搜索_第2张图片

        对于每种参数设置(图中仅显示了一部分),需要计算5个精度值,交叉验证的每次划分都要计算一个精度值。然后,对每种参数设置计算平均验证精度。最后,选择平均验证精度最高的参数,用圆圈标记。

        划分数据、运行网格搜索并评估最终参数,这整个过程如下图所示。
sklearn基础篇(八)-- 网格搜索与随机搜索_第3张图片

        由于带交叉验证的网格搜索是一种常用的调参方法,因此scikit-learn提供了GridSearchCV类,它以估计器(estimator)的形式实现了这种方法。


4 GridSearchCV——网格搜索

        GridSearchCV的名字其实可以拆分为两部分,GridSearch和CV,即网格搜索和交叉验证。这两个名字都非常好理解。网格搜索,搜索的是参数,即在指定的参数范围内,按步长依次调整参数,利用调整的参数训练学习器,从所有的参数中找到在验证集上精度最高的参数,这其实是一个训练和比较的过程。

        GridSearchCV,它存在的意义就是自动调参,只要把参数输进去,就能给出最优化结果和参数。但是这个方法适合于小数据集,一旦数据的量级上去了,很难得到结果。这个时候就需要动脑筋了。数据量比较大的时候可以使用一个快速调优的方法——坐标下降。它其实是一种贪心算法:拿当前对模型影响最大的参数调参,直到最优化;再拿下一个影响最大的参数调优,如此下去,直到所有的参数调整完毕。这个方法的缺点就是可能会跳到局部最优而不是全局最优,但是省时间省力,巨大的优势面前,还是试一试,后续可以再拿bagging再优化。

        Grid Search:一种调参手段;穷举搜索:在所有候选的参数选择中,通过循环遍历,尝试每一种可能性,表现最好的参数就是最终的结果。其原理就像是在数组里找到最大值。GridSearchCV可以保证在指定的参数范围内找到精度最高的参数,但是这也是网格搜索的缺陷所在,他要求遍历所有可能参数的组合,在面对大数据集和多参数的情况下,这种方法的主要缺点是比较耗时!

        要使用GridSearchCV类,你首先需要用一个字典指定要搜索的参数。然后GridSearchCV会执行所有必要的模型拟合。字典的键是我们要调节的参数名称(在构建模型时给出,在这个例子中是C和gamma),字典的值是我们想要尝试的参数设置。如果C和gamma想要尝试的取值为0.001、0.01、0.1、1、10和100,可以将其转化为下面这个字典:

from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import KFold

kfold = KFold(n_splits=5, shuffle=True, random_state=10)
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=kfold)

        创建的grid_search对象的行为就像是一个分类器,我们可以对它调用标准的fit、predict和score方法。但我们在调用fit时,它会对param_grid指定的每种参数组合都运行交叉验证:

grid_search.fit(X_train, y_train)

        GridSearchCV类提供了一个非常方便的接口,可以用predict和score方法来访问重新训练过的模型。为了评估找到的最佳参数的泛化能力,我们可以在测试集上调用score:

print("Test set score: {:.2f}".format(grid_search.score(X_test, y_test)))

        利用交叉验证选择参数,我们实际上找到了一个在测试集上精度为97% 的模型。重要的是,我们没有使用测试集来选择参数。我们找到的参数保存在best_params_属性中,而交叉验证最佳精度(对于这种参数设置,不同划分的平均精度)保存在best_score_中:

print("Best parameters: {}".format(grid_search.best_params_))
print("Best cross-validation score: {:.2f}".format(grid_search.best_score_))

        能够访问实际找到的模型,这有时是很有帮助的,比如查看系数或特征重要性。你可以用best_estimator_属性来访问最佳参数对应的模型,它是在整个训练集上训练得到的:

print("Best estimator:\n{}".format(grid_search.best_estimator_))
# Best estimator: SVC(C=10, gamma=0.1)

        由于grid_search本身具有predict和score方法,所以不需要使用best_estimator_来进行预测或评估模型。

4.1 分析交叉验证的结果

        将交叉验证的结果可视化通常有助于理解模型泛化能力对所搜索参数的依赖关系。由于运行网格搜索的计算成本相当高,所以通常最好从相对比较稀疏且较小的网格开始搜索。然后我们可以检查交叉验证网格搜索的结果,可能也会扩展搜索范围。网格搜索的结果可以在cv_results_ 属性中找到,它是一个字典,其中保存了搜索的所有内容。你可以在下面的输出中看到,它里面包含许多细节,最好将其转换成pandas 数据框后再查看:

import pandas as pd
# 转换为DataFrame(数据框)
results = pd.DataFrame(grid_search.cv_results_)
# 显示前5行
display(results.head())
sklearn基础篇(八)-- 网格搜索与随机搜索_第4张图片

        results 中每一行对应一种特定的参数设置。对于每种参数设置,交叉验证所有划分的结果都被记录下来,所有划分的平均值和标准差也被记录下来。由于我们搜索的是一个二维参数网格(C和gamma),所以最适合用热图可视化。我们首先提取平均验证分数,然后改变分数数组的形状,使其坐标轴分别对应于C和gamma:

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")
sklearn基础篇(八)-- 网格搜索与随机搜索_第5张图片

        热图中的每个点对应于运行一次交叉验证以及一种特定的参数设置。颜色表示交叉验证的精度:浅色表示高精度,深色表示低精度。你可以看到,SVC 对参数设置非常敏感。对于许多种参数设置,精度都在40% 左右,这是非常糟糕的;对于其他参数设置,精度约为96%。我们可以从这张图中看出以下几点。首先,我们调节的参数对于获得良好的性能非常重要。这两个参数(C 和gamma)都很重要,因为调节它们可以将精度从40%提高到96%。此外,在我们选择的参数范围中也可以看到输出发生了显著的变化。同样重要的是要注意,参数的范围要足够大:每个参数的最佳取值不能位于图像的边界上。

        下面我们来看几张图,其结果不那么理想,因为选择的搜索范围不合适。

sklearn基础篇(八)-- 网格搜索与随机搜索_第6张图片
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', ylabel='C', xticklabels=param_grid['gamma'],
        yticklabels=param_grid['C'], cmap="viridis", ax=ax)

plt.colorbar(scores_image, ax=axes.tolist())

        第一张图没有显示任何变化,整个参数网格的颜色相同。在这种情况下,这是由参数C和gamma不正确的缩放以及不正确的范围造成的。但如果对于不同的参数设置都看不到精度的变化,也可能是因为这个参数根本不重要。通常最好在开始时尝试非常极端的值,以观察改变参数是否会导致精度发生变化。

        第二张图显示的是垂直条形模式。这表示只有gamma的设置对精度有影响。这可能意味着gamma参数的搜索范围是我们所关心的,而C参数并不是——也可能意味着C 参数并不重要。

        第三张图中C和gamma对应的精度都有变化。但可以看到,在图像的整个左下角都没有发生什么有趣的事情。我们在后面的网格搜索中可以不考虑非常小的值。最佳参数设置出现在右上角。由于最佳参数位于图像的边界,所以我们可以认为,在这个边界之外可能还有更好的取值,我们可能希望改变搜索范围以包含这一区域内的更多参数。

        基于交叉验证分数来调节参数网格是非常好的,也是探索不同参数的重要性的好方法。

4.2 在非网格的空间中搜索

        在某些情况下,尝试所有参数的所有可能组合(正如GridSearchCV所做的那样)并不是一个好主意。例如,SVC有一个kernel参数,根据所选择的kernel(内核),其他参数也是与之相关的。如果kernel=‘linear’,那么模型是线性的,只会用到C参数。如果kernel=‘rbf’,则需要使用C和gamma两个参数(但用不到类似degree的其他参数)。在这种情况下,搜索C、gamma和kernel所有可能的组合没有意义:如果kernel=‘linear’,那么gamma 是用不到的,尝试gamma的不同取值将会浪费时间。为了处理这种“ 条件”(conditional) 参数,GridSearchCV 的param_grid 可以是字典组成的列表(a list of dictionaries)。列表中的每个字典可扩展为一个独立的网格。包含内核与参数的网格搜索可能如下所示。

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=kfold)
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_))

Best parameters: {‘C’: 10, ‘gamma’: 0.1, ‘kernel’: ‘rbf’}
Best cross-validation score: 0.99

        再次查看cv_results_。正如所料,如果kernel等于’linear’,那么只有C 是变化的:

results = pd.DataFrame(grid_search.cv_results_)
# 我们给出的是转置后的表格,这样更适合页面显示:
display(results.T)
sklearn基础篇(八)-- 网格搜索与随机搜索_第7张图片

4.3 使用不同的交叉验证策略进行网格搜索

1. 嵌套交叉验证
        在前面的例子中,我们先介绍了将数据单次划分为训练集、验证集与测试集,然后介绍了先将数据划分为训练集和测试集,再在训练集上进行交叉验证。但前面在使用GridSearchCV 时,我们仍然将数据单次划分为训练集和测试集,这可能会导致结果不稳定,也让我们过于依赖数据的此次划分。我们可以再深入一点,不是只将原始数据一次划分为训练集和测试集,而是使用交叉验证进行多次划分,这就是所谓的嵌套交叉验证(nested cross-validation)。在嵌套交叉验证中,有一个外层循环,遍历将数据划分为训练集和测试集的所有划分。对于每种划分都运行一次网格搜索(对于外层循环的每种划分可能会得到不同的最佳参数)。然后,对于每种外层划分,利用最佳参数设置计算得到测试集分数。

        这一过程的结果是由分数组成的列表——不是一个模型,也不是一种参数设置。这些分数告诉我们在网格找到的最佳参数下模型的泛化能力好坏。由于嵌套交叉验证不提供可用于新数据的模型,所以在寻找可用于未来数据的预测模型时很少用到它。但是,它对于评估给定模型在特定数据集上的效果很有用。

        在scikit-learn 中实现嵌套交叉验证很简单。我们调用cross_val_score,并用GridSearchCV的一个实例作为模型:

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-validation scores: [0.96666667 1. 0.9 0.96666667 1. ]
Mean cross-validation score: 0.9666666666666668

        嵌套交叉验证的结果可以总结为“SVC 在iris数据集上的交叉验证平均精度为97%”——不多也不少。

        这里我们在内层循环和外层循环中都使用了分层5 折交叉验证。由于param_grid包含36种参数组合,所以需要构建36×5×5 = 900 个模型,导致嵌套交叉验证过程的代价很高。这里我们在内层循环和外层循环中使用相同的交叉验证分离器,但这不是必需的,你可以在内层循环和外层循环中使用交叉验证策略的任意组合。理解上面单行代码的内容可能有点困难,将其展开为for 循环可能会有所帮助,正如我们在下面这个简化的实现中所做的那样:

def nested_cv(X, y, inner_cv, outer_cv, Classifier, parameter_grid):
    outer_scores = []
    # 对于外层交叉验证的每次数据划分,split方法返回索引值
    for training_samples, test_samples in outer_cv.split(X, y):
        # 利用内层交叉验证找到最佳参数
        best_parms = {}
        best_score = -np.inf
        # 遍历参数
        for parameters in parameter_grid:
            # 在内层划分中累加分数
            cv_scores = []
            # 遍历内层交叉验证
            for inner_train, inner_test in inner_cv.split(X[training_samples], y[training_samples]):
                # 对于给定的参数和训练数据来构建分类器
                clf = Classifier(**parameters)
                clf.fit(X[inner_train], y[inner_train])
                # 在内层测试集上进行评估
                score = clf.score(X[inner_test], y[inner_test])
                cv_scores.append(score)
            # 计算内层交叉验证的平均分数
            mean_score = np.mean(cv_scores)
            if mean_score > best_score:
                # 如果比前面的模型都要好,则保存其参数
                best_score = mean_score
                best_params = parameters
        # 利用外层训练集和最佳参数来构建模型
        clf = Classifier(**best_params)
        clf.fit(X[training_samples], y[training_samples])
        # 评估模型
        outer_scores.append(clf.score(X[test_samples], y[test_samples]))
    return np.array(outer_scores)

        下面我们在iris 数据集上运行这个函数:

from sklearn.model_selection import ParameterGrid, StratifiedKFold
scores = nested_cv(iris.data, iris.target, StratifiedKFold(5),
StratifiedKFold(5), SVC, ParameterGrid(param_grid))
print("Cross-validation scores: {}".format(scores))

Cross-validation scores: [0.96666667 1. 0.96666667 0.96666667 1. ]

2. 交叉验证与网格搜索并行

        虽然在许多参数上运行网格搜索和在大型数据集上运行网格搜索的计算量可能很大,但令人尴尬的是,这些计算都是并行的(parallel)。这也就是说,在一种交叉验证划分下使用特定参数设置来构建一个模型,与利用其他参数的模型是完全独立的。这使得网格搜索与交叉验证成为多个CPU 内核或集群上并行化的理想选择。你可以将n_jobs参数设置为你想使用的CPU 内核数量,从而在GridSearchCV 和cross_val_score中使用多个内核。你可以设置n_jobs=-1 来使用所有可用的内核。

代码如下:

#1.使用单线程对文本分类的朴素贝叶斯模型的超参数组合执行网格搜索
 
from sklearn.datasets import fetch_20newsgroups
import numpy as np
news = fetch_20newsgroups(subset='all')
from sklearn.cross_validation import train_test_split
#取前3000条新闻文本进行数据分割
X_train,X_test,y_train,y_test=train_test_split(news.data[:3000],
                                            news.target[:3000],test_size=0.25,random_state=33)
 
 
from sklearn.svm import SVC
from sklearn.feature_extraction.text import TfidfVectorizer
#*************导入pipeline*************
from sklearn.pipeline import Pipeline
#使用Pipeline简化系统搭建流程,sklean提供的pipeline来将多个学习器组成流水线,通常流水线的形式为:
#将数据标准化的学习器---特征提取的学习器---执行预测的学习器
#将文本特征与分类器模型串联起来,[(),()]里有两个参数
#参数1:执行 vect = TfidfVectorizer(stop_words='english',analyzer='word')操作
#参数2:执行 svc = SVC()操作
clf = Pipeline([('vect',TfidfVectorizer(stop_words='english',analyzer='word')),('svc',SVC())])
 
#这里需要试验的2个超参数svc_gamma和svc_C的元素个数分别为4、3,这样我们一共有12种超参数对集合
#numpy.linspace用于创建等差数列,numpy.logspace用于创建等比数列
#logspace中,开始点和结束点是10的幂
#例如logspace(-2,1,4)表示起始数字为10^-2,结尾数字为10^1即10,元素个数为4的等比数列
#parameters变量里面的key都有一个前缀,这个前缀其实就是在Pipeline中定义的操作名。二者相结合,使我们的代码变得十分简洁。
#还有注意的是,这里对参数名是<两条>下划线 __
parameters = {'svc__gamma':np.logspace(-2,1,4),'svc__C':np.logspace(-1,1,3)}
 
#从sklearn.grid_search中导入网格搜索模块GridSearchCV
from sklearn.grid_search import GridSearchCV
#GridSearchCV参数解释:
#1.estimator : estimator(评估) object.
#2.param_grid : dict or list of dictionaries
#3.verbose:Controls the verbosity(冗余度): the higher, the more messages.
#4.refit:default=True, Refit(再次拟合)the best estimator with the entire dataset
#5.cv : int, cross-validation generator 此处表示3折交叉验证
gs = GridSearchCV(clf,parameters,verbose=2,refit=True,cv=3)
 
#执行单线程网格搜索
gs.fit(X_train,y_train)
 
print gs.best_params_,gs.best_score_
 
#最后输出最佳模型在测试集上的准确性
print 'the accuracy of best model in test set is',gs.score(X_test,y_test)
 
#小结:
#1.由输出结果可知,使用单线程的网格搜索技术 对朴素贝叶斯模型在文本分类任务中的超参数组合进行调优,
#  共有12组超参数组合*3折交叉验证 =36项独立运行的计算任务
#2.在本机上,该过程一共运行了2.9min,寻找到最佳的超参数组合在测试集上达到的分类准确性为82.27%
#2.使用多线程对文本分类的朴素贝叶斯模型的超参数组合执行网格搜索
 
#n_jobs=-1,表示使用该计算机的全部cpu
gs = GridSearchCV(clf,parameters,verbose=2,refit=True,cv=3,n_jobs=-1)
gs.fit(X_train,y_train)
print gs.best_params_,gs.best_score_
#输出最佳模型在测试集上的准确性
print 'the accuracy of best model in test set is',gs.score(X_test,y_test)
 
#小结:
#总任务相同的情况下,使用并行搜索技术进行计算的话,执行时间只花费了1.1min;
#而且最终所得的的best_params_和score没有发生变化,说明并行搜索可以在不影响准确性的前提下,
#有效的利用计算机的CPU资源,大大节省了最佳超参数的搜索时间。

         温馨提示: scikit-learn 不允许并行操作的嵌套。因此,如果你在模型(比如随机森林)中使用了n_jobs 选项,那么就不能在GridSearchCV使用它来搜索这个模型。如果你的数据集和模型都非常大,那么使用多个内核可能会占用大量内存,你应该在并行构建大型模型时监视内存的使用情况。

4.4 GridSearch库小结

1. 参数说明

class sklearn.model_selection.GridSearchCV(estimator, param_grid, scoring=None,
fit_params=None, n_jobs=None, iid=’warn’, refit=True, cv=’warn’, verbose=0,
pre_dispatch=2*n_jobs’, error_score=raise-deprecating’, return_train_score=’warn’)

参数如下:

  • 1)estimator:选择使用的分类器,并且传入除需要确定最佳的参数之外的其他参数。每一个分类器都需要一个scoring参数,或者score方法:如estimator = RandomForestClassifier(min_sample_split=100,min_samples_leaf = 20,max_depth = 8,max_features = ‘sqrt’ , random_state =10),

  • 2)param_grid:需要最优化的参数的取值,值为字典或者列表,例如:param_grid = param_test1,param_test1 = {‘n_estimators’ : range(10,71,10)}

  • 3)scoring = None :模型评价标准,默认为None,这时需要使用score函数;或者如scoring = ‘roc_auc’,根据所选模型不同,评价准则不同,字符串(函数名),或是可调用对象,需要其函数签名,形如:scorer(estimator,X,y);如果是None,则使用estimator的误差估计函数。

  • 4)n_jobs = 1 : n_jobs:并行数,int:个数,-1:跟CPU核数一致,1:默认值

  • 5)refit = True :默认为True,程序将会以交叉验证训练集得到的最佳参数,重新对所有可能的训练集与开发集进行,作为最终用于性能评估的最佳模型参数。即在搜索参数结束后,用最佳参数结果再次fit一遍全部数据集。

  • 6)cv = None:交叉验证参数,默认None,使用三折交叉验证。指定fold数量,默认为3,也可以是yield训练/测试数据的生成器。

  • 7)verbose = 0 ,scoring = None  verbose:日志冗长度,int:冗长度,0:不输出训练过程,1:偶尔输出,>1:对每个子模型都输出。

  • 8)pre_dispatch = ‘2*n_jobs’ :指定总共发的并行任务数,当n_jobs大于1时候,数据将在每个运行点进行复制,这可能导致OOM,而设置pre_dispatch参数,则可以预先划分总共的job数量,使数据最多被复制pre_dispatch次。

2. 进行预测的常用方法和属性

  • grid.fit() :运行网格搜索
  • grid_scores_ :给出不同参数情况下的评价结果(已弃用)
  • best_params_ :描述了已取得最佳结果的参数的组合
  • best_score_ :提供优化过程期间观察到的最好的评分
  • cv_results_ :具体用法模型不同参数下交叉验证的结果

3. GridSearchCV属性说明

(1) cv_results_ : dict of numpy (masked) ndarrays

         具有键作为列标题和值作为列的dict,可以导入到DataFrame中。注意,“params”键用于存储所有参数候选项的参数设置列表。

(2) best_estimator_ : estimator

         通过搜索选择的估计器,即在左侧数据上给出最高分数(或指定的最小损失)的估计器。如果refit = False,则不可用。

(3)best_score_ :float best_estimator的分数

(4)best_parmas_ : dict 在保存数据上给出最佳结果的参数设置

(5) best_index_ : int 对应于最佳候选参数设置的索引(cv_results_数组)

search.cv_results _ [‘params’] [search.best_index_]中的dict给出了最佳模型的参数设置,给出了最高的平均分数(search.best_score_)。

(6)scorer_ : function
         用计分器函数对待测数据进行模型参数选择。

(7)n_splits_ : int

         交叉验证分裂的次数(折叠/迭代)


5 RandomizedSearchCV——(随机搜索)

        我们在搜索超参数的时候,如果超参数个数较少(三四个或者更少),那么我们可以采用网格搜索,一种穷尽式的搜索方法。但是当超参数个数比较多的时候,我们仍然采用网格搜索,那么搜索所需时间将会指数级上升。

        所以有人就提出了随机搜索的方法,随机在超参数空间中搜索几十几百个点,其中就有可能有比较小的值。这种做法比上面稀疏化网格的做法快,而且实验证明,随机搜索法结果比稀疏网格法稍好。

        RandomizedSearchCV使用方法和类GridSearchCV很相似,但他不是尝试所有可能的组合,而是通过选择每一个超参数的一个随机值的特定数量的随机组合,这个方法有两个优点:

  • 如果你让随机搜索运行, 比如1000次,它会探索每个超参数的1000个不同的值(而不是像网格搜索那样,只搜索每个超参数的几个值)
  • 可以方便的通过设定搜索次数,控制超参数搜索的计算量。

        RandomizedSearchCV的使用方法其实是和GridSearchCV一致的,但它以随机在参数空间中采样的方式代替了GridSearchCV对于参数的网格搜索,在对于有连续变量的参数时,RandomizedSearchCV会将其当做一个分布进行采样进行这是网格搜索做不到的,它的搜索能力取决于设定的n_iter参数,同样的给出代码

sklearn基础篇(八)-- 网格搜索与随机搜索_第8张图片
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform

iris = load_iris()

logistic = LogisticRegression(solver='saga', tol=1e-2, max_iter=200, random_state=0)
# distributions字典类型,放入参数搜索范围
distributions = dict(C=uniform(loc=0, scale=4), penalty=['l2', 'l1'])
clf = RandomizedSearchCV(logistic, distributions, random_state=0)
search = clf.fit(iris.data, iris.target)
print(search.best_estimator_)
print(search.best_score_)

LogisticRegression(C=2.195254015709299, max_iter=200, penalty=‘l1’, random_state=0, solver=‘saga’, tol=0.01)
0.98

        下面比较随机森林超参数优化的随机搜索和网格搜索。所有影响学习的参数都是同时搜索的(除了估计值的数量,它会造成时间/质量的权衡)。

        随机搜索和网格搜索探索的是完全相同的参数空间。参数设置的结果非常相似,而随机搜索的运行时间要低的多。

        随机搜索的性能稍差,不过这很可能是噪声效应,不会延续到外置测试集

        注意:在实践中,人们不会使用网格搜索同时搜索这么多不同的参数,而是只选择那些被认为最重要的参数

代码如下:

import numpy as np
from time import time
from scipy.stats import randint as sp_randint
 
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import RandomizedSearchCV
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
 
# get some  data
digits = load_digits()
X, y = digits.data , digits.target
 
# build a classifier
clf = RandomForestClassifier(n_estimators=20)
 
# utility function to report best scores
def report(results, n_top= 3):
    for i in range(1, n_top + 1):
        candidates = np.flatnonzero(results['rank_test_score'] == i)
        for candidate in candidates:
            print("Model with rank:{0}".format(i))
            print("Mean validation score : {0:.3f} (std: {1:.3f})".
                  format(results['mean_test_score'][candidate],
                         results['std_test_score'][candidate]))
            print("Parameters: {0}".format(results['params'][candidate]))
            print("")
 
# 指定取样的参数和分布 specify parameters and distributions to sample from
param_dist = {"max_depth":[3,None],
              "max_features":sp_randint(1,11),
              "min_samples_split":sp_randint(2,11),
              "bootstrap":[True, False],
              "criterion":["gini","entropy"]
              }
 
# run randomized search
n_iter_search = 20
random_search = RandomizedSearchCV(clf,param_distributions=param_dist,
                                   n_iter=n_iter_search,cv =5)
start = time()
random_search.fit(X, y)
print("RandomizedSearchCV took %.2f seconds for %d candidates"
      " parameter settings." % ((time() - start), n_iter_search))
report(random_search.cv_results_)
 
# use a full grid over all parameters
param_grid = {"max_depth":[3,None],
              "max_features":[1, 3, 10],
              "min_samples_split":[2, 3, 10],
              "bootstrap":[True, False],
              "criterion":["gini","entropy"]
    }
# run grid search
grid_search = GridSearchCV(clf, param_grid=param_grid, cv =5)
start = time()
grid_search.fit(X , y)
print("GridSearchCV took %.2f seconds for %d candidate parameter settings."
      % (time() - start, len(grid_search.cv_results_['params'])))
report(grid_search.cv_results_)

RandomizedSearchCV took 3.96 seconds for 20 candidates parameter settings.
Model with rank:1
Mean validation score : 0.927 (std: 0.027)
Parameters: {‘bootstrap’: False, ‘criterion’: ‘entropy’, ‘max_depth’: None, ‘max_features’: 10, ‘min_samples_split’: 2}

Model with rank:2
Mean validation score : 0.925 (std: 0.025)
Parameters: {‘bootstrap’: True, ‘criterion’: ‘entropy’, ‘max_depth’: None, ‘max_features’: 7, ‘min_samples_split’: 3}

Model with rank:2
Mean validation score : 0.925 (std: 0.027)
Parameters: {‘bootstrap’: False, ‘criterion’: ‘entropy’, ‘max_depth’: None, ‘max_features’: 10, ‘min_samples_split’: 9}

GridSearchCV took 12.15 seconds for 72 candidate parameter settings.
Model with rank:1
Mean validation score : 0.932 (std: 0.021)
Parameters: {‘bootstrap’: False, ‘criterion’: ‘gini’, ‘max_depth’: None, ‘max_features’: 3, ‘min_samples_split’: 2}

Model with rank:2
Mean validation score : 0.931 (std: 0.022)
Parameters: {‘bootstrap’: False, ‘criterion’: ‘entropy’, ‘max_depth’: None, ‘max_features’: 10, ‘min_samples_split’: 3}

Model with rank:3
Mean validation score : 0.931 (std: 0.022)
Parameters: {‘bootstrap’: True, ‘criterion’: ‘gini’, ‘max_depth’: None, ‘max_features’: 10, ‘min_samples_split’: 2}

        建议使用随机搜索。


参考

  • Grid SearchCV(网格搜索):https://www.cnblogs.com/wj-1314/p/10422159.html
  • Tuning the hyper-parameters of an estimator:https://scikit-learn.org/stable/modules/grid_search.html
  • sklearn.model_selection.GridSearchCV:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html#sklearn.model_selection.GridSearchCV
  • sklearn.model_selection.RandomizedSearchCV:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html#sklearn.model_selection.RandomizedSearchCV

你可能感兴趣的:(Machine,Learning,学习框架,sklearn,网格搜索,GridSearchCV,随机搜索)