3

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

upper_config
|
|-middle_config
|   |
|   |-lower_config

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

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

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

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

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

4

1 回答 1

4

假设我们有以下文件:

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/middle/conf2.yaml在 之上outer/conf1.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,而后者又被 覆盖conf3。那么,这是如何工作的呢?默认列表用于指定每个配置对象的组成顺序。在conf1中,@_here_package 关键字用于指定conf2应该合并到当前配置组的信息,而不是包含在middle包中。这记录在默认列表包关键字中。同样有趣的是@_global_关键字。请注意,也可以写- middle@foo: conf2而不是- middle@_here_: conf2在默认列表中,在这种情况下,一个"foo"键将出现在输出配置中,其内容为conf2 嵌套在其下。

就像在conf1.yaml,conf2.yaml中一样,使用默认列表来指定conf3应该合并到conf2而不是合并到名为的包中"inner"(这将是默认行为,如此处所述

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

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

有关参考,请参阅从 1.0 版迁移到 1.1 版的这些迁移说明

使用包头来指定包。

在 yaml 文件顶部使用package 指令 可以获得类似的结果:

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 对包头的处理在 Hydra 1.0 和 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 强大的命令行覆盖语法 来组成输出配置:

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

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

于 2021-06-08T08:34:56.130 回答