用Python进行超参数调整和优化的指南

486 阅读14分钟

伴随着这篇文章的代码可以在订阅后收到

*表示需要

电子邮件地址*

在之前的几篇文章中,我们特别关注了机器学习算法的 性能.我们谈到了如何量化机器学习模型的性能,以及如何用**正则化来改善它。除此以外,我们还涵盖了主题优化技术,包括梯度下降等基本技术和亚当等高级技术**。

围绕着模型优化的概念,完全不同的子分支是如何发展起来的,这是相当超现实的。其中一个子分支是超参数优化或超参数调谐。

Ultimate Guide to Machine Learning with Python

这捆电子书是专门为初学者制作的。从Python基础知识到机器学习算法在生产中的部署,一切都在一个地方。今天就成为机器学习的超级英雄吧!

在这篇文章中,你可以找到。

  1. 机器学习中的超参数
  2. 先决条件和数据
  3. 网格搜索超参数调优
  4. 随机搜索超参数调控
  5. 贝叶斯超参数优化
  6. 网格搜索和随机搜索的减半
  7. 替代方案

1.机器学习中的超参数

超参数是每个机器学习和深度学习算法的一个组成部分。与标准的机器学习参数不同,这些参数是由算法本身学习的(比如线性回归中的w和b,或者神经网络中的连接权重),超参数是由工程师训练过程中设置的。

它们是控制学习算法行为的一个外部因素,完全由工程师定义。你需要一些例子吗?学习率是最著名的超参数之一,SVM中的C也是一个超参数,决策树的最大深度是一个超参数,等等。这些都可以由工程师手动设置。

AI Visual

然而,如果我们想运行多个测试,这可能是令人厌烦的。这就是我们使用超参数优化的地方。这些技术的主要目标是找到一个给定的机器学习算法的超参数,以提供在验证集上测量的最佳性能。在本教程中,我们将探讨几种可以给你提供最佳超参数的技术。

2.先决条件和数据

2.1 先决条件和库

为了本文的目的,请确保你已经安装了以下Python 库。

  • NumPy - 关注 本指南如果你需要安装方面的帮助。
  • SciKit Learn - 关注 本指南如果你在安装方面需要帮助,请关注。
  • SciPy - 关注 本指南如果你需要安装方面的帮助。
  • Sci-Kit Optimization- 关注 本指南如果你需要安装方面的帮助,请关注。

一旦安装,确保你已经导入了本教程中使用的所有必要模块。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt


from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score

from sklearn.model_selection import GridSearchCV, RandomizedSearchCV

from sklearn.experimental import enable_halving_search_cv
from sklearn.model_selection import HalvingGridSearchCV, HalvingRandomSearchCV

from sklearn.svm import SVC
from sklearn.ensemble import RandomForestRegressor

from scipy import stats
from skopt import BayesSearchCV
from skopt.space import Real, Categorical

除此以外,至少要熟悉以下的基础知识才行 线性代数, 微积分概率.

2.2 准备数据

我们在这篇文章中使用的数据来自PalmerPenguins数据集。这个数据集最近被引入,作为著名的Iris数据集的替代品。它是由Kristen Gorman博士和南极洲LTER的Palmer站创建的。你可以获得这个数据集 这里,或通过Kaggle。

这个数据集基本上由两个数据集组成,每个数据集包含344只企鹅的数据。就像在Iris数据集中,有3个不同种类的企鹅来自帕尔默群岛的3个岛屿。此外,这些数据集还包含每个物种的culmen尺寸。喙是鸟类喙的上脊。在简化的企鹅数据中, culmen的长度和深度被重新命名为变量culmen_length_mmculmen_depth_mm

由于这个数据集是有标签的,我们将能够验证我们的实验结果。然而,情况往往不是这样的,聚类算法结果的验证往往是一个艰难而复杂的过程。

让我们加载和准备PalmerPenguins数据集。首先,我们加载数据集,删除我们在本文中不使用的特征。

data = pd.read_csv('./data/penguins_size.csv')

data = data.dropna()
data = data.drop(['sex', 'island', 'flipper_length_mm', 'body_mass_g'], axis=1)

