【问题标题】:fb-hydra: How to get inner configurations to inherit outer configuration fields?fb-hydra:如何获取内部配置以继承外部配置字段?
【发布时间】:2021-08-15 07:16:20
【问题描述】:

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

upper_config
|
|-middle_config
|   |
|   |-lower_config

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

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

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

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

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

【问题讨论】:

  • 作为我下面的答案(使用 Hydra)的替代方案,这可以使用普通的 OmegaConf 来实现,如下所示:1)使用 Python 的 os.walk 函数获取 yaml 文件列表,2)使用根据您的偏好对这些文件进行一些启发式排序,这些文件应该合并到其他文件之上,3)在每个文件上调用 OmegaConf.load(filename) 以生成 OmegaConf 对象列表,以及 4)调用 cfg = OmegaConf.merge(*list_of_config_objects) 以合并收集的配置对象。

标签: python python-3.x configuration config fb-hydra


【解决方案1】:

假设我们有以下文件:

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'

我们可以采取几种方法来用中间/内部配置覆盖我们的外部配置:

  • 使用默认列表指定包。
  • 使用包头来指定包。
  • 使用命令行包覆盖来指定包(这是上面 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 覆盖,而conf2 又被 被conf3 覆盖。那么,这是如何工作的呢? defaults list 用于指定每个配置对象的顺序 被创作。在conf1 中,@_here_ 包关键字用于指定 conf2 应该合并到当前配置组的信息中,而不是 包含在middle 包中。这记录在Default List package keywords 中。 同样有趣的是@_global_ 关键字。请注意,也可以 在默认列表中写入- middle@foo: conf2 而不是- middle@_here_: conf2,在这种情况下 "foo" 键将出现在输出配置中,其内容为 conf2 嵌套在它下面。

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

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

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

参考见these migration instructions 用于从 1.0 版迁移到 1.1 版。

使用包头指定包。

使用package directive 在 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> 关键字的方式大致相同 列表(如上一节所述),命令行的结果是 完全相同的。这两种方法之间的一个区别是包头 适用于给定输入配置的所有内容,而使用 默认列表中的@<PACKAGE> 关键字提供了更细粒度 控制应将哪些输入配置放入哪些包中。

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

Hydra 对包头的处理是different in Hydra 1.0 vs 1.1

使用命令行包覆盖来指定包

实现所需结果的最优雅、最灵活的方法是使用命令行包覆盖: 给定outer/conf1.yaml如下:

a: 1
b: 2

因此outer/middle/conf2.yaml

b: 3
c: 4

outer/middle/inner/conf3.yaml:

c: 5
d: 6

我们可以使用 Hydra 强大的命令行override 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 是 参考)而不是前置。

【讨论】:

  • 感谢您提供如此详细的回答,它帮助我确认并提高了我对 Hydra 的很多理解,这是我自两周前提出这个问题以来所发展的。我提出的解决方案遵循您最后的建议,在内部 yaml 文件的顶部有指令,并通过调用 compose 覆盖它们。但是,在您解释了如何在默认列表中使用 @ 包指令以及 _self_ 以保证覆盖以正确的顺序发生之后,我想我会稍微调整一下我的方法。
  • 很高兴我能帮上忙 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多