超参数优化

Bayesian Optimization使用Hyperopt进行参数调优

1. 前言

本文将介绍一种快速有效的方法用于实现机器学习模型的调参。有两种常用的调参方法:网格搜索和随机搜索。每一种都有自己的优点和缺点。网格搜索速度慢,但在搜索整个搜索空间方面效果很好,而随机搜索很快,但可能会错过搜索空间中的重要点。幸运的是,还有第三种选择:贝叶斯优化。本文我们将重点介绍贝叶斯优化的一个实现,一个名为hyperopt的Python模块

使用贝叶斯优化进行调参可以让我们获得给定模型的最佳参数,例如逻辑回归模型。这也使我们能够执行最佳的模型选择。通常机器学习工程师或数据科学家将为少数模型(如决策树,支持向量机和K近邻)执行某种形式(网格搜索或随机搜索)的手动调参,然后比较准确率并选择最佳的一个来使用。该方法可能比较的是次优模型。也许数据科学家找到了决策树的最优参数,但却错过了SVM的最优参数。这意味着他们的模型比较是有缺陷的。如果SVM参数调整得很差,K 近邻可能每次都会击败SVM。贝叶斯优化允许数据科学家找到所有模型的最佳参数,并因此比较最佳模型。这会得到更好的模型选择,因为你比较的是最佳的k近邻和最佳的决策树。只有这样你才能非常自信地进行模型选择,确保选择并使用的是实际最佳的模型。

本文涵盖的主题有:

  1. 目标函数
  2. 搜索空间
  3. 存储评估试验
  4. 可视化
  5. 经典数据集上的完整示例:Iris

2. 目标函数 - 一个启发性例子

假设你有一个定义在某个范围内的函数,并且想把它最小化。也就是说,你想找到产生最低输出值的输入值。下面的简单例子找到\(x\)的值用于最小化线性函数\(y(x)=x\)

from hyperopt import fmin, tpe, hp
best = fmin(
fn=lambda x: x,
space=hp.uniform('x', 0, 1),
algo=tpe.suggest,
max_evals=100)
print(best)

输出结果:

{'x': 0.000269455723739237}

  1. \(fmin\)首先接受一个函数来最小化,记为\(fn\),在这里用一个匿名函数\(lambda \ x:x\)来指定。该函数可以是任何有效的值返回函数,例如回归中的平均绝对误差。
  2. \(space\)是指定搜索空间,在本例中,它是0到1之间的连续数字范围,\(hp.uniform('x', 0, 1)\)指定。\(hp.uniform\)是一个内置的hyperopt函数,它有三个参数:名称\(x\),范围的下限和上限0和1。
  3. \(algo\)参数指定搜索算法,本例中tpe表示tree of Parzen estimators。该主题超出了本文的范围,但有数学背景的读者可以细读这篇文章。algo参数也可以设置为\(hyperopt.random\),但是这里我们没有涉及,因为它是众所周知的搜索策略。但在未来的文章中我们可能会涉及。
  4. 最后\(max\_evals\)是最大评估次数。这个fmin函数将返回一个python字典。

2.1 稍微复杂的例子

这有一个更复杂的目标函数:

\(lambda\ x: (x-1)^2\)。这次我们试图最小化一个二次方程\(y(x)=(x-1)^2\)。所以我们改变搜索空间以包括我们已知的最优值\((x=1)\)加上两边的一些次优范围:\(hp.uniform('x', -2, 2)\)。

best = fmin(
fn=lambda x: (x-1)**2,
space=hp.uniform('x', -2, 2),
algo=tpe.suggest,
max_evals=100)
print(best)

输出结果:

{'x': 0.997369045274755}

3. 搜索空间

hyperopt模块包含一些方便的函数来指定输入参数的范围。我们已经见过\(hp.uniform\)。最初,这些是随机搜索空间,但随着hyperopt更多的学习(因为它从目标函数获得更多反馈),通过它认为提供给它最有意义的反馈,会调整并采样初始搜索空间的不同部分。

以下内容将在本文中使用:

  1. \(hp.choice(label, options)\)其中options应是python列表或元组。
  2. \(hp.normal(label, mu, sigma)\)其中mu和sigma分别是均值和标准差。
  3. \(hp.uniform(label, low, high)\)其中low和high是范围的下限和上限。
import hyperopt.pyll.stochastic

space = {
'x': hp.uniform('x', 0, 1),
'y': hp.normal('y', 0, 1),
'name': hp.choice('name', ['alice', 'bob']),
} print(hyperopt.pyll.stochastic.sample(space))

输出结果:

{'y': -1.4012610048810574, 'x': 0.7258615424906184, 'name': 'alice'}

4. Trials捕获信息

如果能看到hyperopt黑匣子内发生了什么是极好的。Trials对象使我们能够做到这一点。我们只需要导入一些东西。