然后,我们把输入的数据分开,并对其进行缩放。

X = data.drop(['species'], axis=1)

ss = StandardScaler()
X = ss.fit_transform(X) 

y = data['species']
spicies = {'Adelie': 0, 'Chinstrap': 1, 'Gentoo': 2}
y = [spicies[item] for item in y]
y = np.array(y) 

最后,我们把数据分成训练和测试数据集。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=33)

当我们绘制数据时,这里是它的样子。

3.网格搜索超参数调整

手动调整超参数是缓慢而令人厌烦的。这就是为什么我们探索第一个也是最简单的超参数优化技术--网格搜索。这种技术正在加速这一过程,它是最常用的超参数优化技术之一。就其本质而言,它使试错过程自动化。对于这种技术,我们提供一个所有超参数值的列表,该算法为每个可能的组合建立一个模型,对其进行评估,并选择提供最佳结果的值。它是一种通用技术,可以应用于任何模型。

在我们的例子中,我们使用 用于分类的SVM算法.有三个超参数是我们要考虑的--Cgammakernel。要更详细地了解它们,请查看 这篇文章.对于C,我们要检查以下数值。0.1, 1, 100, 1000;对于gamma,我们使用的值是。0.0001、0.001、0.005、0.1、1、3、5;对于内核,我们使用的是以下值。线性 ""Rbf"。

3.1 网格搜索实现

下面是代码中的样子。

hyperparameters = {
    'C': [0.1, 1, 100, 1000],
    'gamma': [0.0001, 0.001, 0.005, 0.1, 1, 3, 5],
    'kernel': ('linear', 'rbf')
}

我们利用Sci-Kit Learn和它的SVC类,其中包含分类用的SVM的实现。除此以外,我们还使用了 GridSearchCV 类,该类用于网格搜索优化。结合起来,看起来是这样的。

grid = GridSearchCV(
        estimator=SVC(),
        param_grid=hyperparameters,
        cv=5, 
	scoring='f1_micro', 
	n_jobs=-1)

这个类通过构造函数接收几个参数。

  • estimator--机器学习算法本身的实例。我们在这里传递SVC类的新实例。
  • param_grid- 包含超参数字典。
  • cv- 确定交叉验证的分割策略。
  • scoring- 用于评估预测的验证度量。我们使用F1得分。
  • n_jobs- 代表并行运行的作业数量。值-1意味着正在使用所有的处理器。

唯一要做的就是通过利用拟合方法,运行训练过程。

grid.fit(X_train, y_train)

一旦训练完成,我们可以检查最佳超参数和这些参数的得分。

print(f'Best parameters: {grid.best_params_}')
print(f'Best score: {grid.best_score_}')
Best parameters: {'C': 1000, 'gamma': 0.1, 'kernel': 'rbf'}
Best score: 0.9626834381551361		

此外,我们还可以打印出所有的结果。

