如何使用枚举重新加载 hydra 配置

How to reload hydra config with enumerations

是否有更好的方法从枚举实验中重新加载 hydra 配置?现在我像这样重新加载它:

initialize_config_dir(config_dir=exp_dir, ".hydra"), job_name=config_name)
cfg = compose(config_name, overrides=overrides)
print(cfg.enum)
>>> ENUM1

但ENUM1实际上是一个枚举,通常加载为

>>> <SomeEnumClass.ENUM1: 'enum1'>

我可以通过向实验 hydra 文件添加默认配置存储来解决此问题:

defaults:
  - base_config_cs

现在结果是

initialize_config_dir(config_dir=exp_dir, ".hydra"), job_name=config_name)
cfg = compose(config_name, overrides=overrides)
print(cfg.enum)
>>> <SomeEnumClass.ENUM1: 'enum1'>

有没有更好的方法来做到这一点而不添加这个?或者我可以在 python 代码中添加默认值吗?

这是一个很好的问题——从以前的 Hydra 运行s 可靠地重新加载配置是一个可以改进的领域。 正如您所发现的,加载保存的文件 config.yaml 直接导致未类型化的 DictConfig 对象。

下面的解决方案涉及一个名为 reload.py 的脚本,该脚本使用默认列表创建一个配置节点,该列表加载架构 base_config_cs 和保存的文件 config.yaml.

在这个 post 的最后,我还给出了一个简单的解决方案,涉及将 .hydra/overrides.yaml 加载到 re-run 配置组合过程。


假设您 运行 具有以下设置的 Hydra 作业:

# app.py
from dataclasses import dataclass
from enum import Enum
import hydra
from hydra.core.config_store import ConfigStore
from omegaconf import DictConfig

class SomeEnumClass(Enum):
    ENUM1 = 1
    ENUM2 = 2

@dataclass
class Schema:
    enum: SomeEnumClass
    x: int = 123
    y: str = "abc"

def store_schema() -> None:
    cs = ConfigStore.instance()
    cs.store(name="base_config_cs", node=Schema)

@hydra.main(config_path=".", config_name="foo")
def app(cfg: DictConfig) -> None:
    print(cfg)

if __name__ == "__main__":
    store_schema()
    app()
# foo.yaml
defaults:
  - base_config_cs
  - _self_
enum: ENUM1
x: 456
$ python app.py y=xyz
{'enum': <SomeEnumClass.ENUM1: 1>, 'x': 456, 'y': 'xyz'}

在 运行 宁 app.py 之后,存在一个目录 outputs/2022-02-05/06-42-42/.hydra 包含保存的文件 config.yaml.

正如您在问题中正确指出的那样,要重新加载保存的配置,您必须将架构 base_config_csconfig.yaml 的内容合并。这是实现该目的的模式:

# reload.py
import os
from hydra import compose, initialize_config_dir
from hydra.core.config_store import ConfigStore
from app import store_schema

config_name = "config"
exp_dir = os.path.abspath("outputs/2022-02-05/07-19-56")
saved_cfg_dir = os.path.join(exp_dir, ".hydra")
assert os.path.exists(f"{saved_cfg_dir}/{config_name}.yaml")

store_schema()  # stores `base_config_cs`
cs = ConfigStore.instance()
cs.store(
    name="reload_conf",
    node={
        "defaults": [
            "base_config_cs",
            config_name,
        ]
    },
)

with initialize_config_dir(config_dir=saved_cfg_dir):
    cfg = compose("reload_conf")
print(cfg)
$ python reload.py
{'enum': <SomeEnumClass.ENUM1: 1>, 'x': 456, 'y': 'xyz'}

