Получить исходную конфигурацию Hydra из созданной конфигурации

#python #fb-hydra

#python #fb-hydra

Вопрос:

В Hydra я могу автоматически создавать экземпляры своих классов, например

 _target_: ClassA
foo: bar
model:
  _target_: ClassB
  hello: world
 

что приводит к рекурсивно создаваемым классам, например, где экземпляры «внутреннего класса» передаются «внешним классам»:

 ClassA(
   foo="bar"
   model=ClassB(
      hello="world"
   )
)
 

Есть ли способ ClassA получить исходную структуру конфигурации для ClassB , чтобы у меня был как экземпляр ClassB , так и исходная структура, например, модель и ее гиперпараметры

 model:
  _target_: ClassB
  hello: world
 

Ответ №1:

Вы можете достичь этого с помощью комбинации интерполяции переменных OmegaConf с его пользовательскими преобразователями.

Вот пример, в котором ClassA.__init__ принимаются оба model и modelconf в качестве аргументов. Аргумент modelconf является копией cfg.model , у которой было _target_ удалено поле.

 # conf.yaml
_target_: app.ClassA
foo: bar
model:
  _target_: app.ClassB
  hello: world
modelconf: "${remove_target: ${.model}}"
 
 # app.py
import hydra
from omegaconf import DictConfig, OmegaConf


class ClassB:
    def __init__(self, hello: str):
        print(f"ClassB.__init__ got {hello=}")


class ClassA:
    def __init__(self, foo: str, model: ClassB, modelconf: DictConfig) -> None:
        print(f"ClassA.__init__ got {foo=}, {model=}, {modelconf=}")


def remove_target_impl(conf: DictConfig) -> DictConfig:
    """Return a copy of `conf` with its `_target_` field removed."""
    conf = conf.copy()
    conf.pop("_target_")
    return conf


OmegaConf.register_new_resolver(
    "remove_target", resolver=remove_target_impl, replace=True
)


@hydra.main(config_path=".", config_name="conf.yaml")
def main(cfg: DictConfig) -> None:
    hydra.utils.instantiate(cfg)


if __name__ == "__main__":
    main()
 

В командной строке:

 $ python3 app.py
  ret = run_job(
ClassB.__init__ got hello='world'
ClassA.__init__ got foo='bar', model=<app.ClassB object at 0x10a125ee0>, modelconf={'hello': 'world'}
 

Мотивация для удаления _target_ поля из modelconf заключается в следующем:
Если _target_ ключ не был удален, то вызов to instantiate приведет к modelconf сопоставлению с экземпляром ClassB (а не с a DictConfig ).

Смотрите также _convert_ параметр to instantiate . Использование _convert_=="partial" или _convert_=="all" будет означать, что ClassA.__init__ будет получен modelconf аргумент типа dict , а не типа DictConfig .

 def remove_target_and_add_convert_impl(conf: DictConfig) -> DictConfig:
    conf = conf.copy()
    conf.pop("_target_")
    conf["_convert_"] = "all"  # instantiate should now result in a dict rather than in a DictConfig
    return conf