from hyperopt import fmin, tpe, hp, STATUS_OK, Trials

fspace = {
'x': hp.uniform('x', -5, 5)
} def f(params):
x = params['x']
val = x**2
return {'loss': val, 'status': STATUS_OK} trials = Trials()
best = fmin(fn=f, space=fspace, algo=tpe.suggest, max_evals=50, trials=trials) print('best:', best) print 'trials:'
for trial in trials.trials[:2]:
print(trial)

\(STATUS_OK\)和Trials是新导入的。Trials对象允许我们在每个时间步存储信息。然后我们可以将它们打印出来,并在给定的时间步查看给定参数的函数评估值。

输出结果:

best: {'x': 0.014420181637303776}
trials:
{'refresh_time': None, 'book_time': None, 'misc': {'tid': 0, 'idxs': {'x': [0]}, 'cmd': ('domain_attachment', 'FMinIter_Domain'), 'vals': {'x': [1.9646918559786162]}, 'workdir': None}, 'state': 2, 'tid': 0, 'exp_key': None, 'version': 0, 'result': {'status': 'ok', 'loss': 3.8600140889486996}, 'owner': None, 'spec': None}
{'refresh_time': None, 'book_time': None, 'misc': {'tid': 1, 'idxs': {'x': [1]}, 'cmd': ('domain_attachment', 'FMinIter_Domain'), 'vals': {'x': [-3.9393509404526728]}, 'workdir': None}, 'state': 2, 'tid': 1, 'exp_key': None, 'version': 0, 'result': {'status': 'ok', 'loss': 15.518485832045357}, 'owner': None, 'spec': None}

Trials对象将数据存储为BSON对象,其工作方式与JSON对象相同。BSON来自pymongo模块。我们不会在这里讨论细节,这是对于需要使用MongoDB进行分布式计算的hyperopt的高级选项,因此需要导入pymongo。回到上面的输出。

  1. tid是时间 id,即时间步,其值从0到\(max\_evals-1\)。它随着迭代次数递增。
  2. \('x'\)是键\('vals'\)的值,其中存储的是每次迭代参数的值。
  3. \('loss'\)是键\('result'\)的值,其给出了该次迭代目标函数的值。

4.1 可视化

我们看看损失vs值的图

f, ax = plt.subplots(1)
xs = [t['misc']['vals']['x'] for t in trials.trials]
ys = [t['result']['loss'] for t in trials.trials]
ax.scatter(xs, ys, s=20, linewidth=0.01, alpha=0.75)
ax.set_title('$val$ $vs$ $x$ ', fontsize=18)
ax.set_xlabel('$x$', fontsize=16)
ax.set_ylabel('$val$', fontsize=16)

5. Iris 数据集

在本节中,我们将介绍4个使用hyperopt在经典数据集Iris上调参的完整示例。我们将涵盖K近邻(KNN),支持向量机(SVM),决策树和随机森林。需要注意的是,由于我们试图最大化交叉验证的准确率(acc请参见下面的代码),而hyperopt只知道如何最小化函数,所以必须对准确率取负。最小化函数f与最大化f的负数是相等的。

对于这项任务,我们将使用经典的Iris数据集,并进行一些有监督的机器学习。数据集有有4个输入特征和3个输出类别。数据被标记为属于类别0,1或2,其映射到不同种类的鸢尾花。输入有4列:萼片长度,萼片宽度,花瓣长度和花瓣宽度。输入的单位是厘米。我们将使用这4个特征来学习模型,预测三种输出类别之一。因为数据由sklearn提供,它有一个很好的DESCR属性,可以提供有关数据集的详细信息。尝试以下代码以获得更多细节信息。

5.1 K近邻

我们现在将使用hyperopt来找到K近邻(KNN)机器学习模型的最佳参数。KNN模型是基于训练数据集中k个最近数据点的大多数类别对来自测试集的数据点进行分类。下面的代码结合了我们所涵盖的一切。

from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target def hyperopt_train_test(params):
clf = KNeighborsClassifier(**params)
return cross_val_score(clf, X, y).mean() space4knn = {
'n_neighbors': hp.choice('n_neighbors', range(1,100))
} def f(params):
acc = hyperopt_train_test(params)
return {'loss': -acc, 'status': STATUS_OK} trials = Trials()
best = fmin(f, space4knn, algo=tpe.suggest, max_evals=100, trials=trials)
print('best:',best)

现在让我们看看输出结果的图。y轴是交叉验证分数,x轴是k近邻个数。下面是代码和它的图像:

