在 Ray Tune 中使用 BayesOptSearch 进行贝叶斯超参数优化
【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray
导读
本文围绕 Ray Tune 官方示例 bayesopt_example.py(对应文档 bayesopt_example.rst 与配套 Notebook bayesopt_example.ipynb),完整讲解如何将bayesian-optimization库的高斯过程贝叶斯优化集成到 Ray Tune 中,用BayesOptSearch搜索算法自动寻找最优超参数。读完本文,你将掌握:环境安装与版本约束、目标函数与搜索空间的组织方式、BayesOptSearch全部核心参数(utility 函数、并发限制、随机初始化步数、收敛提前终止等)的语义与默认值,以及与AsyncHyperBandScheduler调度器组合使用的完整可运行代码,并能对照 搜索算法源码 理解底层实现原理。
BayesOpt 与 Ray Tune 的集成方式
什么是 BayesOptSearch
BayesOptSearch是 Ray Tune 为bayesian-optimization库(pip 包名bayesian-optimization,类名为bayes_opt.BayesianOptimization)封装的搜索算法(Searcher),代码位于 python/ray/tune/search/bayesopt/bayesopt_search.py。
从原理上讲,BayesianOptimization是一个基于高斯过程(Gaussian Process)的受限全局优化工具,核心目标是用尽可能少的迭代次数逼近未知函数的最大值(Ray Tune 内部通过_metric_op符号翻转支持最小化目标,见源码第 205-208 行)。它属于"无导数"(derivative-free)与"黑盒"(black-box)优化范畴,特别适合目标函数评估代价高昂、需要权衡探索(exploration)与利用(exploitation)的场景。
Ray Tune 的定位是"优化任意显式或隐式目标函数"——超参数调优只是最常见的应用。BayesOptSearch在 Tune 中扮演"搜索算法"(Search Algorithm)角色,负责根据历史 trial 的结果建议下一组超参数配置;它通常与"调度器"(Scheduler,如提前停止低效 trial 的AsyncHyperBandScheduler)协同工作。在 doc/source/tune/key-concepts.rst 的搜索算法总览表中,BayesOptSearch被归类为"Bayesian Optimization"类集成,官方推荐示例即本文所讲的 bayesopt_example。
安装与版本约束
示例源码 docstring 与BayesOptSearch.__init__中的断言均明确要求先安装 BayesOpt 库:
pip install bayesian-optimization==1.4.3需要说明的是,仓库内不同文档记录的版本号并不完全一致:
- bayesopt_search.py 与构造函数断言中推荐
bayesian-optimization==1.4.3; - 配套 Notebook bayesopt_example.ipynb 中安装命令为
!pip install -q bayesian-optimization==1.2.0 "ray[tune]"(该 Notebook 生成于 2022 年,属于历史运行环境)。
实际使用时建议以当前仓库源码标注的1.4.3为准,同时通过pip install "ray[tune]"安装 Ray Tune。若未安装 BayesOpt,构造BayesOptSearch会直接抛出断言错误(源码第 168-171 行),提示安装命令。
完整示例:最小化一个简单目标函数
以下代码来自 python/ray/tune/examples/bayesopt_example.py,是官方文档通过literalinclude直接嵌入到 RST 中的正文(见 bayesopt_example.rst),它同时验证了BayesOptSearch可以与其他调度器(此处为AsyncHyperBandScheduler)组合使用:
"""This example demonstrates the usage of BayesOpt with Ray Tune. It also checks that it is usable with a separate scheduler. Requires the BayesOpt library to be installed (`pip install bayesian-optimization`). """ import time from ray import tune from ray.tune.schedulers import AsyncHyperBandScheduler from ray.tune.search import ConcurrencyLimiter from ray.tune.search.bayesopt import BayesOptSearch def evaluation_fn(step, width, height): return (0.1 + width * step / 100) ** (-1) + height * 0.1 def easy_objective(config): # Hyperparameters width, height = config["width"], config["height"] for step in range(config["steps"]): # Iterative training function - can be any arbitrary training procedure intermediate_score = evaluation_fn(step, width, height) # Feed the score back back to Tune. tune.report({"iterations": step, "mean_loss": intermediate_score}) time.sleep(0.1) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument( "--smoke-test", action="store_true", help="Finish quickly for testing" ) args, _ = parser.parse_known_args() algo = BayesOptSearch(utility_kwargs={"kind": "ucb", "kappa": 2.5, "xi": 0.0}) algo = ConcurrencyLimiter(algo, max_concurrent=4) scheduler = AsyncHyperBandScheduler() tuner = tune.Tuner( easy_objective, tune_config=tune.TuneConfig( metric="mean_loss", mode="min", search_alg=algo, scheduler=scheduler, num_samples=10 if args.smoke_test else 1000, ), run_config=tune.RunConfig(name="my_exp"), param_space={ "steps": 100, "width": tune.uniform(0, 20), "height": tune.uniform(-100, 100), }, ) results = tuner.fit() print("Best hyperparameters found were: ", results.get_best_result().config)运行方式:
python python/ray/tune/examples/bayesopt_example.py # 完整运行 1000 次采样 python python/ray/tune/examples/bayesopt_example.py --smoke-test # 快速验证,仅 10 次采样代码结构拆解
示例由三部分构成,每一部分都对应 Tune 的一个核心概念:
目标函数(Trainable):
easy_objective(config)接收 Tune 传入的配置字典,在一个模拟训练循环中逐step计算evaluation_fn(step, width, height)的中间得分,并通过tune.report(...)将iterations与mean_loss实时回报给 Tune。time.sleep(0.1)用于模拟真实训练中每步的耗时,使贝叶斯优化在高代价目标上的价值更直观。注释也明确说明:这里可以是任意训练过程(如 PyTorch / TensorFlow 训练循环)。搜索空间(Search Space):
param_space中steps是固定参数(每次采样都是 100),width与height是连续超参数,分别采样自tune.uniform(0, 20)和tune.uniform(-100, 100)。关键假设是:最优超参数位于该空间之内;空间越大,在有限时间内找到它们就越难。搜索算法 + 调度器:
BayesOptSearch负责建议下一组配置,ConcurrencyLimiter(algo, max_concurrent=4)将同时运行的 trial 数限制为 4,AsyncHyperBandScheduler负责提前停止表现不佳的 trial。三者通过tune.TuneConfig(search_alg=..., scheduler=...)注入Tuner。
运行结果解读
示例以mean_loss为指标、mode="min"为目标方向运行num_samples次采样。Notebook 中记录了一次 10 trial 的实际运行输出(见 bayesopt_example.ipynb):
- 最优 trial:
mean_loss=-9.536507956046009,对应参数width=19.398197043239886、height=-95.88310114083951; - 日志目录:
~/ray_results/objective_2022-07-22_15-30-08,每个 trial 均完整执行了 100 步迭代。
结果表中neg_mean_loss列即-mean_loss,这是因为 Tune 在最大化框架下跟踪最小化目标。trial 名称前缀objective_来自目标函数名,RunConfig(name="my_exp")则用于自定义实验目录名。
BayesOptSearch 核心参数详解
对照 bayesopt_search.py 的类 docstring 与构造函数(第 153-235 行),BayesOptSearch的完整参数语义如下:
| 参数 | 默认值 | 含义与说明 |
|---|---|---|
space | None | 连续搜索空间。若以 Tune 搜索空间(如tune.uniform)传入Tuner(param_space=...),会被convert_search_space自动转换;也可直接传入BayesianOptimization原生格式{"width": (0, 20), "height": (-100, 100)} |
metric | None | 目标指标名。缺省时若指定了mode,则使用匿名指标_metric |
mode | None | "min"或"max",决定目标是最小化还是最大化(构造函数强制校验,见第 172-173 行) |
points_to_evaluate | None | 优先评估的初始参数建议(dict 列表),用于把已知较优的配置先跑一遍,帮助 GP 更快给出后续建议 |
utility_kwargs | {"kind": "ucb", "kappa": 2.576, "xi": 0.0} | 效用函数参数,与bayesian-optimization包默认值一致,见第 196-203 行 |
random_state | 42 | BayesOpt 的随机种子 |
random_search_steps | 10 | 初始随机搜索的 trial 数,用于避免贝叶斯过程初始阶段在局部过拟合 |
verbose | 0 | BayesOpt 包的日志详细程度 |
patience | 5 | 同一配置被重复建议的次数上限。GP 收敛后可能反复建议同一点,超过patience后suggest返回Searcher.FINISHED,实验提前结束(可能少于num_samples)。设patience=1则配置首次重复即停止 |
skip_duplicate | True | 是否跳过已评估过的配置。False时允许重复建议(适用于噪声目标,或希望坚持跑到num_samples) |
analysis | None | 可选的ExperimentAnalysis,用于把历史实验结果注册进高斯过程,实现 warm-start |
repeat_float_precision | 5 | 检测重复配置时对浮点数做哈希的小数精度。值越大查重越严格,被判为"重复"的配置越少(第 100-103 行) |
示例中的utility_kwargs={"kind": "ucb", "kappa": 2.5, "xi": 0.0}显式选择了 UCB(Upper Confidence Bound,上置信界)效用函数,并微调kappa=2.5(默认 2.576)。kappa控制探索强度,xi用于 EI(Expected Improvement)类效用函数中的改善期望阈值。
两种搜索空间传参方式
BayesOptSearch同时支持自动转换与手动传参两种方式(源码 docstring 第 105-146 行):
方式一:Tune 搜索空间自动转换(推荐,与示例一致):
from ray import tune from ray.tune.search.bayesopt import BayesOptSearch config = { "width": tune.uniform(0, 20), "height": tune.uniform(-100, 100), } bayesopt = BayesOptSearch(metric="mean_loss", mode="min") tuner = tune.Tuner( my_func, tune_config=tune.TuneConfig(search_alg=bayesopt), param_space=config, ) tuner.fit()方式二:手动传入 BayesOpt 原生边界格式(此时搜索空间经space参数传入,Tuner不再传param_space):
space = { 'width': (0, 20), 'height': (-100, 100), } bayesopt = BayesOptSearch(space, metric="mean_loss", mode="min") tuner = tune.Tuner( my_func, tune_config=tune.TuneConfig(search_alg=bayesopt), ) tuner.fit()底层实现原理:从源码看 BayesOptSearch 的工作流程
阅读 bayesopt_search.py 的完整实现,可以梳理出 Tune 与 BayesOpt 交互的完整闭环:
1. 初始化与搜索空间转换(__init__/set_search_properties)
- 构造函数对
mode做合法性校验(仅允许min/max),并通过_metric_op(最小化取 -1.0,最大化取 1.0)把"最小化"问题翻转为底层库的"最大化"框架(第 205-208 行); - 内部用
byo.UtilityFunction(**utility_kwargs)构建效用函数(第 217 行); - 若直接传入含
tune.uniform等 Domain 对象的space,会先parse_spec_vars解析,检测到未解析搜索空间时给出警告并调用convert_search_space(第 221-228 行); - 若搜索空间在构造时尚未就绪(例如由
Tuner(param_space=...)注入),则推迟到set_search_properties中通过convert_search_space(config)完成转换并调用_setup_optimizer(第 253-271 行),最终构造byo.BayesianOptimization(f=None, pbounds=..., verbose=..., random_state=...)(第 242-247 行)。
convert_search_space(静态方法,第 448-493 行)对搜索空间的约束很关键:
- 只支持连续
Float域:其他类型参数(如整型、类别型)会抛出ValueError(第 481-484 行); - 不支持网格搜索:检测到
grid_vars直接抛错(第 452-456 行); - 不支持量化:
Quantized采样器会被丢弃并警告(第 464-469 行); - 不支持自定义采样方法:非
Uniform采样器会被丢弃并警告(第 471-478 行); - 嵌套字典参数会以
/连接路径作为参数名(第 487 行)。
2. 建议下一个配置(suggest)
每次需要新 trial 时,Tune 调用suggest(trial_id)(第 273-356 行):
- 若设置了
points_to_evaluate,先依次弹出这些初始点(第 298-300 行); - 否则调用
self.optimizer.suggest(self.utility)让高斯过程给出下一个采样点(第 302 行); - 对配置做哈希查重(
_dict_hash,第 31-38 行按repeat_float_precision精度格式化浮点数后 JSON 序列化):- 同一配置重复建议次数超过
patience时,记录收敛警告并返回Searcher.FINISHED,实验提前终止(第 312-323 行); - 已见过且
skip_duplicate=True时跳过该配置返回None(第 325-338 行);
- 同一配置重复建议次数超过
- 前
random_search_steps个 trial 属于随机初始化阶段(第 340-350 行)。
3. 结果反馈与高斯过程更新(on_trial_complete/_register_result)
trial 结束后,Tune 回调on_trial_complete(第 373-410 行):
- 结果或参数缺失、出错时直接忽略(第 394-395 行);
- 随机初始化阶段的 trial 结果先缓存到
_buffered_trial_results,攒够random_search_steps个后一次性注册给优化器(第 398-410 行),这是为了避免初始阶段 GP 过拟合; _register_result会自动过滤 NaN、inf、-inf 结果(is_nan_or_inf判断,第 414-416 行),再以_metric_op * result[self.metric]的符号将最小化目标注册进optimizer.register(...)(第 416 行)。
4. 检查点与热启动(save/restore/register_analysis)
save/restore用 pickle 序列化/反序列化整个 searcher 状态(第 425-446 行),用于实验容错恢复;register_analysis(analysis)(第 358-371 行)可把上一次实验的ExperimentAnalysis结果注册进高斯过程,实现 warm-start,与analysis参数配合使用。
组合调度器:与 AsyncHyperBandScheduler 协同
示例特意"checks that it is usable with a separate scheduler"——即验证BayesOptSearch可以与独立调度器组合。Tune 中搜索算法与调度器职责正交:
- 搜索算法决定"下一个 trial 试什么超参数";
- 调度器决定"正在跑的 trial 何时可以提前终止/暂停/分配更多资源"。
示例选用AsyncHyperBandScheduler:它属于 HyperBand 的异步变体,会在训练过程中持续淘汰表现不佳的 trial,从而节省算力。二者组合后,BayesOpt 负责智能探索参数空间,调度器负责及时止损,这在真实的高代价训练任务(如大模型调参)中收益尤其明显。相关的基础用法另见 async_hyperband_example.py。
并发控制:ConcurrencyLimiter 的作用
BayesOptSearch本身是顺序建议下一个配置的;当集群算力充足、希望并行跑多个 trial 时,需要用 python/ray/tune/search/concurrency_limiter.py 中的ConcurrencyLimiter包装:
algo = BayesOptSearch(utility_kwargs={"kind": "ucb", "kappa": 2.5, "xi": 0.0}) algo = ConcurrencyLimiter(algo, max_concurrent=4)max_concurrent=4将同时运行的 trial 上限设为 4:在任意时刻最多只有 4 个 trial 在跑,已完成的 trial 结果会及时回填高斯过程,用于更新后续建议。这样既充分利用并行资源,又保证贝叶斯模型的采样建议始终基于最新观测。
测试验证与常见问题排查
仓库在 python/ray/tune/tests/test_searchers.py 中覆盖了BayesOptSearch的多种边界行为:
- 参数校验:
repeat_float_precision必须为非负整数,传-1、字符串、浮点数或布尔值都会抛错(第 827-839 行); - 收敛检测:专门测试了"GP 收敛后应发出警告而非静默停止"的行为——同一配置被反复建议且
skip_duplicate=True时,实验会在提示后正常结束(第 844-867 行)。
据此,实际使用中可重点关注以下问题:
- 报错
BayesOpt must be installed!:bayesian-optimization未安装或版本不兼容,按pip install bayesian-optimization==1.4.3安装即可; - 报错
BayesOpt does not support parameters of type ...:搜索空间里出现了非连续浮点类型参数。BayesOptSearch只接受连续域;整型参数可改用其他搜索算法(如 Optuna、HyperOpt),或自行把整型表达为连续值后取整; - 报错 grid search 不支持:
param_space中混入了tune.grid_search(...),需移除或改用连续分布; - 运行很快结束、trial 数少于
num_samples:属于正常收敛行为——GP 反复建议同一最优区域,触发patience提前终止。可通过增大patience、设置skip_duplicate=False或扩大搜索空间继续探索(源码第 314-321 行日志中有明确提示); - 结果含 NaN 被静默忽略:
_register_result会自动丢弃 NaN/inf 观测,若大量出现会拖慢收敛,应检查目标函数。
延伸阅读
- 官方 API 文档:doc/source/tune/api/suggestion.rst 中
bayesopt锚点对应的BayesOptSearchautosummary 条目; - 概念导读:doc/source/tune/key-concepts.rst 中搜索算法章节,含 BayesOpt 与其他搜索算法的横向对比表;
- 搜索算法入口:python/ray/tune/search/bayesopt/init.py;
- 并发限制实现:python/ray/tune/search/concurrency_limiter.py;
- 测试用例:python/ray/tune/tests/test_searchers.py;
- 同类集成示例:HyperOpt(hyperopt_example.py)、Optuna(optuna_example.py)、BOHB(bohb_example.py),以及示例索引页 doc/source/tune/examples/index.rst。
【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考