print(f'All results: {grid.cv_results_}')
Allresults: {'mean_fit_time': array([0.00780015, 0.00280147, 0.00120015, 0.00219998, 0.0240006 ,
       0.00739942, 0.00059962, 0.00600033, 0.0009994 , 0.00279789,
       0.00099969, 0.00340114, 0.00059986, 0.00299864, 0.000597  ,
       0.00340023, 0.00119658, 0.00280094, 0.00060058, 0.00179944,
       0.00099964, 0.00079966, 0.00099916, 0.00100031, 0.00079999,
       0.002     , 0.00080023, 0.00220037, 0.00119958, 0.00160012,
       0.02939963, 0.00099955, 0.00119963, 0.00139995, 0.00100069,
       0.00100017, 0.00140052, 0.00119977, 0.00099974, 0.00180006,
       0.00100312, 0.00199976, 0.00220003, 0.00320096, 0.00240035,
       0.001999  , 0.00319982, 0.00199995, 0.00299931, 0.00199928,   
...

好的,现在让我们建立这个模型,并检查它在测试数据集上的表现如何。

model = SVC(C=500, gamma = 0.1, kernel = 'rbf')
model.fit(X_train, y_train)

preditions = model.predict(X_test)
print(f1_score(preditions, y_test, average='micro'))
0.9701492537313433

很好,我们的模型和建议的超参数得到了97%的准确性。下面是模型绘制时的样子。

4.随机搜索超参数的调整

网格搜索是超级简单的。然而,它也是计算昂贵的。特别是在 深度学习,训练可能需要大量的时间。另外,可能会发生一些超参数比其他参数更重要的情况。这就是为什么随机搜索的想法诞生并被引入到了 这篇论文.事实上,这项研究表明,就计算成本而言,随机搜索比网格搜索的超参数优化效率更高。这种技术也能更精确地发现重要的超参数的良好值。

就像网格搜索一样,随机搜索创建一个超参数值的网格,并选择随机组合来训练模型。这种方法有可能错过最理想的组合,然而,与网格搜索相比,它出人意料地 经常挑选出最佳结果,而且只用了一小部分时间。

4.1 随机搜索的实现

让我们看看代码中是如何运作的。我们再次利用Sci-Kit Learn的SVC类,但这次我们用 随机搜索CV 类来进行随机搜索优化。

hyperparameters = {
    "C": stats.uniform(500, 1500),
    "gamma": stats.uniform(0, 1),
    'kernel': ('linear', 'rbf')
}

random = RandomizedSearchCV(
                estimator = SVC(), 
                param_distributions = hyperparameters, 
                n_iter = 100, 
                cv = 3, 
                random_state=42, 
                n_jobs = -1)

random.fit(X_train, y_train)

请注意,我们对C和gamma使用了均匀分布。同样,我们可以打印出结果。

print(f'Best parameters: {random.best_params_}')
print(f'Best score: {random.best_score_}')
Best parameters: {'C': 510.5994578295761, 'gamma': 0.023062425041415757, 'kernel': 'linear'}
Best score: 0.9700374531835205

请注意,我们得到了接近,但与使用 网格搜索时不同的结果。使用网格搜索时,超参数C的值是500,而使用随机搜索时,我们得到了510.59。仅从这一点,你就可以看出随机搜索的好处,因为我们不太可能这个值放在网格搜索列表中。同样,对于伽马,我们的随机搜索得到0.23,而网格搜索得到0.1。真正令人惊讶的是,随机搜索选择了线性核而不是RBF,而且它得到了更高的F1分数。为了打印所有结果,我们使用cv_results_属性。

print(f'All results: {random.cv_results_}')
Allresults: {'mean_fit_time': array([0.00200065, 0.00233404, 0.00100454, 0.00233777, 0.00100009,
       0.00033339, 0.00099715, 0.00132942, 0.00099921, 0.00066725,
       0.00266568, 0.00233348, 0.00233301, 0.0006667 , 0.00233285,
       0.00100001, 0.00099993, 0.00033331, 0.00166742, 0.00233364,
       0.00199914, 0.00433286, 0.00399915, 0.00200049, 0.01033338,
       0.00100342, 0.0029997 , 0.00166655, 0.00166726, 0.00133403,
       0.00233293, 0.00133729, 0.00100009, 0.00066662, 0.00066646,
	   
	  ....

让我们做与网格搜索相同的事情:用建议的超参数创建模型,检查测试数据集上的得分并绘制出模型。

model = SVC(C=510.5994578295761, gamma = 0.023062425041415757, kernel = 'linear')
model.fit(X_train, y_train)

preditions = model.predict(X_test)
print(f1_score(preditions, y_test, average='micro'))
0.9701492537313433

哇,测试数据集上的F1得分与我们使用网格搜索时完全相同。看看这个模型吧。

5.贝叶斯超参数优化

关于前两种算法的真正酷的事实是,所有的超参数值的实验都可以并行运行。这可以为我们节省大量的时间。然而,这也是他们最大的不足。这意味着,由于每个实验都是孤立运行的,我们不能在当前实验中使用过去的信息。有一整个领域致力于解决顺序优化的问题 -基于模型的顺序优化(SMBO)。在这个领域探索的算法使用以前的实验和损失函数的观察。基于它们,他们试图确定下一个最佳点。贝叶斯优化就是这样的算法之一。

Decision Tree

就像SMBO小组的其他算法一样,使用先前评估的点(在这种情况下,这些是超参数值,但我们可以泛化)来计算损失函数的后验期望值的样子。这个算法使用了两个重要的数学概念--高斯过程获取函数。由于 高斯分布是在随机变量上进行的,高斯过程是它在函数上的泛化。就像高斯分布有平均值协方差一样,高斯过程是由平均值函数协方差函数描述的。

获取函数是我们用来评估当前损失值的函数。观察它的一种方式是作为损失函数的损失函数。它是损失函数的后验分布的一个函数,它描述了超参数所有值的效用。最流行的获取函数是预期改进

其中f是损失函数,x'是当前最优的超参数集。当我们把这一切放在一起时,Byesian优化分3步完成。

  • 使用先前评估的损失函数的点,使用高斯过程计算后验期望值
  • 选择新的点集,使预期收益最大化。
  • 计算新选点的损失函数

Decision Tree

5.1 贝叶斯优化的实现

最简单的方法是使用Sci-Kit优化库(通常称为skopt)来实现。按照我们在以前的例子中使用的过程,我们可以做以下工作。

hyperparameters = {
    "C": Real(1e-6, 1e+6, prior='log-uniform'),
    "gamma": Real(1e-6, 1e+1, prior='log-uniform'),
    "kernel": Categorical(['linear', 'rbf']),
}

bayesian = BayesSearchCV(
                estimator = SVC(), 
                search_spaces = hyperparameters, 
                n_iter = 100, 
                cv = 5, 
                random_state=42, 
                n_jobs = -1)

bayesian.fit(X_train, y_train)

同样,我们为超参数定义了字典。注意,我们使用了Sci-Kit优化库中的RealCategorical类。然后我们利用 BayesSearchCV 类,就像我们使用GridSearchCVRandomSearchCV一样。训练完成后,我们可以打印出最佳结果。

print(f'Best parameters: {bayesian.best_params_}')
print(f'Best score: {bayesian.best_score_}')
Best parameters: 
OrderedDict([('C', 3932.2516133086), ('gamma', 0.0011646737978730447), ('kernel', 'rbf')])
Best score: 0.9625468164794008

这很有趣,不是吗?我们使用这个优化得到了相当不同的结果。损失比我们使用随机搜索时要高一点。我们甚至可以打印出所有的结果。

print(f'All results: {bayesian.cv_results_}')
All results: defaultdict(<class 'list'>, {'split0_test_score': [0.9629629629629629,
  0.9444444444444444, 0.9444444444444444, 0.9444444444444444,  0.9444444444444444,
  0.9444444444444444, 0.9444444444444444, 0.9444444444444444, 0.46296296296296297,
  0.9444444444444444, 0.8703703703703703, 0.9444444444444444, 0.9444444444444444, 
  0.9444444444444444, 0.9444444444444444, 0.9444444444444444, 0.9444444444444444, 
  .....

具有这些超参数的模型在测试数据集上的表现如何?让我们来看看。

model = SVC(C=3932.2516133086, gamma = 0.0011646737978730447, kernel = 'rbf')
model.fit(X_train, y_train)

preditions = model.predict(X_test)
print(f1_score(preditions, y_test, average='micro'))
0.9850746268656716

这真是太有趣了。我们在测试数据集上得到了更好的分数,尽管我们在验证数据集上的结果更差。这里是模型。

Decision Tree

为了好玩,让我们把所有这些模型并排放在一起。

Decision Tree

6.减半网格搜索和减半随机搜索

几个月前,Sci-Kit Learn推出了两个新的类HalvingGridSearchCV和HalvingRandomSearchCV。他们声称,有了这两个类,他们声称 "可以更快地找到一个好的参数组合"。这些类在指定的参数值上使用了连续减半的搜索。这种技术开始用少量的资源评估所有的候选者,然后迭代地选择最佳候选者,使用越来越多的资源。

AI Visual

从减半网格搜索的角度来看,这意味着在第一次迭代中,所有的候选人都将在少量的训练数据上进行训练。下一次迭代将只包括在上一次迭代中表现最好的候选人。这些模型将获得更多的资源,即更多的训练数据,它们将被评估。这个过程将继续下去,半数网格搜索将只保留前几次迭代中的最佳候选者,直到只剩下一个。

这整个过程由两个参数控制--min_samples和factor。第一个参数--min_samples代表进程开始时的数据量。每一次迭代,这个数据集将按因子定义的数值增长。这个过程与HalvingRandomSearchCV相似。

6.1 减半网格搜索和减半随机搜索的实现

代码与前面的例子相似,我们只是使用了不同的类。让我们从HalvingGridSearch 开始。

hyperparameters = {
    'C': [0.1, 1, 100, 500, 1000],
    'gamma': [0.0001, 0.001, 0.01, 0.005, 0.1, 1, 3, 5],
    'kernel': ('linear', 'rbf')
}



grid = HalvingGridSearchCV(
        estimator=SVC(),
        param_grid=hyperparameters,
        cv=5, 
        scoring='f1_micro', 
        n_jobs=-1)

grid.fit(X_train, y_train)

有趣的是,这段代码只运行了0.7秒。相比之下,使用GridSearchCV 类的相同代码要花3.6秒。这就快多了。但结果却有点不同。

print(f'Best parameters: {grid.best_params_}')
print(f'Best score: {grid.best_score_}')
Best parameters: {'C': 500, 'gamma': 0.005, 'kernel': 'rbf'}
Best score: 0.9529411764705882

我们得到了类似的结果,但不一样。如果我们用这些值创建一个模型,我们会得到以下的准确性和图表。

model = SVC(C=500, gamma = 0.005, kernel = 'rbf')
model.fit(X_train, y_train)

preditions = model.predict(X_test)
print(f1_score(preditions, y_test, average='micro'))
0.9850746268656716

Halving Grid Search Output Model

我们用减半随机搜索做完全相同的事情。有趣的是,用这种方法我们得到了最奇怪的结果。我们可以说,用这种方法创建的模型是过度拟合的,很难。

hyperparameters = {
    "C": stats.uniform(500, 1500),
    "gamma": stats.uniform(0, 1),
    'kernel': ('linear', 'rbf')
}

random = HalvingRandomSearchCV(
                estimator = SVC(), 
                param_distributions = hyperparameters, 
                cv = 3, 
                random_state=42, 
                n_jobs = -1)

random.fit(X_train, y_train)

print(f'Best parameters: {random.best_params_}')
print(f'Best score: {random.best_score_}')
Best parameters: {'C': 530.8767414437036, 'gamma': 0.9699098521619943, 'kernel': 'rbf'}
Best score: 0.9506172839506174

Halving Random Search Model

7.替代方法

一般来说,前面描述的方法是最流行和最经常使用的。然而,如果以前的方法不适合你,你可以考虑几个替代方法。其中之一是基于梯度的超参数值优化。这种技术计算与超参数有关的梯度,然后使用梯度下降算法对其进行优化。这种方法的问题是,为了使梯度下降法顺利运行,我们需要的是凸和平滑的函数,而当我们谈论超参数时,往往不是这种情况。另一种方法是使用进化算法进行优化。

总结

在这篇文章中,我们介绍了几种著名的超参数优化和调整算法。我们学习了如何使用网格搜索、随机搜索和贝叶斯优化来为我们的超参数获得最佳值。我们还看到了如何利用Sci-Kit学习类和方法在代码中做到这一点。

谢谢你的阅读!

Ultimate Guide to Machine Learning with Python

这捆电子书是专门为初学者制作的。从Python基础知识到机器学习算法在生产中的部署,一切都在一个地方。今天就成为机器学习的超级英雄吧!

Nikola M. Zivkovic

Nikola M. Zivkovic

Nikola M. Zivkovic是 书籍的作者 。 机器学习终极指南程序员的深度学习.他热爱知识分享,是一位经验丰富的演讲者。你可以看到他在 聚会、会议上发言 ,并在诺维萨德大学担任客座讲师。

分享。