f, ax = plt.subplots(1)#, figsize=(10,10))
xs = [t['misc']['vals']['n'] for t in trials.trials]
ys = [-t['result']['loss'] for t in trials.trials]
ax.scatter(xs, ys, s=20, linewidth=0.01, alpha=0.5)
ax.set_title('Iris Dataset - KNN', fontsize=18)
ax.set_xlabel('n_neighbors', fontsize=12)
ax.set_ylabel('cross validation accuracy', fontsize=12)

k 大于63后,准确率急剧下降。这是因为数据集中每个类的数量。这三个类中每个类只有50个实例。所以让我们将\('n\_neighbors'\)的值限制为较小的值来进一步探索。

from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target def hyperopt_train_test(params):
clf = KNeighborsClassifier(**params)
return cross_val_score(clf, X, y).mean() space4knn = {
'n_neighbors': hp.choice('n_neighbors', range(1,50))
} def f(params):
acc = hyperopt_train_test(params)
return {'loss': -acc, 'status': STATUS_OK} trials = Trials()
best = fmin(f, space4knn, algo=tpe.suggest, max_evals=100, trials=trials)
print('best:',best)

现在我们可以清楚地看到k有一个最佳值,k=4。

上面的模型没有做任何预处理。所以我们来归一化和缩放特征,看看是否有帮助。用如下代码:

# now with scaling as an option
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target def hyperopt_train_test(params):
X_ = X[:] if 'normalize' in params:
if params['normalize'] == 1:
X_ = normalize(X_)
del params['normalize'] if 'scale' in params:
if params['scale'] == 1:
X_ = scale(X_)
del params['scale'] clf = KNeighborsClassifier(**params)
return cross_val_score(clf, X_, y).mean() space4knn = {
'n_neighbors': hp.choice('n_neighbors', range(1,50)),
'scale': hp.choice('scale', [0, 1]),
'normalize': hp.choice('normalize', [0, 1])
} def f(params):
acc = hyperopt_train_test(params)
return {'loss': -acc, 'status': STATUS_OK} trials = Trials()
best = fmin(f, space4knn, algo=tpe.suggest, max_evals=100, trials=trials)
print('best:',best)

并像这样绘制参数:

parameters = ['n_neighbors', 'scale', 'normalize']
cols = len(parameters)
f, axes = plt.subplots(nrows=1, ncols=cols, figsize=(15,5))
cmap = plt.cm.jet
for i, val in enumerate(parameters):
xs = np.array([t['misc']['vals'][val] for t in trials.trials]).ravel()
ys = [-t['result']['loss'] for t in trials.trials]
xs, ys = zip(\*sorted(zip(xs, ys)))
ys = np.array(ys)
axes[i].scatter(xs, ys, s=20, linewidth=0.01, alpha=0.75, c=cmap(float(i)/len(parameters)))
axes[i].set_title(val)

我们看到缩放和/或归一化数据并不会提高预测准确率。k的最佳值仍然为4,这得到98.6%的准确率。

所以这对于简单模型 KNN 调参很有用。让我们看看用支持向量机(SVM)能做什么。

5.2 支持向量机(SVM)

由于这是一个分类任务,我们将使用sklearn的SVC类。代码如下:

iris = datasets.load_iris()
X = iris.data
y = iris.target def hyperopt_train_test(params):
X_ = X[:] if 'normalize' in params:
if params['normalize'] == 1:
X_ = normalize(X_)
del params['normalize'] if 'scale' in params:
if params['scale'] == 1:
X_ = scale(X_)
del params['scale'] clf = SVC(**params)
return cross_val_score(clf, X_, y).mean() space4svm = {
'C': hp.uniform('C', 0, 20),
'kernel': hp.choice('kernel', ['linear', 'sigmoid', 'poly', 'rbf']),
'gamma': hp.uniform('gamma', 0, 20),
'scale': hp.choice('scale', [0, 1]),
'normalize': hp.choice('normalize', [0, 1])
} def f(params):
acc = hyperopt_train_test(params)
return {'loss': -acc, 'status': STATUS_OK} trials = Trials()
best = fmin(f, space4svm, algo=tpe.suggest, max_evals=100, trials=trials)
print('best:',best) parameters = ['C', 'kernel', 'gamma', 'scale', 'normalize']
cols = len(parameters)
f, axes = plt.subplots(nrows=1, ncols=cols, figsize=(20,5))
cmap = plt.cm.jet
for i, val in enumerate(parameters):
xs = np.array([t['misc']['vals'][val] for t in trials.trials]).ravel()
ys = [-t['result']['loss'] for t in trials.trials]
xs, ys = zip(\*sorted(zip(xs, ys)))
axes[i].scatter(xs, ys, s=20, linewidth=0.01, alpha=0.25, c=cmap(float(i)/len(parameters)))
axes[i].set_title(val)
axes[i].set_ylim([0.9, 1.0])

我们得到结果:

