ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Ray Tune 条件搜索空间实战:用 HyperOpt 建模「参数依赖」的超参调优任务

Ray Tune 条件搜索空间实战:用 HyperOpt 建模「参数依赖」的超参调优任务 Ray Tune 条件搜索空间实战:用 HyperOpt 建模「参数依赖」的超参调优任务【免费下载链接】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 官方示例hyperopt_conditional_search_space_example.py(由 示例文档 内嵌引用),完整讲解如何在 Tune 中定义条件搜索空间(conditional search space)——即一个参数的存在与否、取值范围依赖另一个参数的取值(例如选择relu激活函数时才需要采样缩放系数mult)。读完后你将掌握:使用 HyperOpt 的hp.choice构建嵌套条件维度、在训练目标中正确展开(unwrap)嵌套配置字典、以及将HyperOptSearch与ConcurrencyLimiter、AsyncHyperBandScheduler组合使用的完整调优流水线。一、什么是条件搜索空间在真实调参任务中,超参之间常常存在依赖关系:模型结构选择为 A 时,才需要为 A 的专属参数取值;选择 B 时该参数根本不存在。如果用 Tune 原生的扁平tune.choice/tune.uniform字典来表达,就无法优雅地表达这种「参数随分支出现/消失」的语义。HyperOpt 库恰好原生支持这种搜索空间:它用 pyll 表达式树描述空间,hp.choice的每个分支可以是一个嵌套字典,字典内部还可以再包含随机变量。示例 示例文件 的核心就是这个模式:config_space { activation: hp.choice( activation, [ {activation: relu, mult: hp.uniform(mult, 1, 2)}, {activation: tanh}, ], ), width: hp.uniform(width, 0, 20), height: hp.uniform(heright, -100, 100), steps: 100, }其语义是:activation是一个二选一分支;若采中relu,本次实验的配置里额外多出mult ∈ [1, 2]的均匀随机变量;若采中tanh,配置中只有activation tanh,不存在mult键;width、height是全局连续变量,steps 100是常量(不参与采样)。这种条件下,训练目标函数不能假定mult一定存在,而必须用config.get(mult, 1)提供默认值兜底——这正是本示例刻意演示的健壮性写法。二、完整示例代码解读以下是 示例脚本 的完整内容(与文档页hyperopt_conditional_search_space_example.rst通过literalinclude逐字引用的代码一致),按其执行逻辑分为四部分。2.1 递归展开嵌套字典:f_unpack_dict条件空间采样出的配置是嵌套结构(顶层键 分支字典),而训练目标通常期望一个扁平字典。示例用递归展开处理:def f_unpack_dict(dct: dict) - dict: Unpacks all sub-dictionaries in given dictionary recursively. There should be no duplicated keys across all nested subdictionaries, or some instances will be lost without warning res {} for k, v in dct.items(): if isinstance(v, dict): res {**res, **f_unpack_dict(v)} else: res[k] v return res注意其 docstring 中的告警:若嵌套子字典之间存在重复键,后展开的会静默覆盖前者。使用条件空间时,应保证各分支的键名互不冲突,或使用不同前缀。2.2 训练目标:逐步报告指标def evaluation_fn(step, width, height, mult1): return (0.1 width * step / 100) ** (-1) height * 0.1 * mult def easy_objective(config_in): # Hyperparameters config f_unpack_dict(config_in) width, height, mult config[width], config[height], config.get(mult, 1) print(config) for step in range(config[steps]): # Iterative training function - can be any arbitrary training procedure intermediate_score evaluation_fn(step, width, height, mult) # Feed the score back back to Tune. tune.report({iterations: step, mean_loss: intermediate_score}) time.sleep(0.1)三个要点:config.get(mult, 1):只有relu分支才会注入mult,tanh分支用默认值 1 代替,目标函数因此对两种分支都成立;循环调用tune.report:每一step都上报一次mean_loss与进度键iterations,这使得调度器(后文的 Async HyperBand)能基于中间结果提前砍掉表现差的试验,而不是等全部 100 步跑完;time.sleep(0.1)模拟耗时训练,让「按迭代数提前停止」这一调度器能力有实际意义。2.3 组装搜索算法、并发限制与调度器def run_hyperopt_tune(config_dictconfig_space, smoke_testFalse): algo HyperOptSearch(spaceconfig_dict, metricmean_loss, modemin) algo ConcurrencyLimiter(algo, max_concurrent4) scheduler AsyncHyperBandScheduler() tuner tune.Tuner( easy_objective, tune_configtune.TuneConfig( metricmean_loss, modemin, search_algalgo, schedulerscheduler, num_samples10 if smoke_test else 100, ), ) results tuner.fit() print(Best hyperparameters found were: , results.get_best_result().config)这一小段是整个示例的「骨架」,逐个组件说明:组件作用HyperOptSearch(space..., metricmean_loss, modemin)以 HyperOpt 的 TPE(Tree-structured Parzen Estimator)算法,基于历史试验结果持续向低mean_loss区域提议新参数组合;modemin表示最小化该指标ConcurrencyLimiter(algo, max_concurrent4)装饰器式包装:任意时刻最多 4 个试验处于「等待/运行」中由该搜索器并发驱动,避免一次性把全部 100 个试验压入调度AsyncHyperBandScheduler()异步 Successive Halving 调度器,按training_iteration进度对表现差的试验提前剪枝num_samples100整个实验共跑 100 个参数组合;--smoke-test模式下缩减为 10 个,便于快速验证流水线results.get_best_result().config结束后打印最优试验的完整配置(含展开后的条件键)2.4 命令行入口if __name__ __main__: import argparse parser argparse.ArgumentParser() parser.add_argument( --smoke-test, actionstore_true, helpFinish quickly for testing ) args, _ parser.parse_known_args() ray.init(configure_loggingFalse) run_hyperopt_tune(smoke_testargs.smoke_test)使用parse_known_args()是为了兼容 Ray 自身注入的命令行参数;--smoke-test即快速模式。该脚本在 CI 中通过 Bazel 测试目标运行,目标定义见 python/ray/tune/BUILD.bazel(hyperopt_conditional_search_space_example),并在 示例索引 中登记为 Conditional search space example using HyperOpt。运行方式(需先安装 HyperOpt):pip install -U hyperopt python python/ray/tune/examples/hyperopt_conditional_search_space_example.py --smoke-test三、源码级原理:HyperOptSearch 如何工作条件空间能跑通,依赖HyperOptSearch对「嵌套空间」的专门处理。其实现位于 hyperopt_search.py,构造函数签名(类定义)为:def __init__( self, space: Optional[Dict] None, metric: Optional[str] None, mode: Optional[str] None, points_to_evaluate: Optional[List[Dict]] None, n_initial_points: int 20, random_state_seed: Optional[int] None, gamma: float 0.25, ):结合源码,几个关键机制值得展开:3.1 采样:pyll 表达式求值与配置展平suggest()方法是每次试验的入口(suggest 实现),核心流程是:调用hpo.tpe.suggest生成新的 HyperOpt 试验(前n_initial_points20次为随机启动,之后才启用 TPE 建模);用hpo.base.spec_from_misc还原出原始配置,然后执行config flatten_dict(config, flatten_listTrue)——这里正是条件空间的关键:嵌套字典被展平成单层,使activation、relu分支里的mult等参数名能与外层键并列,从而与目标函数中f_unpack_dict展开后的键名对齐;通过hpo.pyll.rec_eval(self.domain.expr, memomemo)对 pyll 表达式树求值,得到最终参数字典后deepcopy返回给 Trial。rec_eval阶段还有一层防御:如果points_to_evaluate中的配置缺少space里声明的键,会抛出带明确指引的ValueError(提示 Ensure that points_to_evaluate contains all non-constant keys from space),避免静默出错。3.2 指标方向:HyperOpt 只做最小化HyperOpt 内部永远做最小化,HyperOptSearch用metric_op桥接方向:# hyperopt internally minimizes, so max -1 if mode max: self.metric_op -1.0 elif mode min: self.metric_op 1.0试验完成时(_to_hyperopt_result),上报的mean_loss会乘以metric_op写入 HyperOpt 的 trial 结果。因此本示例传modemin,指标越小越好,与目标函数(0.1 width * step / 100) ** (-1) height * 0.1 * mult的数学性质一致——width越大、mult越大,该项越小,而height的符号直接影响结果,条件分支因此真正「有用」。3.3 常量键陷阱源码中有一段专门的错误提示(HYPEROPT_UNDEFINED_DETAILS):若搜索空间只包含常量变量,HyperOpt 不支持,会报 search space is undefined。本示例中steps: 100是常量,但空间同时含有width、height、activation三个随机变量,因此合法。实践中常见错误是误以为整个空间都能写常量,建议保留至少一个采样变量,或改用num_samples等机制。3.4 两种传参方式:原生 space 与自动转换除了像本示例这样直接传 HyperOpt 原生space,Tune 也支持把标准tune.uniform/tune.choice字典交给搜索器自动转换——convert_search_space静态方法(转换实现)会递归把Float/Integer/Categorical域映射为hp.uniform、hp.uniformint、hp.choice等,且tune.choice的字典型类别同样会被递归转换。本示例选择手写hp.choice嵌套字典,是为了展示 HyperOpt 原生的条件表达能力,这也是该文档页存在的原因。四、组件协作:ConcurrencyLimiter 与 AsyncHyperBandScheduler示例特意把三个组件串起来,验证「条件空间 搜索器 调度器」的兼容性(脚本 docstring 亦写明 It also checks that it is usable with a separate scheduler)。4.1 ConcurrencyLimiter:限制并发试验ConcurrencyLimiter 是一个Searcher装饰器,构造参数为searcher、max_concurrent、batch(默认False)。从源码看:它通过live_trials集合跟踪未完成的试验,未完成数达到max_concurrent时不再向底层搜索器索取新建议,直到有试验结束释放名额;若被包装的搜索器自己实现了set_max_concurrency,则交给搜索器内部逻辑接管,装饰器退化为透传。对本示例,max_concurrent4意味着:虽然一次要跑 100 个样本(条件空间使每个样本的配置结构不同),但 HyperOpt 至多同时「在途」4 个试验,既控制内存与计算资源占用,也让 TPE 能尽快拿到早期反馈来修正后续建议。4.2 AsyncHyperBandScheduler:基于迭代进度的剪枝AsyncHyperBandScheduler 实现异步 Successive Halving(相对同步 HyperBand 规避慢节点拖垮整个 bracket 轮次的问题)。默认参数(构造签名):time_attrtraining_iteration:以「训练迭代数」而非墙钟时间作为进度度量;max_t100:试验最多推进 100 个时间单位;grace_period1:至少跑 1 步才允许被停;reduction_factor4、brackets1:按 4 倍速率分层减半;stop_last_trialsTrue:到达max_t后终止最后存活的试验。示例目标函数每步tune.report一次,training_iteration随之单调递增,调度器据此判断:同一时间刻度下mean_loss明显靠后的试验会被提前 STOP,把算力让给新的建议——对 100 步、每步 0.1s 的模拟训练尤其有效。五、实践要点小结条件维度用hp.choice 嵌套字典表达:分支内声明「该分支专属」的随机变量;目标函数一律dict.get(key, default)兜底缺省键,并用f_unpack_dict之类的递归展平把配置拉平成单层。参数标签与字典键:示例中hp.uniform(heright, -100, 100)的第一个参数是 HyperOpt 内部的参数标签,结果配置中的键名由外层字典字面量决定,因此标签与键名height不一致时示例依然可以正常运行。HyperOptSearch关键参数:space必须含至少一个非常量变量;metric必须与tune.report上报的键一致(否则试验结束时报错,源码中有明确的报错指引);points_to_evaluate可注入已知的好参数作为起点;random_state_seed保证可复现;gamma(默认 0.25)与n_initial_points(默认 20)分别控制 TPE 建议策略与随机预热长度。搜索器与调度器解耦:示例中search_alg与scheduler相互独立装配,这正是 Tune「搜索器负责提议参数、调度器负责决定试验生命周期」的分层设计;把ConcurrencyLimiter包在搜索器外层即可在不改搜索器的前提下控制并发度。可运行性与 CI:脚本提供--smoke-test快速模式,并纳入 Bazel 测试目标(python/ray/tune/BUILD.bazel),保证条件空间示例在每次发版前经过回归验证。适用前提:需额外安装hyperopt(pip install -U hyperopt),HyperOptSearch在缺失该库时会直接断言失败并提示安装命令;示例基于当前仓库的 Ray Tune API(tune.Tunertune.TuneConfig形式)编写,若使用旧版ray.tune.runAPI,参数结构有所不同,请以本仓库当前源码为准。【免费下载链接】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),仅供参考
返回列表