Hydra Nevergrad Sweeper 插件实战:用无导数优化算法自动搜索超参数
【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra
本指南完整讲解 Hydra 生态中的hydra-nevergrad-sweeper插件:它让 Hydra 应用能够调用 Nevergrad 无导数优化平台中一系列先进的超参数搜索算法,在--multirun模式下对实验/应用参数进行智能寻优。读完本文,你将掌握该插件的安装激活方式、完整配置项含义、命令行与配置文件两种搜索空间定义语法,并能结合仓库中的示例应用跑通一次端到端优化、读懂optimization_results.yaml输出结果。
插件概述:从网格/随机搜索到"智能寻优"
Hydra 自身的 Basic Sweeper 提供的是网格与随机组合搜索;而 Nevergrad 则是一个无导数优化平台,内置了大量状态先进的超参数搜索算法(如进化策略、CMA-ES 类算法、差分进化等)。hydra-nevergrad-sweeper将两者衔接起来:
- Hydra 负责配置组合、任务函数注入与并行调度;
- Nevergrad 负责"下一个候选参数怎么生成",即根据历史评估结果自适应地选择更优的方向,从而用远少于网格搜索的预算逼近最优参数。
从源码看,插件的核心实现位于 nevergrad_sweeper.py,其NevergradSweeper类实现了 Hydra 的 Sweeper 接口,内部委托给NevergradSweeperImpl(见 _impl.py)完成优化循环:optimizer.ask()取候选参数 →launcher.launch()并行评估 →optimizer.tell()回传损失 → 循环直至预算耗尽 → 输出推荐结果。
安装与激活
安装插件(会同时拉取hydra-core与nevergrad依赖,仓库 setup.py 中声明了hydra-core>=1.1.0.dev7、nevergrad>=1.0.12,且要求 Python >= 3.10):
pip install hydra-nevergrad-sweeper --upgrade激活方式有两种,任选其一:
方式一:命令行指定(适合一次性使用)
python your_app.py hydra/sweeper=nevergrad -m方式二:在配置文件的defaults中声明(适合作为项目的默认寻优方案)
defaults: - hydra/sweeper: nevergrad激活后,可用下面的命令查看该 sweeper 的默认配置(文档中原样给出的参数即为 config.py 中NevergradSweeperConf的默认值):
# @package hydra.sweeper _target_: hydra_plugins.hydra_nevergrad_sweeper.nevergrad_sweeper.NevergradSweeper optim: optimizer: OnePlusOne budget: 80 num_workers: 10 noisy: false maximize: false seed: null parametrization: {} version: 1说明:仓库当前源码的默认配置与文档快照略有演进——config.py 中
optimizer默认值已改为"NGOpt",并新增了max_failure_rate: 0.0参数,同时用params取代了旧字段parametrization(后者保留但标记为弃用,两者不可同时配置)。下表以当前仓库源码为准。
优化器配置项(hydra.sweeper.optim.*)
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
optimizer | str | "NGOpt"(旧文档快照为"OnePlusOne") | Nevergrad 优化器名称,可从ng.optimizers.registry中取值 |
budget | int | 80 | 函数评估(job 运行)的总预算次数 |
num_workers | int | 10 | 并行 worker 数,控制每批同时评估多少个参数组合 |
noisy | bool | false | 目标函数是否含噪声(含噪时优化器会采用抗噪策略) |
maximize | bool | false | 置true时执行最大化而非最小化 |
seed | Optional[int] | null | 随机种子,用于结果复现 |
max_failure_rate | float | 0.0 | 一批参数中允许的最大失败率;超过则抛错终止 |
config.py 的注释给出了常用优化器的选型建议:
OnePlusOne:极其简单且稳健,尤其适合低预算,但容易过早收敛;CMA:非常优秀的算法,但通常需要较显著预算(如 > 120 次评估);TwoPointsDE:在广泛场景下表现良好,适合较大预算(> 120 次评估);NGOpt:旨在根据你的参数定义自动挑选最优优化器(会定期更新),适合不确定选型的场景。
搜索空间配置项(hydra.sweeper.params.*)
| 配置项 | 说明 |
|---|---|
params | 当前推荐的搜索空间配置方式,使用 Hydra 的 override 语法描述参数(如db: choice(mnist, cifar)) |
parametrization | 旧字段,已弃用(Hydra 1.4 起告警、1.5 移除),仅接受列表(Choice)或 Scalar 字典两种结构,不可与params同时使用 |
示例:用 Nevergrad 做一次端到端超参寻优
仓库在 plugins/hydra_nevergrad_sweeper/example 下提供了一个可直接运行的示例。目标函数由 my_app.py 定义,是一个最小值为 0 的"伪训练"函数(真实值为 0,最优解位于db=mnist, lr=0.12, dropout=0.33, batch_size=4):
@hydra.main(config_path=".", config_name="config") def dummy_training(cfg: DictConfig) -> float: do = cfg.dropout bs = cfg.batch_size out = float( abs(do - 0.33) + int(cfg.db == "mnist") + abs(cfg.lr - 0.12) + abs(bs - 4) ) log.info(f"dummy_training(dropout={do:.3f}, lr={cfg.lr:.3f}, db={cfg.db}, batch_size={bs}) = {out:.3f}") if cfg.error: raise RuntimeError("cfg.error is True") return out注意三点:① 被@hydra.main()装饰的函数必须返回一个 float,作为优化目标值(越小越好,除非设置maximize: true);②cfg.error字段用于演示"job 失败"场景;③ 示例配置 config.yaml 通过- override hydra/sweeper: nevergrad预置了 sweeper,并给出了 4 个参数的搜索空间定义。
克隆仓库代码后,在plugins/hydra_nevergrad_sweeper目录下执行:
python example/my_app.py -m启动后日志会依次展示初始化信息、并行批次与最终最优参数(文档给出的真实运行片段,预算 100、10 个并行 worker):
[2020-10-08 20:13:53,592][HYDRA] NevergradSweeper(optimizer=OnePlusOne, budget=100, num_workers=10) minimization [2020-10-08 20:13:53,593][HYDRA] with parametrization Dict(batch_size=Choice(choices=Tuple(4,8,16),weights=Array{(1,3)}),db=Choice(choices=Tuple(mnist,cifar),weights=Array{(1,2)}),dropout=Scalar{Cl(0,1,b)}[sigma=Log{exp=2.0}],lr=Log{exp=3.162277660168379,Cl(0.001,1,b)}):{'db': 'mnist', 'lr': 0.03162277660168379, 'dropout': 0.5, 'batch_size': 8} [2020-10-08 20:13:53,593][HYDRA] Sweep output dir: multirun/2020-10-08/20-13-53 [2020-10-08 20:13:55,023][HYDRA] Launching 10 jobs locally [2020-10-08 20:13:55,023][HYDRA] #0 : db=mnist lr=0.03162277660168379 dropout=0.5 batch_size=16 [2020-10-08 20:13:55,217][__main__][INFO] - dummy_training(dropout=0.500, lr=0.032, db=mnist, batch_size=16) = 13.258 [2020-10-08 20:13:55,218][HYDRA] #1 : db=cifar lr=0.018178519762066934 dropout=0.5061074452336254 batch_size=4 [2020-10-08 20:13:55,408][__main__][INFO] - dummy_training(dropout=0.506, lr=0.018, db=cifar, batch_size=4) = 0.278 [2020-10-08 20:13:55,409][HYDRA] #2 : db=cifar lr=0.10056825918734161 dropout=0.6399687427725211 batch_size=4 [2020-10-08 20:13:55,595][__main__][INFO] - dummy_training(dropout=0.640, lr=0.101, db=cifar, batch_size=4) = 0.329 [2020-10-08 20:13:55,596][HYDRA] #3 : db=mnist lr=0.06617542958182834 dropout=0.5059497416026679 batch_size=8 [2020-10-08 20:13:55,812][__main__][INFO] - dummy_training(dropout=0.506, lr=0.066, db=mnist, batch_size=8) = 5.230 [2020-10-08 20:13:55,813][HYDRA] #4 : db=mnist lr=0.16717013388679514 dropout=0.6519070394318255 batch_size=4 ... [2020-10-08 20:14:27,988][HYDRA] Best parameters: db=cifar lr=0.11961221693764439 dropout=0.37285878409770895 batch_size=4优化后期收敛(日志展示最后 2 次评估,此时已围绕最优解微调):
[HYDRA] #8 : db=mnist batch_size=4 lr=0.094 dropout=0.381 [__main__][INFO] - my_app.py(dropout=0.381, lr=0.094, db=mnist, batch_size=4) = 1.077 [HYDRA] #9 : db=mnist batch_size=4 lr=0.094 dropout=0.381 [__main__][INFO] - my_app.py(dropout=0.381, lr=0.094, db=mnist, batch_size=4) = 1.077 [HYDRA] Best parameters: db=mnist batch_size=4 lr=0.094 dropout=0.381用命令行覆盖搜索空间
不修改任何配置文件,直接通过 Hydra 的 override 语法重定义搜索空间(示例中db与batch_size用列表/范围表达离散选项,lr用对数区间、dropout用线性区间):
python example/my_app.py --multirun db=mnist,cifar batch_size=4,8,16 \ 'lr=tag(log, interval(0.001, 1))' 'dropout=interval(0,1)'理解optimization_results.yaml
优化结束后,sweep 目录下会生成optimization_results.yaml,记录优化器给出的最终推荐(由 _impl.py 写入):
best_evaluated_result: 0.381 best_evaluated_params: batch_size: 4 db: mnist dropout: 0.381 lr: 0.094 name: nevergrad字段含义:best_evaluated_result为历史评估中最优的目标函数值(注意maximize: true时此处为去符号后的原始值,见源码中direction * best[0]的处理);best_evaluated_params为该最优结果对应的参数组合;name固定为"nevergrad"便于下游脚本识别。测试 test_nevergrad_sweeper_plugin.py 会校验该文件的name == "nevergrad"、包含 3 个键,且best_evaluated_params.batch_size收敛到 4。
定义搜索空间:两种方式详解
插件支持两类 Nevergrad 参数:ng.p.Choice)。
方式一:命令行 override
这依赖 Hydra 自带的富语法 override 解析器,建议先阅读 Override 语法基础 与 Override 语法扩展。
Choices(离散选项)
'key=1,5' 'key=shuffle(range(1, 8))' 'key=range(1,5)'用ordered标签可将选择转换为ng.p.TransitionChoice(有序传递选择,适合相邻取值有相似性的场景,如学习率档位):
`key=tag(ordered, choice(1,2,3))`Scalar(连续标量)
`key=interval(1,12)` # Interval 默认是 float `key=int(interval(1,8))` # 将 Scalar 边界转为 int(整数离散化) `key=tag(log, interval(1,12))` # 带 log 标签则调用 ng.p.Log(对数分布)这些语法与解析器的对应关系,可以在插件的参数映射函数create_nevergrad_parameter_from_override(_impl.py)与单元测试用例(test_nevergrad_sweeper_plugin.py)中找到一一验证:如key=choice(1,2)→ng.p.Choice([1,2])、key=tag(ordered, choice(1,2,3))→ng.p.TransitionChoice([1,2,3])、key=range(1,5)→ng.p.Choice([1,2,3,4])、key=int(interval(1,12))→ 整数化的ng.p.Scalar、key=tag(log, interval(1,12))→ng.p.Log。
方式二:配置文件
Choices(离散选项):直接写一个 YAML 列表即可:
db: - mnist - cifarScalar(连续标量):用一个字典描述,可用的字段如下(对应 config.py 中的ScalarConfigSpec):
| 字段 | 说明 |
|---|---|
init | 可选,初始值 |
lower | 可选,下界 |
upper | 可选,上界 |
log | 置true时采用对数分布取值 |
step | 可选,搜索步长:线性模式下为加性步长,对数模式下为乘性步长 |
integer | 置true时取值限定为整数(优先使用 float 而非整数) |
关键行为:只提供lower与upper时,init自动取区间中点,step自动取区间长度的六分之一。另外注意:无界标量(缺省lower和/或upper)只能通过配置文件定义,命令行 override 无法表达无界区间。
示例配置中三种标量的实际写法(config.yaml):
# 一个对数分布的连续正标量,平均按 2 倍因子演变 lr: {init: 0.02, step: 2.0, log: true} # 一个 0 到 1 之间线性分布的标量 dropout: interval(0, 1) # 一个 4 到 16 的整数标量,init 与 step 缺省时取中点与区间六分之一 batch_size: int(interval(4, 16))配置式与命令行式的混合能力也在测试中被覆盖:params中值为字符串(如"choice(1,2,3)")的项会被当作 override 追加解析,值为字典/列表的项则直接转成 Nevergrad 参数(见 _impl.py 与 test_nevergrad_sweeper_plugin.py)。
优化循环的内部机制与失败处理
从源码看,一次完整的寻优由NevergradSweeperImpl.sweep()(_impl.py)驱动,核心流程如下:
- 合并
params/parametrization配置与命令行 overrides,逐一映射为 Nevergrad 参数,组装成ng.p.Dict参数化对象; - 依据
noisy与seed设置parametrization.function.deterministic与随机种子,实现可复现实验; - 从
ng.optimizers.registry[optimizer]实例化优化器,传入budget与num_workers; - 循环:每轮
optimizer.ask()生成min(num_workers, 剩余预算)个候选,转成 override 后交给 Hydra Launcher 并行启动 job,收集返回的目标值(失败 job 计为inf),调用optimizer.tell()反馈损失并维护历史最优; - 若一批任务中失败率超过
max_failure_rate,直接抛出异常终止(缺省 0.0 即不允许失败); - 预算耗尽后,
optimizer.provide_recommendation()给出最终推荐,写入optimization_results.yaml并打印Best parameters:。
失败处理的行为差异有测试专门验证(test_nevergrad_sweeper_plugin.py):当max_failure_rate=1.0时,失败 job 只记录日志("Returning infinity for failed experiment")不中断优化;低于 1.0 时则重新抛出真实异常栈。这为真实训练中偶发的 job 崩溃提供了容错选项。
小结与最佳实践
- 无导数优化 ≠ 网格搜索:在评估代价高昂的场景(真实训练、仿真)下,用
budget控制评估次数、num_workers控制并行度,通常能以远小于全网格搜索的开销逼近最优参数。 - 预算紧张优先选
OnePlusOne,预算充足可尝试CMA/TwoPointsDE/NGOpt;seed固定后结果可复现。 - 参数类型上优先使用 float 而非 integer(整数离散化会限制优化器效率);无界标量只能写在配置文件里。
- 若任务可能失败,适当调高
max_failure_rate让优化继续而非整体中断。 - 更多验证与进阶用法可直接阅读仓库的 测试用例 与插件 源码,以及示例应用 config.yaml。
【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考