跳至主要内容

掌握数据科学中的贝叶斯优化

释放贝叶斯优化在机器学习超参数调优中的威力。掌握理论基础与 Python 实战,提升模型准确率。
· 11分钟 阅读

使用 AI 探索

在广阔的机器学习领域中,在模型训练期间于庞大而复杂的数据集中寻找到最优超参数是一项常见且棘手的挑战。

传统方法通常基于随机搜索和网格搜索,往往耗时且效率不高,尤其是在处理复杂模型架构或大规模数据时更是如此。

这正是需要更智能、更自适应的方法(如贝叶斯优化)来进行超参数优化的原因。

本文的首要目标是解释什么是贝叶斯优化,为读者提供坚实的理论基础。不过,在深入介绍贝叶斯优化之前,先了解我们手头可用的不同超参数优化方法同样重要。

文章的第二部分将使用 Python 编程语言更为实用地实现这些关键概念。在最后一节,读者将学习如何利用贝叶斯优化来为机器学习模型进行超参数调优。

什么是贝叶斯优化?

在给出贝叶斯优化的定义之前,我们先通过简单示例概览四种主要的超参数优化方法:

  • 手动搜索
  • 随机搜索
  • 网格搜索,及
  • 贝叶斯优化。

手动搜索

这是最基础的方法,依赖经验、直觉或反复试验。通常十分耗时,并且高度依赖实践者的专业知识。

例如,一位经验丰富的烘焙师会根据以往经验和直觉设定烤箱温度和烘烤时间。

一个典型的起点可能是 350°F、烘烤 30 分钟,这反映了较为常见且成功的设置。随后会根据每次烘焙的结果对这些设置进行调整。

随机搜索

这种方法会在指定范围内随机选择温度和时间的组合。例如,温度可能在 325°F 到 375°F 之间变化,时间在 25 到 35 分钟之间变化。

选择过程可能先从 330°F、27 分钟开始,然后是 370°F、31 分钟,如此随机继续,直到找出最有效的组合。

网格搜索

该方法系统性地遍历多种超参数取值组合。

继续沿用烘焙的例子。烘焙师会在墙上创建一个有结构的温度与时间组合网格。

该网格可能包含 325°F 到 375°F 之间每 5°F 的温度增量,以及 25 到 35 分钟之间每 2 分钟的时间增量。然后会按照网格中的每一个组合进行烘焙(325°F 烤 25 分钟、325°F 烤 27 分钟,…,375°F 烤 35 分钟),以找到最佳组合。

贝叶斯搜索

与随机搜索相比,贝叶斯优化可以被视为一种更智能的方式来寻找蛋糕的最佳烘焙温度和时间。

随机搜索可能让烘焙师随机尝试大量温度和时间组合,其中许多会导致蛋糕未熟或过熟。

在贝叶斯优化中,烘焙师对最优烘焙条件的搜索会由之前的结果加以引导和启发。

例如,如果烘焙师发现大约 350°F、烘烤 30 分钟的蛋糕更容易成功,那么在后续尝试中就会更多关注接近这些数值的温度和时间组合。

这种方法减少了所需的试验次数,因为它会根据之前烘焙结果“学习”哪些组合更可能烤出完美的蛋糕。

总体而言,贝叶斯优化基于贝叶斯定理,用于优化评估代价高昂的目标函数。它在采样成本高、目标函数未知但可采样的情境中尤为有效。

贝叶斯优化通常采用概率模型(如高斯过程)来估计目标函数,然后使用采集函数决定下一步在哪里进行采样。

贝叶斯优化的挑战

该方法已在多个领域成功应用。但需要注意的是,它也存在一些挑战,主要如下:

批量/并行评估

一个挑战是如何高效地进行批量或并行评估。传统上,贝叶斯优化以顺序方式运行,每次选择一个点进行评估。将其扩展到同时评估多个点的批处理场景并非易事,需要在所选点的多样性与效率之间进行谨慎平衡。

非短视优化

这指的是在决定评估哪些点时进行长期、策略性选择的挑战。非短视方法会考虑当前评估对未来的影响,这在计算上可能开销很大且建模复杂。

空间维度

随着搜索空间维度的增加,贝叶斯优化的效果可能下降。这源于“维度灾难”:空间体积呈指数增长,在有限评估次数下更难找到最优解。

