1

有没有更好的方法从枚举实验中重新加载 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 文件添加 configstore 默认值来解决此问题:

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 代码中添加默认值吗?

4

1 回答 1

1

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

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

在这篇文章的最后,我还提供了一个简单的解决方案,涉及加载.hydra/overrides.yaml以重新运行配置组合过程。


假设您使用以下设置运行 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中,我们存储了一个名为reload_confConfigStore 的节点。以这种方式存储reload_conf等同于创建一个名为reload_conf.yamlHydra 可在配置搜索路径上发现的文件。该reload_conf节点有一个默认列表,可以同时加载模式base_config_csconfig. 为此,必须满足以下两个条件:

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

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


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'}

这种方法有其缺点:如果您的应用程序的配置涉及一些非封闭操作,例如查询时间戳(例如通过 Hydra 的now解析器)或查找环境变量(例如通过oc.env解析器),则由 组成的配置reload.py可能与原始版本不同加载在app.py.

于 2022-02-05T13:33:13.070 回答