#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