结构化搜索空间

在许多现实问题中,搜索空间并非简单的连续 n 维空间,而是具有复杂结构,例如变量之间的依赖关系或各种约束。如何高效处理这些结构化空间是一个重大挑战。

多任务/多目标搜索,多保真度搜索

将贝叶斯优化扩展到同时处理多个目标,或集成不同保真度(精度)评估的信息,具有相当的复杂性。这些场景需要在不同目标之间,或不同评估细节层级之间进行权衡。

热启动搜索

这涉及利用先验知识或数据来加速优化过程。如何在不引入偏差的前提下,将这些信息有效纳入贝叶斯框架,是一项挑战。

大量评估

尽管贝叶斯优化旨在用于评估代价高昂的场景,但在某些情况下仍可能需要进行大量评估。如何管理并高效地在这些评估中导航,尤其是在计算资源与时间方面,依旧具有挑战性。

棘手的目标函数

当目标函数具有众多局部最优、不连续性或非平稳性(函数随时间变化)时,问题会更加困难。这类函数可能误导优化过程,使得寻找全局最优解变得艰难。

间接观测

在某些情境下,目标函数无法被直接观测,只能通过间接测量获得。这会增加额外复杂度,因为优化算法需要从这些间接观测中推断出真实的目标函数。

采集函数与决策

在贝叶斯优化中,采集函数用于引导对最优值的搜索。

采集函数与代理模型在贝叶斯优化中相互依存。

代理模型对目标函数给出概率性的预测,采集函数据此决定下一步最值得评估的点。此外,当目标函数复杂或直接评估代价高昂时,诸如高斯过程回归等代理模型尤为有用。

进一步地,随着不断用新数据点更新代理模型,采集函数可以更准确地指向搜索空间中最可能产生最佳结果的区域。

贝叶斯优化中可以使用多种采集函数,包括:

  • Thompson 采样:基于对目标函数的概率估计来选择点。
  • 上置信界(UCB):选择最有可能提升目前最佳观测值的点。它利用潜在新解的结果与当前最佳之间的差异,计算改进的概率。
  • 改进概率(PI):选择最有可能达到目前最佳观测值的点。
  • 期望改进(EI):选择对当前最佳观测值具有最高期望改进的点。

不存在绝对最好或最差的函数。每种采集函数都有其优势,需根据具体优化问题的需求来选择。

贝叶斯优化的应用

贝叶斯优化已在诸多行业中成功应用,本节将以四个用例加以说明:

  • 自然语言处理
  • 机器学习与超参数调优
  • A/B 测试,及
  • 强化学习与机器人

这些示例对数据科学家尤为相关且富有启发。

自然语言处理

在将机器学习应用于自然语言处理问题时,需要在文本表示方式上做出多重选择,而这些选择会影响底层模型的最终效果。

贝叶斯优化可用于自动化选择 NLP 模型中文本表示的最佳方式,从而简化模型开发流程并提高效率,同时保持甚至提升模型的整体性能。

若想进一步了解嵌入表示,我们的课程Introduction to Embeddings with OpenAI API提供了使用 OpenAI API 创建嵌入的完整指南。

机器学习与超参数调优

在自动化机器学习(AutoML)中,贝叶斯优化可自动为给定数据集上的给定问题选择最佳模型及其超参数。

近期的应用显示,贝叶斯优化在调优复杂模型(如深度置信网络、卷积神经网络和马尔可夫链蒙特卡罗方法)方面效果显著。

此外,它也有助于自动化选择诸如 WEKA 和 scikit-learn 等流行机器学习库所提供的不同模型。

A/B 测试

在 A/B 测试中,贝叶斯优化用于提升对不同产品配置(如广告、应用、游戏或网站)的测试效率。

通过向一小部分用户展示两个选项(A 和 B),开发者收集反馈以优化诸如用户参与度或点击率等指标。关键挑战是在用户查询预算有限的前提下找出最佳产品配置。

贝叶斯优化通过利用先前测试的反馈来做出更明智的决策,决定下一步应查询哪些用户子集,力求在最小化机会成本的同时优化产品的整体表现。

强化学习与机器人

贝叶斯优化已在机器人与强化学习中得到有效应用。

在机器人领域,它被用于针对特定目标(如速度或平滑度)优化机器人的步态,Sony AIBO ERS-7 机器人的相关研究即为例证。它也被用于导航任务,帮助机器人在移动过程中尽量减少关于其位置与地图估计的不确定性。

