假设我们有以下文件:
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
如您所见,conf1 被conf2 覆盖,而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 是
参考)而不是前置。