这是一个很好的问题——从以前的 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_cs
与config.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_conf
ConfigStore 的节点。以这种方式存储reload_conf
等同于创建一个名为reload_conf.yaml
Hydra 可在配置搜索路径上发现的文件。该reload_conf
节点有一个默认列表,可以同时加载模式base_config_cs
和config
. 为此,必须满足以下两个条件:
- 架构
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
.