python编辑贝叶斯公式 (提高贝叶斯模型精度方法python)

python贝叶斯算法讲解,python实现朴素贝叶斯代码演示

调整机器学习超参数是一项繁琐但至关重要的任务,因为算法的性能可能高度依赖于超参数的选择。手动调整需要时间远离机器学习管道的重要步骤,如特征工程和解释结果。网格和随机搜索是不干涉的,但需要很长的运行时间,因为它们浪费时间探索搜索空间中没有希望的区域。越来越多的超参数调整任务是通过自动化方法完成的,这些方法旨在使用明智的搜索在更短的时间内找到最佳的超参数,而无需在初始设置之外进行手动操作。

贝叶斯优化是一种用于找到函数最小值的基于模型的方法,最近已应用于机器学习超参数调整,结果表明该方法可以在测试集上实现更好的性能,同时比随机搜索需要更少的迭代。此外,现在有许多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贝叶斯算法讲解,python实现朴素贝叶斯代码演示

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 = lgb.cv(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 = lgb.cv(...) 。为了实现早期停止的交叉验证,我们使用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,

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贝叶斯算法讲解,python实现朴素贝叶斯代码演示

现在,让我们定义整个域:

# 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。

当我们看结果的时候,有一些重要的注意事项要记住:

  1. 最佳超参数是那些在交叉验证方面表现最佳的参数,而不一定是那些在测试数据上做得最好的参数。当我们使用交叉验证时,我们希望这些结果可以推广到测试数据。
  2. 即使使用10倍交叉验证,超参数调整也会过度拟合训练数据。交叉验证的最佳分数显着高于测试数据。
  3. 随机搜索可以通过纯粹的运气返回更好的超参数(重新运行可以改变结果)。贝叶斯优化不能保证找到更好的超参数,并且可能陷入目标函数的局部最小值。

贝叶斯优化是有效的,但它不能解决所有的优化问题。随着搜索的进行,算法从探索——尝试新的超参数值——转换到利用——使用导致目标函数损失最小的超参数值。如果该算法找到目标函数的局部最小值,那么它可能会集中在围绕局部最小值的超参数值上,而不是尝试位于远域空间的不同值。随机搜索不受这个问题的影响,因为它不关注任何值!

另一个重要的一点是,超参数优化的好处与数据集不同。这是一个相对较小的数据集(~ 6000个训练观察),调优超参数有一个小的回报(获得更多的数据会更好地利用时间!)考虑到所有这些注意事项,在这种情况下,通过贝叶斯优化我们可以得到:

  • 在测试集上有更好的性能
  • 调整超参数的迭代次数减少。

贝叶斯方法可以(尽管并非总是)产生比随机搜索更好的调优结果。在接下来的几节中,我们将研究贝叶斯超参数搜索的演变,并与随机搜索进行比较,以了解贝叶斯优化是如何工作的。

可视化搜索结果

绘制结果图表是一种直观的方式,可以了解超参数搜索过程中发生的情况。此外,将贝叶斯优化与随机搜索进行比较是有帮助的,这样我们就可以看出方法的不同之处。(所有这些图都是500次迭代)

首先,我们可以learning_rate在随机搜索和贝叶斯优化中制作采样的核密度估计图。作为参考,我们还可以显示采样分布。垂直虚线表示学习率的最佳值(根据交叉验证)。

python贝叶斯算法讲解,python实现朴素贝叶斯代码演示

我们将学习率定义为0.005到0.2之间的对数正态,贝叶斯优化结果看起来与采样分布类似。这告诉我们,我们定义的分布看起来适合于任务,尽管最佳值比我们放置最大概率的值略高。这可用于通知域进一步搜索。

另一个超参数是增强类型,在随机搜索和贝叶斯优化期间评估每种类型的条形图。由于随机搜索不关注过去的结果,我们预计每种增强类型的使用次数大致相同。

python贝叶斯算法讲解,python实现朴素贝叶斯代码演示

根据贝叶斯算法,gdbt增强型比dart或更有希望goss。同样,这可以帮助进一步搜索,贝叶斯方法或网格搜索。如果我们想要进行更明智的网格搜索,我们可以使用这些结果来定义围绕超参数最有希望的值的较小网格。

由于我们有它们,让我们看看参考分布,随机搜索和贝叶斯优化中的所有数字超参数。垂直线再次表示每次搜索的超参数的最佳值:

python贝叶斯算法讲解,python实现朴素贝叶斯代码演示

在大多数情况下(除了subsample_for_bin),贝叶斯优化搜索倾向于集中(放置更多概率)在超参数值附近,从而产生交叉验证中的最低损失。这显示了使用贝叶斯方法进行超参数调整的基本思想:花费更多时间来评估有希望的超参数值。

此处还有一些有趣的结果可能会帮助我们在将来定义要搜索的域空间时。作为一个例子,它看起来像reg_alpha和reg_lambda应该相互补充:如果是高(接近1.0),其他的要低。不能保证这会解决问题,但通过研究结果,我们可以获得可能适用于未来机器学习问题的见解!

搜索的演变

随着优化的进展,我们期望贝叶斯方法关注超参数的更有希望的值:那些在交叉验证中产生最低误差的值。我们可以绘制超参数与迭代的值,以查看是否存在明显的趋势。

python贝叶斯算法讲解,python实现朴素贝叶斯代码演示

黑星表示最佳值。在colsample_bytree和learning_rate随着时间的推移可能引导我们未来的搜索下降。

python贝叶斯算法讲解,python实现朴素贝叶斯代码演示

最后,如果贝叶斯优化工作正常,我们预计平均验证分数会随着时间的推移而增加(相反,损失减少):

python贝叶斯算法讲解,python实现朴素贝叶斯代码演示

来自贝叶斯超参数优化的验证分数随着时间的推移而增加,表明该方法正在尝试“更好”的超参数值(应该注意,这些仅根据验证分数更好)。随机搜索没有显示迭代的改进。

继续搜索

如果我们对模型的性能不满意,我们可以继续使用Hyperopt进行搜索。我们只需要传入相同的试验对象,算法将继续搜索。

随着算法的进展,它会进行更多的挖掘 - 挑选过去表现良好的价值 - 而不是探索 - 挑选新价值。因此,开始完全不同的搜索可能是一个好主意,而不是继续搜索停止的地方。如果来自第一次搜索的最佳超参数确实是“最优的”,我们希望后续搜索专注于相同的值。鉴于问题的高维度以及超参数之间的复杂交互,另一个搜索不太可能导致类似的超参数集。

经过另外500次训练后,最终模型在测试集上得分为0.72736 ROC AUC。(我们实际上不应该评估测试集上的第一个模型,而只依赖于验证分数。理想情况下,测试集应该只使用一次,以便在部署到新数据时获得算法性能的度量)。同样,由于数据集的小尺寸,这个问题可能导致进一步超参数优化的收益递减,并且最终会出现验证错误的平台(由于隐藏变量导致数据集上任何模型的性能存在固有限制未测量和噪声数据,称为贝叶斯误差。

结论

可以使用贝叶斯优化来完成机器学习模型的自动超参数调整。与随机搜索相比,贝叶斯优化以知情方法选择下一个超参数,以花更多时间来评估有希望的值。与随机或网格搜索相比,最终结果可以是对目标函数的评估较少以及测试集上的更好的泛化性能。

在本文中,我们使用Hyperopt逐步完成了Python中的贝叶斯超参数优化。除了基线和随机搜索之外,我们还能够提高梯度增强机的测试集性能,尽管我们需要谨慎对待训练数据的过度拟合。此外,我们通过检查结果图表看到随机搜索与贝叶斯优化的不同之处,这些图表显示贝叶斯方法对超参数值的概率更大,导致交叉验证损失更低。

使用优化问题的四个部分,我们可以使用Hyperopt来解决各种各样的问题。贝叶斯优化的基本部分也适用于Python中实现不同算法的许多库。从手动切换到随机或网格搜索只是一小步,但要将机器学习提升到新的水平,需要一些自动形式的超参数调整。贝叶斯优化是一种易于在Python中使用的方法,并且可以比随机搜索返回更好的结果。希望您现在有信心开始使用这种强大的方法来解决您自己的机器学习问题!