fb-hydra:如何获取内部配置以继承外部配置字段?

fb-hydra: How to get inner configurations to inherit outer configuration fields?

我正在尝试编写分层配置结构,以便内部目录中的配置文件继承外部目录中的配置文件。例如,在下面的场景中

upper_config
|
|-middle_config
|   |
|   |-lower_config

我希望 middle_config 能够继承和覆盖 upper_config 的参数,并且 lower_config 能够继承和覆盖 middle_config 的参数] 和 upper_config.

一个解决方案是编写一个配置解析器,以便首先读取外部模块,当读取内部模块时,它们会覆盖外部模块中的字段。

但是,我想使用 Hydra(或其他一些工具,接受建议)以获得所有额外的便利。我已经从头到尾阅读了几次文档,虽然感觉配置组或包指令应该能够处理这个问题,但我无法将它们拼凑起来。

我相信 post 问了一个非常相似的问题,但答案没有启发我,而且问问题的人似乎决定实施一个版本的配置我上面描述的解析器。

我希望有一种方法可以将内部配置文件的 package 指令更改为指向父配置,并以某种方式继承其默认列表。

假设我们有以下文件:

my_app.py
outer/conf1.yaml
outer/middle/conf2.yaml
outer/middle/inner/conf3.yaml

为了使事情具体化,这里是my_app.py的内容:

import hydra, omegaconf

@hydra.main(config_path="outer", config_name="conf1")
def my_app(cfg) -> None:
    print(omegaconf.OmegaConf.to_yaml(cfg))

my_app()

TLDR

如果您的 yaml 文件只包含纯数据(即没有默认列表或包指令),在命令行动态组合配置的最灵活方法如下所示:

$ python my_app.py +middle@_global_=conf2 +middle/inner@_global_=conf3

这将在 outer/conf1.yaml 之上合并 outer/middle/conf2.yaml,然后在其之上合并 outer/middle/inner/conf3.yaml@_global_ 关键字表示输入配置应在顶层合并,而不是根据其包含目录的名称嵌套。

现在了解详情...

在回答这个问题时,我可能会使用 Hydra 1.1 的最新候选版本中的一些功能:

>>> import hydra
>>> hydra.__version__
'1.1.0.rc1'

我们可以采用一些方法来使用 middle/inner 配置覆盖我们的外部配置:

  • 使用默认列表指定包。
  • 使用包header指定包
  • 使用 command-line 包替代来指定包(这是上面 TLDR 部分中使用的方法)

以下是每种方法的详细信息:

使用默认列表指定包。

假设我们有以下内容: 在 outer/conf1.yaml:

defaults:
  - _self_
  - middle@_here_: conf2
a: 1
b: 2

outer/middle/conf1.yaml中:

defaults:
  - _self_
  - inner@_here_: conf3
b: 3
c: 4

outer/middle/inner/conf3.yaml中:

c: 5
d: 6

使用这些 yaml 文件,运行 my_app.py 给出以下结果:

$ python my_app.py
a: 1
b: 3
c: 5
d: 6

如您所见,conf1conf2 覆盖,后者又被 被 conf3 覆盖。那么,这是如何工作的呢? defaults list用于指定每个配置的顺序object 被创作。在 conf1 中, @_here_ 包关键字用于指定 conf2 应该合并到当前配置组的信息而不是 包含在 middle 包中。这记录在 Default List 包中 关键字@_global_ 关键字也很有趣。请注意,可以 just-as-well 在默认列表中写入 - middle@foo: conf2 而不是 - middle@_here_: conf2,在这种情况下 "foo" 键将出现在输出配置中,内容为 conf2 嵌套在它下面。

正如在 conf1.yaml 中一样,conf2.yaml 使用默认列表来指定 conf3 应该合并到 conf2 而不是合并到 名为 "inner" 的包(这将是默认行为,因为 记录在案 here).

- _self_ 关键字有什么作用? 在默认列表中,此关键字允许控制 当前配置与默认值中指定的其他输入配置合并 列表。例如,在 conf2.yaml 默认值列表中,写入 - _self_ before - inner@_here_: conf3 确保 conf3 将被合并到 conf2,而不是相反。此 _self_ 关键字已记录 here。如果 - _self_ 中没有指定 默认列表,然后是默认与当前合并的顺序 配置是:

  • 使用 Hydra 1.0:从 默认列表已合并到当前配置中
  • 使用 Hydra 1.1:最后合并当前配置,覆盖默认列表中指定的其他配置

如需参考,请参阅 这些迁移 说明 从 1.0 版移动到 1.1 版。

使用包header指定包

使用包 指令 在yaml文件的顶部可以达到类似的结果:

outer/conf1.yaml中:

defaults:
  - _self_
  - middle: conf2
a: 1
b: 2

outer/middle/conf2.yaml中:

# @package _global_
defaults:
  - _self_
  - inner: conf3
b: 3
c: 4

outer/middle/inner/conf3.yaml

# @package _global_
c: 5
d: 6

# @package <PACKAGE> 指令指定 应放置当前输入配置。

$ python my_app.py
a: 1
b: 3
c: 5
d: 6

这与在默认值中使用 @<PACKAGE> 关键字的方式大致相同 列表(如前一节所述),command-line 处的结果是 完全相同的。这两种方法之间的一个区别是包 header 适用于给定输入配置的所有内容,而使用 @<PACKAGE> 默认列表中的关键字提供了更细粒度的 控制应将哪些输入配置放入哪些包中。

仍然需要在默认列表中使用 - _self_ 关键字以确保 合并以正确的顺序发生(请参阅上一节的注释 在 _self_).

Hydra 对包 headers 的处理在 Hydra 1.0 与 中有所不同 1.1.

使用 command-line 包替代来指定包

获得所需结果的最优雅、最灵活的方法是使用 command-line 包覆盖: 给定 outer/conf1.yaml 如下:

a: 1
b: 2

outer/middle/conf2.yaml因此:

b: 3
c: 4

outer/middle/inner/conf3.yaml

c: 5
d: 6

我们可以利用九头蛇的强大command-lineoverride syntax 组成输出配置:

$ python my_app.py +middle@_global_=conf2 +middle/inner@_global_=conf3
a: 1
b: 3
c: 5
d: 6

使用此方法不需要使用 _self_ 关键字,因为 +<group>@<package>=<option> 具有 附加 到默认值的效果 列表(here 是一个 参考)而不是前置。