在分层强化学习中,贝叶斯优化被用于自动调节神经网络策略的参数,并在分层系统的不同层级学习价值函数。

此外,该技术还被用于借助深度网络在图像跟踪中开发注意力策略,展现了其在复杂环境中适应学习与决策不同方面的多样性。

实现贝叶斯优化的分步指南

前文已对贝叶斯优化进行了全面阐述。本节将演示如何使用 GPyOpt 库来优化 XGBRegressor 的超参数。

PyMC3 是另一款用于贝叶斯优化的强大库,我们的课程Bayesian Data Analysis in Python提供了完整指南及一些真实世界的示例。

如果您希望使用 R 语言进行贝叶斯优化,我们的课程Fundamentals of Bayesian Data Analysis in R将是合适之选。

我们将应用两种方法:网格搜索与贝叶斯优化。目标是确定哪一种能提供最佳超参数、从而得到更好的模型。完整源码可在此 DataLab 工作簿中获取;您可以创建自己的副本,在浏览器中编辑并运行代码,而无需在本地安装任何软件。

先决条件

成功运行上述两种技术需要以下库。

  • numpy:Python 的数值计算库。
  • scipy:Python 的科学计算库。
  • sklearn:Python 的机器学习库。
  • GPyOpt:Python 的贝叶斯优化库。
  • Xgboost:用于 XGBoost 模型

这些库可以在 Jupyter notebook 中通过如下 pip 命令安装:

!pip -q install xgboost scikit-learn GPyOpt numpy

成功运行上述代码后,使用 import 语句进行导入:

import xgboost as xgb
from sklearn.datasets import fetch_california_housing as fch
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import RandomizedSearchCV
from GPyOpt.methods import BayesianOptimization
from sklearn.model_selection import cross_val_score
import numpy as np

数据集准备

基准分析基于 California housing 数据集。

  • 70% 的数据用于训练 XGBoost 模型
  • 其余 30% 用于测试

训练集与测试集划分如下:

# Load dataset
california_housing = fch()

X = california_housing.data
y = california_housing.target

# Split the dataset into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                	test_size=0.3,
                                                	random_state=2024)

加载训练和测试数据后,我们可以使用 .shape 函数查看它们的尺寸。

print(f"Training Shape: {X_train.shape}")
print(f"Testing Shape: {X_test.shape}")

使用随机搜索进行超参数调优

本节说明如何使用 scikit-learn 模块中的 RandomizedSearchCV 类,以网格搜索思路为 XGBoost 回归设置并执行随机搜索的超参数调优。

设置参数网格

创建名为 param_dist 的字典,以不同超参数为键、其可能取值列表为值。这些超参数包括:

  • max_depth:树的最大深度。取值:3、10、5、15。
  • min_child_weight:子节点所需的实例权重(Hessian)和的最小值。取值:1、5、10。
  • subsample:训练样本的子采样比例。取值:0.5、0.7、1.0。
  • colsample_bytree:构建每棵树时列的子采样比例。取值:0.5、0.7、1.0。
  • n_estimators: 梯度提升树的数量,即 boosting 轮数。取值:100、200、300、400。
param_dist = {
	'max_depth': [3, 10, 5, 15],
	'min_child_weight': [1, 5, 10],
	'subsample': [0.5, 0.7, 1.0],
	'colsample_bytree': [0.5, 0.7, 1.0],
	'n_estimators': [100, 200, 300, 400],
	'learning_rate': [0.01, 0.05, 0.1, 0.15, 0.2]
}

初始化 XGBoost 回归器

创建 XGBoost 库中 XGBRegressor 类的实例:

xgb_reg = xgb.XGBRegressor()

配置 RandomizedSearchCV

按以下参数设置 RandomizedSearchCV:

  • 估计器(xgb_reg)为 XGBoost 回归器。
  • param_distributions=param_dist 指定要采样的参数字典。
  • n_iter=25 表示将尝试 25 种不同的组合。
  • scoring='neg_mean_squared_error' 将评分指标设为负均方误差。
  • cv=3 将交叉验证的折数设为 3。
  • verbose=1 开启详细输出。
  • random_state=42 确保结果可复现。