同样,缩放和归一化也没有帮助。核函数的首选是(linear),C的最佳值是1.4168540399911616,gamma的最佳值是15.04230279483486。这组参数得到了99.3%的分类准确率。

5.3 是时候把所有东西合为一体了

自动调整一个模型的参数(如SVM或KNN)非常有趣并且具有启发性,但同时调整它们并取得全局最佳模型则更有用。这使我们能够一次比较所有参数和所有模型,因此为我们提供了最佳模型。代码如下:

digits = datasets.load_digits()
X = digits.data
y = digits.target
print X.shape, y.shape def hyperopt_train_test(params):
t = params['type']
del params['type']
if t == 'naive_bayes':
clf = BernoulliNB(**params)
elif t == 'svm':
clf = SVC(**params)
elif t == 'dtree':
clf = DecisionTreeClassifier(**params)
elif t == 'knn':
clf = KNeighborsClassifier(**params)
else:
return 0
return cross_val_score(clf, X, y).mean() space = hp.choice('classifier_type', [
{
'type': 'naive_bayes',
'alpha': hp.uniform('alpha', 0.0, 2.0)
},
{
'type': 'svm',
'C': hp.uniform('C', 0, 10.0),
'kernel': hp.choice('kernel', ['linear', 'rbf']),
'gamma': hp.uniform('gamma', 0, 20.0)
},
{
'type': 'randomforest',
'max_depth': hp.choice('max_depth', range(1,20)),
'max_features': hp.choice('max_features', range(1,5)),
'n_estimators': hp.choice('n_estimators', range(1,20)),
'criterion': hp.choice('criterion', ["gini", "entropy"]),
'scale': hp.choice('scale', [0, 1]),
'normalize': hp.choice('normalize', [0, 1])
},
{
'type': 'knn',
'n_neighbors': hp.choice('knn_n_neighbors', range(1,50))
}
]) count = 0
best = 0
def f(params):
global best, count
count += 1
acc = hyperopt_train_test(params.copy())
if acc > best:
print 'new best:', acc, 'using', params['type']
best = acc
if count % 50 == 0:
print 'iters:', count, ', acc:', acc, 'using', params
return {'loss': -acc, 'status': STATUS_OK} trials = Trials()
best = fmin(f, space, algo=tpe.suggest, max_evals=1500, trials=trials)
print('best:',best)

由于我们增加了评估数量,此代码需要一段时间才能运行完:\(max\_evals=1500\)。当找到新的最佳准确率时,它还会添加到输出用于更新。好奇为什么使用这种方法没有找到前面的最佳模型:参数为kernel=linear,C=1.416,gamma=15.042的SVM。

6. 总结

我们已经介绍了简单的例子,如最小化确定的线性函数,以及复杂的例子,如调整SVM的参数。后面读者需要根据自己的需求再去调整选择的参数,也可以基于深度学习模型进行调参。

转载至https://www.jianshu.com/p/35eed1567463

最新文章

  1. CRL快速开发框架系列教程二(基于Lambda表达式查询)
  2. 浅析“依赖注入(DI)/控制反转(IOC)”的实现思路
  3. CentOS下MySQL数据库安装
  4. Redis教程(十五):C语言连接操作代码实例
  5. Inno Setup info
  6. PAT 1003. 我要通过!(20)
  7. SQL Server :DBLINK创建及使用
  8. itoa函数的实现(不同进制)
  9. windows下python 编码问题
  10. 我所理解的 KMP(Knuth–Morris–Pratt) 算法
  11. 电源VCC、VSS、VDD、VEE、VPP、Vddf标号的区别
  12. android的注意点
  13. 解决Win7下一个VC++6.0您不能直接打开多个project问题
  14. Push Notification总结系列之移动客户端定位服务
  15. 201521123075 《Java程序设计》第9周学习总结
  16. 生活日历NABCD需求分析
  17. 直接插入排序(js版)
  18. Android开发者的Anko使用指南(一)之Intent
  19. windows 中查找占用某个端口的进程并杀死的命令
  20. mybatis调用oracle存储过程 out游标类型参数 如何赋给java map

热门文章

  1. 数据分析三剑客 numpy,oandas,matplotlib
  2. Maven更改本地默认仓库时遇到的问题。 No implementation for org.apache.maven.model.path.PathTranslator was bound
  3. c# WF 第4节 窗体的事件
  4. 基于 lstm 的股票收盘价预测 -- python
  5. arduino (3) 控制sim900A发送短信
  6. [LOJ 6435][PKUSC 2018]星际穿越
  7. 【半小时大话.net依赖注入】(下)详解AutoFac+实战Mvc、Api以及.NET Core的依赖注入
  8. 『卧槽』意外发现了 Hashtable 的 foreach 用法 BUG
  9. flash判断,及安装注意
  10. nginx二级域名反向代理