完整介绍用于Python中自动超参数调剂的贝叶斯优化-1.jpg (109.5 KB, 下载次数: 0)
2018-7-4 23:45 上传
调剂机器学习超参数是一项繁琐但至关重要的任务,因为算法的性能可能高度依赖于超参数的选择。手动调剂需要时间远离机器学习管道的重要步调,如特征工程和解释结果。网格和随机搜索是不干与的,但需要很长的运行时间,因为它们浪费时间探索搜索空间中没有希望的区域。越来越多的超参数调剂任务是通过自动化体例完成的,这些体例旨在使用明智的搜索在更短的时间内找到最佳的超参数,而无需在初始设置之外进行手动操作。
贝叶斯优化是一种用于找到函数最小值的基于模型的体例,最近已应用于机器学习超参数调剂,结果表白该体例可以在测试集上实现更好的性能,同时比随机搜索需要更少的迭代。另外,现在有许多Python库可以为任何机器学习模型简化实现贝叶斯超参数调剂。
在本文中,我们将使用Hyperopt库演示gradient boosting machine的贝叶斯超参数调剂的完整示例。
贝叶斯优化体例
作为简要的入门,贝叶斯优化通过基于目标的过去评估结果建立替代函数(几率模型)来找到最小化目标函数的值。代办署理比目标优化更廉价,因此通过对代办署理应用标准(通常是预期改进)来选择要评估的下一个输入值。贝叶斯体例与随机或网格搜索的不合之处在于它们使用过去的评估结果来选择要评估的下一个值。这个概念是:通过选择基于过去表示良好的下一个输入值来限制对目标函数的昂贵评估。
在超参数优化的情况下,目标函数是使用一组超参数的机器学习模型的验证毛病。目的是找到在验证集上产生最低误差的超参数,希望这些结果推广到测试集。评估目标函数是昂贵的,因为它需要使用一组特定的超参数来训练机器学习模型。理想情况下,我们需要一种可以探索搜索空间的体例,同时还要限制对不良超参数选择的评估。贝叶斯超参数调剂使用不竭更新的几率模型,通过推断过去的结果来“集中”有希望的超参数。
Python Options
Python中有几个贝叶斯优化库,它们在目标函数的代办署理算法上有所不合。在本文中,我们将使用Hyperopt,它使用Tree Parzen Estimator(TPE),其他Python库包含Spearmint(高斯过程代办署理)和SMAC(随机森林回归)。这个领域有很多有趣的工作,所以如果你对一个库不满意,请查看替代方案!问题的一般结构是库之间进行转换,语法上只有很小的差别。
优化问题的四个部分
贝叶斯优化问题有四个部分:
目标函数:我们要最小化的,在这种情况下,机器学习模型对超参数的验证误差域空间:要搜索的超参数值优化算法:构建代办署理模型和选择下一个超参数值的体例历史结果:通过对包含超参数和验证损失的目标函数的评估来存储结果
通过这4个部分,我们可以优化(查找最小值)任何返回实际值的函数。这是一个强年夜的抽象,除调优机器学习超参数外,还允许我们解决许多问题。
数据集
对本例,我们将使用Caravan Insurance数据集(https://www.kaggle.com/uciml/caravan-insurance-challenge),目标是预测客户是否会购买保险单。这是一个有监督的分类问题,有5800个训练观察和4000个测试点。我们用来评估性能的指标是曲线下的受试者工作特征(ROC AUC),因为这是一个不服衡的分类问题。(ROC AUC越高越好,1分代表完美模型)。数据集如下所示:
完整介绍用于Python中自动超参数调剂的贝叶斯优化-2.jpg (34.69 KB, 下载次数: 0)
2018-7-4 23:45 上传
Dataset (CARAVAN) is the label
因为Hyperopt需要最小化值,我们将从目标函数返回1-ROC AUC,从而提高ROC AUC。
Gradient Boosting Model
本文不需要详细了解GBM,这里是我们需要了解的基础知识:GBM是一种基于使用弱学习者(几乎总是决策树)顺序训练以形成强年夜的集合增强体例模型。GBM中有许多超参数控制整个集合和单个决策树。选择树数量(称为估算器)的最有效体例之一是我们将使用的早期停止。LightGBM在Python中提供了GBM的快速而简单的实现。
有了需要的布景,让我们来编写贝叶斯优化问题的四个部分,用于超参数调剂。
目标函数
目标函数是我们试图最小化的。它接受一组值 - 在这种情况下为GBM的超参数 - 并输出实际值以最小化 - 交叉验证损失。Hyperopt将目标函数视为一个黑匣子,因为它只考虑输入和输出。该算法不需要知道目标函数的内部信息,就可以找到使损失最小化的输入值!在一个很是高的级别(伪代码)中,我们的目标函数应该是:
def objective(hyperparameters):
"""Returns validation score from hyperparameters"""
model = Classifier(hyperparameters)
validation_loss = cross_validation(model, training_data)
return validation_loss
我们需要注意的是,不要在测试集上使用损失,因为在评估最终模型时,我们只能使用一次测试集。相反,我们对验证集上的超参数进行评估,并且,我们使用KFold交叉验证,而不是将训练数据分手到一个不合的验证集上,除保存有价值的训练数据外,我们还应该在测试集上给我们一个不那么有偏差的估计毛病。
用于超参数调优的目标函数的基本结构在不合的模型中是相同的:函数接受超参数并使用这些超参数返回交叉验证误差。虽然这个示例是针对GBM的,可是该结构可以应用于其他体例。
使用10倍交叉验证的GBM的完整目标函数如下所示
import lightgbm as lgb
from hyperopt import STATUS_OK
N_FOLDS = 10
# Create the dataset
train_set = lgb.Dataset(train_features, train_labels)
def objective(params, n_folds = N_FOLDS):
"""Objective function for Gradient Boosting Machine Hyperparameter Tuning"""
# Perform n_fold cross validation with hyperparameters
# Use early stopping and evalute based on ROC AUC
cv_results = (params, train_set, nfold = n_folds, num_boost_round = 10000,
early_stopping_rounds = 100, metrics = 'auc', seed = 50)
# Extract the best score
best_score = max(cv_results['auc-mean'])
# Loss must be minimized
loss = 1 - best_score
# Dictionary with information for evaluation
return {'loss': loss, 'params': params, 'status': STATUS_OK}
主要的行是cv_results = (...) 。为了实现早期停止的交叉验证,我们使用LightGBM函数cv,该函数接收超参数,训练集,用于交叉验证的多个folds ,以及其他几个参数。我们将估算器(num_boost_round)的数量设置为10000,但实际上不会达到这个数字,因为early_stopping_rounds当100个估算器的验证分数没有改良时,我们正在使用停止训练。提前停止是选择估算器数量的有效体例,而不是将其设置为需要调剂的另一个超参数!
一旦交叉验证完成,我们就会获得最好的分数(ROC AUC),然后,因为我们想要一个最小化的值,我们获得1个最佳分数。然后,此值将作为loss返回字典中的键返回。
这个目标函数实际上比它需要的要复杂很多,因为我们返回一个值字典。对Hyperopt中的目标函数,我们可以返回单个值,损失或至少具有键"loss"和的字典"status" 。返回超参数将让我们检查每组超参数致使的损失。
域空间
域空间暗示我们要对每个超参数进行评估的值的规模。每次迭代搜索,贝叶斯优化算法城市从域空间中为每个超参数选择一个值。当我们进行随机或网格搜索时,域空间就是一个网格。在贝叶斯优化中,想法是一样的,除这个空间对每个超参数有几率散布而不是离散值。
指定域是贝叶斯优化问题中最棘手的部分。如果我们有机器学习体例的经验,我们可以通过在我们认为最佳值的位置放置更年夜的几率来使用它来告知我们对超参数散布的选择。然而,最佳模型设置将在数据集之间转变,并且具有高维度问题(许多超参数),可能难以确定超参数之间的交互。在我们不确定最佳值的情况下,我们可以使用广泛的散布,让贝叶斯算法为我们做推理。
首先,我们应该看看GBM中的所有超参数:
import lgb
# Default gradient boosting machine classifier
model = lgb.LGBMClassifier()
model
LGBMClassifier(boosting_type='gbdt', n_estimators=100,
class_weight=None, colsample_bytree=1.0,
learning_rate=0.1, max_depth=-1,
min_child_samples=20,
min_child_weight=0.001, min_split_gain=0.0,
n_jobs=-1, num_leaves=31, objective=None,
random_state=None, reg_alpha=0.0, reg_lambda=0.0,
silent=True, subsample=1.0,
subsample_for_bin=200000, subsample_freq=1)
其中一些我们不需要调剂(例如objective和random_state),我们将使用早期停止来找到最好的n_estimators。可是,我们还有10个超参数进行优化!首次调剂模型时,我通常会建立一个以默认值为中心的宽域空间,然后在后续搜索中对其进行细化。
例如,让我们在Hyperopt中界说一个简单的域,这是GBM中每棵树中叶子数量的离散均匀散布:
from hyperopt import hp
# Discrete uniform distribution
num_leaves = {'num_leaves': hp.quniform('num_leaves', 30, 150, 1)}
这是一个离散的均匀散布,因为叶子的数量必须是整数(离散),并且域中的每个值都可能(均匀)。
另一种散布选择是对数均匀,它在对数标度上均匀散布值。对学习速率,我们将使用log uniform(从0.005到0.2),因为它在几个数量级上转变:
# Learning rate log uniform distribution
learning_rate = {'learning_rate': hp.loguniform('learning_rate',
np.log(0.005),
np.log(0.2)}
因为这是对数均匀散布,所以在exp(低)和exp(高)之间绘制值。下面左边的图显示了离散的均匀散布,右边的图是对数均匀。这些是核密度估计图,因此y轴是密度而不是计数!
完整介绍用于Python中自动超参数调剂的贝叶斯优化-3.jpg (24.54 KB, 下载次数: 0)
2018-7-4 23:45 上传
现在,让我们界说整个域:
# Define the search space
space = {
'class_weight': hp.choice('class_weight', [None, 'balanced']),
'boosting_type': hp.choice('boosting_type',
[{'boosting_type': 'gbdt',
'subsample': hp.uniform('gdbt_subsample', 0.5, 1)},
{'boosting_type': 'dart',
'subsample': hp.uniform('dart_subsample', 0.5, 1)},
{'boosting_type': 'goss'}]),
'num_leaves': hp.quniform('num_leaves', 30, 150, 1),
'learning_rate': hp.loguniform('learning_rate', np.log(0.01), np.log(0.2)),
'subsample_for_bin': hp.quniform('subsample_for_bin', 20000, 300000, 20000),
'min_child_samples': hp.quniform('min_child_samples', 20, 500, 5),
'reg_alpha': hp.uniform('reg_alpha', 0.0, 1.0),
'reg_lambda': hp.uniform('reg_lambda', 0.0, 1.0),
'colsample_bytree': hp.uniform('colsample_by_tree', 0.6, 1.0)
}
这里我们使用一些不合的域散布类型:
choice :分类变量quniform :离散均匀(整数间隔均匀)uniform:连续均匀(浮动间隔均匀)loguniform:连续对数均匀(在对数标准上均匀间隔的浮点数)
在界说增强类型时,有一点需要注意:
# boosting type domain
boosting_type = {'boosting_type': hp.choice('boosting_type',
[{'boosting_type': 'gbdt',
'subsample': hp.uniform('subsample', 0.5, 1)},
{'boosting_type': 'dart',
'subsample': hp.uniform('subsample', 0.5, 1)},
{'boosting_type': 'goss',
'subsample': 1.0}])}
这里我们使用条件域,这意味着一个超参数的值取决于另一个超参数的值。对增强类型"goss",gbm不克不及使用子采样(仅选择subsample在每次迭代中使用的训练观察的一小部分)。因此,subsample如果增强类型是,则比率设置为1.0(无子采样),"goss" 不然为0.5-1.0。这是使用嵌套域实现的。
当我们使用具有完全自力参数的不合机器学习模型时,条件嵌套可能很有用。条件允许我们根据a的值使用不合的超参数集choice。
既然界说了我们的域,我们可以从中绘制一个示例来查看典型样本的样子。当我们进行采样时,因为subsample最初是嵌套的,所以我们需要将它分派给顶级键。这是使用Python字典get体例完成的,默认值为1.0。
# Sample from the full space
example = sample(space)
# Dictionary get method with default
subsample = example['boosting_type'].get('subsample', 1.0)
# Assign top-level keys
example['boosting_type'] = example['boosting_type']['boosting_type']
example['subsample'] = subsample
example{'boosting_type': 'gbdt',
'class_weight': 'balanced',
'colsample_bytree': 0.8111305579351727,
'learning_rate': 0.16186471096789776,
'min_child_samples': 470.0,
'num_leaves': 88.0,
'reg_alpha': 0.6338327001528129,
'reg_lambda': 0.8554826167886239,
'subsample_for_bin': 280000.0,
'subsample': 0.6318665053932255}
(这种重新分派嵌套键是需要的,因为GBM不克不及措置嵌套的超参数字典)。
优化算法
虽然这是贝叶斯优化中概念上最困难的部分,但在Hyperopt中建立优化算法只需一行。要使用Tree Parzen Estimator,代码为:
from hyperopt import tpe
# Algorithm
tpe_algorithm = tpe.suggest
在优化期间,TPE算法根据过去的结果构建几率模型,并通过最年夜化预期的改进来决定下一组超参数以在目标函数中进行评估。
历史结果
跟踪结果其实不是绝对需要的,因为Hyperopt将在内部为算法执行此操作。可是,如果我们想知道幕后产生了什么,我们可以使用一个Trials存储基本训练信息的对象,以及从目标函数(包含loss和params)返回的字典。制作试验对象是一条线:
from hyperopt import Trials
# Trials object to track progress
bayes_trials = Trials()
另一个可以让我们监视持久训练进程的选项是在每次搜索迭代时将一行写入csv文件。这也将所有的结果保存到磁盘,以防产生灾难性的事情,而使我们丢失了试验对象(从经验讲)。我们可以使用csv库来实现这一点。在训练之前,我们要打开一个新的csv文件,并写下题目
import csv
# File to save first results
out_file = 'gbm_trials.csv'
of_connection = open(out_file, 'w')
writer = csv.writer(of_connection)
# Write the headers to the file
writer.writerow(['loss', 'params', 'iteration', 'estimators', 'train_time'])
of_connection.close()
然后在目标函数中我们可以在每次迭代时添加行写入csv
#写入csv文件('a'暗示追加)
of_connection = open(out_file,' a ')
writer = csv.writer(of_connection)
writer.writerow([loss,params,iteration,n_estimators,run_time])
of_connection.close()
写入csv意味着我们可以通过在训练时打开文件来检查进度(使用tail out_file.csvbash来查看文件的最后几行)。
优化
一旦我们有四个部分,优化运行fmin :
from hyperopt import fmin
MAX_EVALS = 500
# Optimize
best = fmin(fn = objective, space = space, algo = tpe.suggest,
max_evals = MAX_EVALS, trials = bayes_trials)
在每次迭代时,算法从代办署理函数中选择新的超参数值,该代办署理函数基于先前的结果构建并在目标函数中评估这些值。这继续用于MAX_EVALS目标函数的评估,其中代办署理函数随每个新结果不竭更新。
结果
best返回的对象fmin包含在目标函数上产生最低损失的超参数:
{'boosting_type':'gbdt',
'class_weight':'balanced',
'colsample_bytree':0.7125187075392453,
'learning_rate':0.022592570862044956,
'min_child_samples':250,
'num_leaves':49,
'reg_alpha':0.2035211643104735,
'reg_lambda' :0.6455131715928091,
'subsample':0.983566228071919,
'subsample_for_bin':200000}
一旦我们有了这些超参数,我们就可以用它们来训练一个完整的训练数据模型,然后对测试数据进行评估(记住,当我们评估最终模型时,我们只能使用一次测试集)。我们可以使用估计器的数量,这些估计器在交叉验证中以早期停止返回最小的损失。最终结果如下:
The best model scores 0.72506 AUC ROC on the test set.
The best cross validation score was 0.77101 AUC ROC.
This was achieved after 413 search iterations.
作为参考,500次随机搜索迭代返回的模型在测试集中的ROC AUC为0.7232,在交叉验证中为0.76850。一个没有优化的默认模型在测试集中得分为0.7143 ROC AUC。
当我们看结果的时候,有一些重要的注意事项要记住:
最佳超参数是那些在交叉验证方面表示最佳的参数,而不一定是那些在测试数据上做得最好的参数。当我们使用交叉验证时,我们希望这些结果可以推广到测试数据。即使使用10倍交叉验证,超参数调剂也会过度拟合训练数据。交叉验证的最佳分数显着高于测试数据。随机搜索可以通过纯粹的运气返回更好的超参数(重新运行可以改变结果)。贝叶斯优化不克不及包管找到更好的超参数,并且可能陷入目标函数的局部最小值。
贝叶斯优化是有效的,但它不克不及解决所有的优化问题。随着搜索的进行,算法从探索——测验考试新的超参数值——转换到利用——使用致使目标函数损失最小的超参数值。如果该算法找到目标函数的局部最小值,那么它可能会集中在围绕局部最小值的超参数值上,而不是测验考试位于远域空间的不合值。随机搜索不受这个问题的影响,因为它不关注任何值!
另一个重要的一点是,超参数优化的好处与数据集不合。这是一个相对较小的数据集(~ 6000个训练观察),调优超参数有一个小的回报(获得更多的数据会更好地利用时间!)考虑到所有这些注意事项,在这种情况下,通过贝叶斯优化我们可以获得:
在测试集上有更好的性能调剂超参数的迭代次数减少。
贝叶斯体例可以(尽管并不是总是)产生比随机搜索更好的调优结果。在接下来的几节中,我们将研究贝叶斯超参数搜索的演变,并与随机搜索进行比较,以了解贝叶斯优化是如何工作的。
可视化搜索结果
绘制结果图表是一种直观的体例,可以了解超参数搜索过程中产生的情况。另外,将贝叶斯优化与随机搜索进行比较是有帮忙的,这样我们就可以看出体例的不合之处。(所有这些图都是500次迭代)
首先,我们可以learning_rate在随机搜索和贝叶斯优化中制作采样的核密度估计图。作为参考,我们还可以显示采样散布。垂直虚线暗示学习率的最佳值(根据交叉验证)。
完整介绍用于Python中自动超参数调剂的贝叶斯优化-4.jpg (25.76 KB, 下载次数: 0)
2018-7-4 23:45 上传
我们将学习率界说为0.005到0.2之间的对数正态,贝叶斯优化结果看起来与采样散布类似。这告诉我们,我们界说的散布看起来适合于任务,尽管最佳值比我们放置最年夜几率的值略高。这可用于通知域进一步搜索。
另一个超参数是增强类型,在随机搜索和贝叶斯优化期间评估每种类型的条形图。由于随机搜索不关注过去的结果,我们预计每种增强类型的使用次数年夜致相同。
完整介绍用于Python中自动超参数调剂的贝叶斯优化-5.jpg (27.83 KB, 下载次数: 0)
2018-7-4 23:45 上传
根据贝叶斯算法,gdbt增强型比dart或更有希望goss。同样,这可以帮忙进一步搜索,贝叶斯体例或网格搜索。如果我们想要进行更明智的网格搜索,我们可以使用这些结果来界说围绕超参数最有希望的值的较小网格。
由于我们有它们,让我们看看参考散布,随机搜索和贝叶斯优化中的所有数字超参数。垂直线再次暗示每次搜索的超参数的最佳值:
完整介绍用于Python中自动超参数调剂的贝叶斯优化-6.jpg (72.74 KB, 下载次数: 0)
2018-7-4 23:45 上传
在年夜大都情况下(除subsample_for_bin),贝叶斯优化搜索倾向于集中(放置更多几率)在超参数值四周,从而产生交叉验证中的最低损失。这显示了使用贝叶斯体例进行超参数调剂的基本思想:花费更多时间来评估有希望的超参数值。
此处还有一些有趣的结果可能会帮忙我们在将来界说要搜索的域空间时。作为一个例子,它看起来像reg_alpha和reg_lambda应该相互弥补:如果是高(接近1.0),其他的要低。不克不及包管这会解决问题,但通过研究结果,我们可以获得可能适用于未来机器学习问题的见解!
搜索的演变
随着优化的进展,我们期望贝叶斯体例关注超参数的更有希望的值:那些在交叉验证中产生最低误差的值。我们可以绘制超参数与迭代的值,以查看是否存在明显的趋势。
完整介绍用于Python中自动超参数调剂的贝叶斯优化-7.jpg (171.97 KB, 下载次数: 0)
2018-7-4 23:45 上传
黑星暗示最佳值。在colsample_bytree和learning_rate随着时间的推移可能引导我们未来的搜索下降。
完整介绍用于Python中自动超参数调剂的贝叶斯优化-8.jpg (100.43 KB, 下载次数: 0)
2018-7-4 23:45 上传
最后,如果贝叶斯优化工作正常,我们预计平均验证分数会随着时间的推移而增加(相反,损失减少):
完整介绍用于Python中自动超参数调剂的贝叶斯优化-9.jpg (62.33 KB, 下载次数: 0)
2018-7-4 23:45 上传
来自贝叶斯超参数优化的验证分数随着时间的推移而增加,表白该体例正在测验考试“更好”的超参数值(应该注意,这些仅根据验证分数更好)。随机搜索没有显示迭代的改进。
继续搜索
如果我们对模型的性能不满意,我们可以继续使用Hyperopt进行搜索。我们只需要传入相同的试验对象,算法将继续搜索。
随着算法的进展,它会进行更多的挖掘 - 挑选过去表示良好的价值 - 而不是探索 - 挑选新价值。因此,开始完全不合的搜索可能是一个好主意,而不是继续搜索停止的处所。如果来自第一次搜索的最佳超参数确实是“最优的”,我们希望后续搜索专注于相同的值。鉴于问题的高维度以及超参数之间的复杂交互,另一个搜索不太可能致使类似的超参数集。
经过另外500次训练后,最终模型在测试集上得分为0.72736 ROC AUC。(我们实际上不该该评估测试集上的第一个模型,而只依赖于验证分数。理想情况下,测试集应该只使用一次,以便在摆设到新数据时获得算法性能的怀抱)。同样,由于数据集的小尺寸,这个问题可能致使进一步超参数优化的收益递减,并且最终会呈现验证毛病的平台(由于隐藏变量致使数据集上任何模型的性能存在固有限制未丈量和噪声数据,称为贝叶斯误差。
结论
可以使用贝叶斯优化来完成机器学习模型的自动超参数调剂。与随机搜索相比,贝叶斯优化以知情体例选择下一个超参数,以花更多时间来评估有希望的值。与随机或网格搜索相比,最终结果可以是对目标函数的评估较少以及测试集上的更好的泛化性能。
在本文中,我们使用Hyperopt逐步完成了Python中的贝叶斯超参数优化。除基线和随机搜索之外,我们还能够提高梯度增强机的测试集性能,尽管我们需要谨慎看待训练数据的过度拟合。另外,我们通过检查结果图表看到随机搜索与贝叶斯优化的不合之处,这些图表显示贝叶斯体例对超参数值的几率更年夜,致使交叉验证损失更低。
使用优化问题的四个部分,我们可以使用Hyperopt来解决各种各样的问题。贝叶斯优化的基本部分也适用于Python中实现不合算法的许多库。从手动切换到随机或网格搜索只是一小步,但要将机器学习提升到新的水平,需要一些自动形式的超参数调剂。贝叶斯优化是一种易于在Python中使用的体例,并且可以比随机搜索返回更好的结果。希望您现在有信心开始使用这种强年夜的体例来解决您自己的机器学习问题!
更多内容回复查看:
游客,如果您要查看本帖隐藏内容请回复