random_search = RandomizedSearchCV(
	xgb_reg, param_distributions=param_dist, n_iter=25,
	scoring='neg_mean_squared_error', cv=3, verbose=1, random_state=2024
)

拟合模型并获取参数

使用 fit 方法在给定的训练数据(X_train 与 y_train)上运行随机搜索:

random_search.fit(X_train, y_train)

拟合后模型的 best_params 属性包含可带来更好性能的最佳超参数信息。

可以如下获取这些参数:

print("Random Search Best Parameters:", random_search.best_params_)

image2.png

随机搜索的最佳参数

在所有可能的参数中,使用随机搜索得到的 XGBoost 模型最佳参数如下:

'subsample': 0.5,
'n_estimators': 400,
'min_child_weight': 5,
'max_depth': 10,
'learning_rate': 0.05,
'colsample_bytree': 1.0
}

评估模型

使用这些取值,我们可以如下评估模型并获取其性能:

首先,在训练前用最佳超参数初始化模型。

# Initialize and train the model
model_random_search = xgb.XGBRegressor(**params_random_search)
model_random_search.fit(X_train, y_train)

然后,在测试数据上评估模型。

predictions_random_search = model_random_search.predict(X_test)
mse_random_search = mean_squared_error(y_test, predictions_random_search)
print("MSE for Random Search: ", mse_random_search)

成功执行以上代码后,会得到如下均方误差(MSE):

使用随机搜索超参数的 XGBosst 的 MSE

使用贝叶斯优化进行超参数调优

与随机搜索类似,我们按步骤应用贝叶斯优化来生成最佳超参数。

设置参数边界

在列表 baysian_opt_bounds 中定义超参数的边界。每个超参数用包含 name、type 和 domain 的字典表示。

  • max_depth、min_child_weight、n_estimators 为离散变量。
  • subsample、colsample_bytree、learning_rate 为连续变量。
  • domain 指定每个超参数的取值范围。
baysian_opt_bounds = [
	{'name': 'max_depth', 'type': 'discrete', 'domain': (3, 10, 5, 15)},
	{'name': 'min_child_weight', 'type': 'discrete', 'domain': (1, 5, 10)},
	{'name': 'subsample', 'type': 'continuous', 'domain': (0.5, 1.0)},
	{'name': 'colsample_bytree', 'type': 'continuous', 'domain': (0.5, 1.0)},
	{'name': 'n_estimators', 'type': 'discrete', 'domain': (100, 200, 300, 400)},
	{'name': 'learning_rate', 'type': 'continuous', 'domain': (0.01, 0.2)}
]

定义目标函数

定义目标函数 xgb_cv_score,用于计算 XGBoost 模型的交叉验证负均方误差。

  • 函数接收一组参数,并将其转换为所需格式。
  • 使用 cross_val_score 按指定参数评估 XGBoost 模型。
  • 返回交叉验证得分的负均值,作为需要最小化的目标。
def xgb_cv_score(parameters):
	parameters = parameters[0]
	score = -cross_val_score(
            	xgb.XGBRegressor(
                	max_depth=int(parameters[0]),
                	min_child_weight=int(parameters[1]),
                	subsample=parameters[2],
                	colsample_bytree=parameters[3],
                	n_estimators=int(parameters[4]),
                	learning_rate=parameters[5]),
            	X_train, y_train, scoring='neg_mean_squared_error', cv=3).mean()
	return score

初始化并运行贝叶斯模型

使用以下参数实例化贝叶斯优化:

  • f=xgb_cv_score:要最小化的目标函数。
  • domain=baysian_opt_bounds:超参数边界。
  • model_type='GP':指定使用高斯过程。
  • acquisition_type='EI':使用期望改进作为采集函数。
  • max_iter=25:优化过程的迭代次数。

通过 optimizer.run_optimization() 启动优化过程

optimizer = BayesianOptimization(
	f=xgb_cv_score, domain=baysian_opt_bounds, model_type='GP',
	acquisition_type='EI', max_iter=25
)
optimizer.run_optimization()

获取最佳参数

优化结束后,提取并格式化最佳参数:

  • 从 optimizer.x_opt 中获取参数。
  • 将离散参数(max_depth、min_child_weight、n_estimators)转换为整数。

打印最佳参数:

print("Bayesian Optimization Best Parameters:", best_params_bayesian)