在上面的python文件reload.py中,我们在ConfigStore中存储了一个名为reload_conf的节点。以这种方式存储 reload_conf 相当于创建一个名为 reload_conf.yaml 的文件,Hydra 在配置搜索路径上可以发现该文件。此 reload_conf 节点有一个默认列表,可加载架构 base_config_csconfig。为此,必须满足以下两个条件:

  • 架构 base_config_cs 必须存储在 ConfigStore 中。这是通过调用我们从 app.py.
  • 导入的 store_schema 函数来完成的
  • 名称由变量 config_name 指定的配置节点,即本例中的 config.yaml,必须可被 Hydra 发现(此处通过调用 initialize_config_dir 处理) .

请注意,在 foo.yaml 中,我们有一个默认列表 ["base_config_cs", "_self_"],它在加载 foo 的内容 _self_ 之前加载架构 base_config_cs。为了让 reload_conf 以相同的合并顺序重建应用程序的配置,base_config_cs 应该在属于 reload_conf.

的默认列表中出现在 config_name 之前

通过从 foo.yaml 中删除默认列表并使用 cs.store 确保在应用程序和重新加载脚本中使用相同的默认列表

# app2.py
from dataclasses import dataclass
from enum import Enum
from typing import Any, List
import hydra
from hydra.core.config_store import ConfigStore
from omegaconf import MISSING, DictConfig

class SomeEnumClass(Enum):
    ENUM1 = 1
    ENUM2 = 2

@dataclass
class RootConfig:
    defaults: List[Any] = MISSING
    enum: SomeEnumClass = MISSING
    x: int = 123
    y: str = "abc"

def store_root_config(primary_config_name: str) -> None:
    cs = ConfigStore.instance()
    # defaults list defined here:
    cs.store(
        name="root_config", node=RootConfig(defaults=["_self_", primary_config_name])
    )

@hydra.main(config_path=".", config_name="root_config")
def app(cfg: DictConfig) -> None:
    print(cfg)

if __name__ == "__main__":
    store_root_config("foo2")
    app()
# foo2.yaml (note NO DEFAULTS LIST)
enum: ENUM1
x: 456
$ python app2.py hydra.job.chdir=false y=xyz
{'enum': <SomeEnumClass.ENUM1: 1>, 'x': 456, 'y': 'xyz'}
# reload2.py
import os
from hydra import compose, initialize_config_dir
from hydra.core.config_store import ConfigStore
from app2 import store_root_config

config_name = "config"
exp_dir = os.path.abspath("outputs/2022-02-05/07-45-43")
saved_cfg_dir = os.path.join(exp_dir, ".hydra")
assert os.path.exists(f"{saved_cfg_dir}/{config_name}.yaml")

store_root_config("config")
with initialize_config_dir(config_dir=saved_cfg_dir):
    cfg = compose("root_config")
print(cfg)
$ python reload2.py
{'enum': <SomeEnumClass.ENUM1: 1>, 'x': 456, 'y': 'xyz'}

一种更简单的替代方法是使用 .hydra/overrides.yaml 根据最初传递给 Hydra 的覆盖重构应用的配置:

# reload3.py
import os
import yaml
from hydra import compose, initialize
from app import store_schema

config_name = "config"
exp_dir = os.path.abspath("outputs/2022-02-05/07-19-56")
saved_cfg_dir = os.path.join(exp_dir, ".hydra")
overrides_path = f"{saved_cfg_dir}/overrides.yaml"
assert os.path.exists(overrides_path)

overrides = yaml.unsafe_load(open(overrides_path, "r"))
print(f"{overrides=}")
store_schema()
with initialize(config_path="."):
    cfg = compose("foo", overrides=overrides)
print(cfg)
$ python reload3.py
overrides=['y=xyz']
{'enum': <SomeEnumClass.ENUM1: 1>, 'x': 456, 'y': 'xyz'}

这种方法有其缺点:如果您的应用程序的配置涉及某些 non-hermetic 操作,例如查询时间戳(例如通过 Hydra 的 now resolver) or looking up an environment variable (e.g. via the oc.env 解析器),则由 reload.py 组成的配置可能是与 app.py.

中加载的原始版本不同