伴随着这篇文章的代码可以在订阅后收到
*表示需要
电子邮件地址*
在之前的几篇文章中,我们特别关注了机器学习算法的 性能.我们谈到了如何量化机器学习模型的性能,以及如何用**正则化来改善它。除此以外,我们还涵盖了主题优化技术,包括梯度下降等基本技术和亚当等高级技术**。
围绕着模型优化的概念,完全不同的子分支是如何发展起来的,这是相当超现实的。其中一个子分支是超参数优化或超参数调谐。
这捆电子书是专门为初学者制作的。从Python基础知识到机器学习算法在生产中的部署,一切都在一个地方。今天就成为机器学习的超级英雄吧!
在这篇文章中,你可以找到。
1.机器学习中的超参数
超参数是每个机器学习和深度学习算法的一个组成部分。与标准的机器学习参数不同,这些参数是由算法本身学习的(比如线性回归中的w和b,或者神经网络中的连接权重),超参数是由工程师在训练过程中设置的。
它们是控制学习算法行为的一个外部因素,完全由工程师定义。你需要一些例子吗?学习率是最著名的超参数之一,SVM中的C也是一个超参数,决策树的最大深度是一个超参数,等等。这些都可以由工程师手动设置。
然而,如果我们想运行多个测试,这可能是令人厌烦的。这就是我们使用超参数优化的地方。这些技术的主要目标是找到一个给定的机器学习算法的超参数,以提供在验证集上测量的最佳性能。在本教程中,我们将探讨几种可以给你提供最佳超参数的技术。
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_mm和culmen_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算法.有三个超参数是我们要考虑的--C、gamma和kernel。要更详细地了解它们,请查看 这篇文章.对于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)。在这个领域探索的算法使用以前的实验和损失函数的观察。基于它们,他们试图确定下一个最佳点。贝叶斯优化就是这样的算法之一。
就像SMBO小组的其他算法一样,使用先前评估的点(在这种情况下,这些是超参数值,但我们可以泛化)来计算损失函数的后验期望值的样子。这个算法使用了两个重要的数学概念--高斯过程和获取函数。由于 高斯分布是在随机变量上进行的,高斯过程是它在函数上的泛化。就像高斯分布有平均值和协方差一样,高斯过程是由平均值函数和协方差函数描述的。
获取函数是我们用来评估当前损失值的函数。观察它的一种方式是作为损失函数的损失函数。它是损失函数的后验分布的一个函数,它描述了超参数所有值的效用。最流行的获取函数是预期改进。
其中f是损失函数,x'是当前最优的超参数集。当我们把这一切放在一起时,Byesian优化分3步完成。
- 使用先前评估的损失函数的点,使用高斯过程计算后验期望值。
- 选择新的点集,使预期收益最大化。
- 计算新选点的损失函数
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优化库中的Real和Categorical类。然后我们利用 BayesSearchCV 类,就像我们使用GridSearchCV或RandomSearchCV一样。训练完成后,我们可以打印出最佳结果。
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
这真是太有趣了。我们在测试数据集上得到了更好的分数,尽管我们在验证数据集上的结果更差。这里是模型。
为了好玩,让我们把所有这些模型并排放在一起。
6.减半网格搜索和减半随机搜索
几个月前,Sci-Kit Learn推出了两个新的类HalvingGridSearchCV和HalvingRandomSearchCV。他们声称,有了这两个类,他们声称 "可以更快地找到一个好的参数组合"。这些类在指定的参数值上使用了连续减半的搜索。这种技术开始用少量的资源评估所有的候选者,然后迭代地选择最佳候选者,使用越来越多的资源。
从减半网格搜索的角度来看,这意味着在第一次迭代中,所有的候选人都将在少量的训练数据上进行训练。下一次迭代将只包括在上一次迭代中表现最好的候选人。这些模型将获得更多的资源,即更多的训练数据,它们将被评估。这个过程将继续下去,半数网格搜索将只保留前几次迭代中的最佳候选者,直到只剩下一个。
这整个过程由两个参数控制--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
我们用减半随机搜索做完全相同的事情。有趣的是,用这种方法我们得到了最奇怪的结果。我们可以说,用这种方法创建的模型是过度拟合的,很难。
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
7.替代方法
一般来说,前面描述的方法是最流行和最经常使用的。然而,如果以前的方法不适合你,你可以考虑几个替代方法。其中之一是基于梯度的超参数值优化。这种技术计算与超参数有关的梯度,然后使用梯度下降算法对其进行优化。这种方法的问题是,为了使梯度下降法顺利运行,我们需要的是凸和平滑的函数,而当我们谈论超参数时,往往不是这种情况。另一种方法是使用进化算法进行优化。
总结
在这篇文章中,我们介绍了几种著名的超参数优化和调整算法。我们学习了如何使用网格搜索、随机搜索和贝叶斯优化来为我们的超参数获得最佳值。我们还看到了如何利用Sci-Kit学习类和方法在代码中做到这一点。
谢谢你的阅读!
这捆电子书是专门为初学者制作的。从Python基础知识到机器学习算法在生产中的部署,一切都在一个地方。今天就成为机器学习的超级英雄吧!
Nikola M. Zivkovic
Nikola M. Zivkovic是 书籍的作者 。 机器学习终极指南 和 程序员的深度学习.他热爱知识分享,是一位经验丰富的演讲者。你可以看到他在 聚会、会议上发言 ,并在诺维萨德大学担任客座讲师。