Bayesian optimization best parameters

贝叶斯优化的最佳参数

评估模型

初始化与拟合过程与之前相同,但这一次使用贝叶斯优化得到的参数:

params_bayesian_opt = {
	'max_depth': 10,
	'min_child_weight': 10,
	'subsample': 0.820222997732141,
	'colsample_bytree': 0.6710357796023916,
	'n_estimators': 300,
	'learning_rate': 0.04797554348030097
}

# Initialize and train the model
model_bayesian_opt = xgb.XGBRegressor(**params_bayesian_opt)
model_bayesian_opt.fit(X_train, y_train)

# Make predictions and evaluate
predictions_bayesian_opt = model_bayesian_opt.predict(X_test)
mse_bayesian_opt = mean_squared_error(y_test, predictions_bayesian_opt)
print("MSE for Bayesian Optimization: ", mse_bayesian_opt)

成功执行以上代码后会输出模型性能,结果如下:

MSE for XGBosst using Random search hyperparameters

使用贝叶斯优化超参数的 XGBosst 的 MSE

结果解读

从两组实验可以看到:

  • 贝叶斯优化的 MSE 更低:使用贝叶斯优化超参数训练的模型,其 MSE(0.204)低于随机搜索得到的模型(0.219)。更低的数值表明模型的预测平均上更接近真实数据点。
  • 模型性能:更低的 MSE 表明贝叶斯优化在为 XGBoost 模型调参方面更有效。结果暗示由贝叶斯优化调参的模型更能从训练数据泛化到未见数据(此处为测试集)。
  • 贝叶斯优化的效率:贝叶斯优化通常能带来更好的调参表现,因为它使用概率模型来引导寻找最优参数。它会从先前评估中学习,并将搜索引向更有前景的超参数,往往能找到优于随机搜索的取值。

在本例中,贝叶斯优化提供了更好的结果,体现在更低的 MSE 上。这表明对于该数据集与模型(XGBoost),贝叶斯优化在识别能最小化测试集预测误差的超参数集合方面更为有效。

需要注意的是,尽管此处贝叶斯优化表现更好,但在随机搜索与贝叶斯优化之间做选择仍取决于诸多因素,如模型复杂度、数据性质以及计算资源。

然而,在对超参数调优精度要求较高且计算资源允许的情境下,贝叶斯优化往往是更优选择。

结论

本文深入探讨了贝叶斯优化这一用于优化复杂函数的关键技术。

我们首先定义了什么是贝叶斯优化,随后讨论了函数优化中的挑战,从而引出贝叶斯优化的相关性。

通过实践指南,我们深入到实现层面,重点关注高斯过程回归与采集函数。相当一部分讨论围绕将贝叶斯优化应用于机器学习模型的超参数调优,展示了其在现实世界中的实用性。

总体而言,本文既是入门概览也是实用指南,突出了贝叶斯优化在机器学习与计算优化中的关键作用。

对于相关领域的从业者、研究者和爱好者而言,本文提供了有价值的见解,强调了该方法在现代计算挑战中的适用性与影响力。

如果您希望进一步深入学习贝叶斯优化及其在 Python 中的实现,我们的课程Hyperparameter Tuning in Python将带您用 Scikit Learn 库实践常见的自动化超参数调优方法,其中包括贝叶斯与遗传算法等进阶方法。

Zoumana Keita

作者

Zoumana Keita

一位多才多艺、乐于分享知识并回馈他人的数据科学家。Zoumana 是 YouTube 内容创作者,也是 Medium 上的顶尖科技写作者。他热爱演讲、编程与教学。Zoumana 拥有两个硕士学位:其一是在法国巴黎获得的计算机科学硕士,专注于机器学习;其二是在美国德州理工大学获得的数据科学硕士。他的职业生涯始于法国 Groupe OPEN 的软件开发工程师,随后加入 IBM 担任机器学习顾问,为保险公司构建端到端的 AI 解决方案。之后,Zoumana 加入了 Axionable——一家总部位于巴黎与蒙特利尔的首家“可持续 AI”初创公司。在那里,他担任数据科学家,为来自法国、蒙特利尔、新加坡和瑞士的客户交付 AI 产品,主要聚焦 NLP 场景。此外,他将 5% 的时间投入到研发工作。目前,他在 IFC(世界银行集团)担任高级数据科学家。

主题