LASSL Tech Blog

간단한 예제로 살펴본 Hydra를 이용한 어플리케이션 구성

hydra

Introduction

하나의 완성된 모형을 만들기 위해서는 데이터셋이나 학습 파라미터 값을 변경하여 적어도 수십번의 실험을 하게 됩니다. 이 때 사용된 arguments와 결과값들을 사람이 엑셀 혹은 메모장으로 관리 할 수도 있습니다. 그러나 arguments가 복잡할 수록 이를 구조화하거나 관리하기 힘들기 때문에 별도의 config 파일을 프로그램으로 작성하여 관리하는 것을 추천합니다. 신화 속의 동물 Hydra가 비슷하지만 서로 다른 머리를 가지면서도 하나의 몸에서 유기적으로 움직이듯이, configs를 통해 비슷한 여러 job을 관리 및 실행할 수 있도록 합니다. 이 글에서는 Hydra에서 어떻게 config 관리의 장점들을 강화하고 반대로 단점들을 보완하고 있는지 살펴봅니다.

Hydra의 Config 철학

01. 변경에 민감할 것

Config를 이용해 머신러닝 실험을 할 때 learning rate만 바꾸고 배치 사이즈는 동일하게 가져 갈 경우 서로 다른 2개의 파일(configs)을 별도로 미리 가지고 있어야 할까요? 만약 CLI(Command-Line Interface)에서 config에 원하는 값들만 덮어쓰기(override)가 가능하다면 모든 셋팅에 대한 config 파일이 없어도 될 것 입니다. 즉 작은 변경에 민감하게 디자인 되어야 합니다.

다음과 같은 config.yaml 이 있을 때

app:
  task: classfication
  optimizer: adamw

아래와 같이 실행하면 override할 수 있습니다.

python my_app.py app.task=regression

다음은 config 출력 결과입니다.

app:
  task: regression
  optimizer: adamw

또한, +를 이용해 새로운 arguments를 추가할 수 있습니다.

python my_app.py app.task=regression +app.epochs=10
app:
  task: regression
  optimizer: adamw
  epochs: 10

02. Monolithic 구조

머신러닝 실험에서 datasets, model, optimizer 등으로 구조화하여 실험을 할 수 있습니다. 또한 다양한 계층의 config를 구조화하여 통합 관리할 수 있습니다.

하위 config를 그대로 사용하기 위해 Hydra에서는 몇 가지 개념들을 도입하고 있습니다.

예시를 통해 이를 이해해보겠습니다. 아래의 예시에서 기본 config는 conf/config.yaml에서 정의하고 있습니다.

conf/config.yaml

defaults:
  - training: default
  - optimizer/adam_series: adamw

Config group으로 training과 optimizer를 사용하여 계층을 표현하고 있습니다. conf/training/default.yaml를 사용하고 싶다면 defaults를 이용해 사용할 수 있습니다. 즉 여기서는 기본값(defaults)으로 training의 기본값과 optimizer는 adamW를 사용합니다. 그런데 optimizer는 adam_series를 폴더로 한 단 계 더 계층을 이루고 있습니다. Hydra는 이러한 다층 구조를 통합적으로 관리할 수 있습니다. 즉, config.yaml과 각각의 config yaml들을 nested 형태의 구조로 구성할 수 있습니다.

다음은 optimizer에서 training에 있는 파라미터를 사용하는 예시 입니다. 즉 adamW에서는 training에 있는 lr을 package를 이용하여 변수로 값을 받을 수 있습니다.

conf/training/default.yaml

# @package training
lr: 1e-5

conf/optimizer/adam_series/adamw.yaml

# @package optimizer
_target_: torch.optim.AdamW
lr: ${training.lr}
weight_decay: 0.001

또한, adamW는 파이토치 객체인데 _target_과 hydra의 instantiate를 이용하여 어플리케이션 실행 시 이를 해석 할 수 있습니다.

src/my_hydra.py

@hydra.main(config_path='../conf/', config_name='config.yaml')
def my_app(cfg: DictConfig) -> None:
    print(OmegaConf.to_yaml(cfg))
    model = torch.nn.Sequential(
        torch.nn.Linear(3, 1),
        torch.nn.Flatten(0, 1)
    )
    opt = instantiate(cfg.optimizer, params=model.parameters())

만약 adamW를 pacakgeoptimizer 명시하지 않고 이를 표현하려면 아래처럼 _group_을 이용하여 표현 할 수 있습니다.

conf/config.yaml

defaults:
  - /optimizer@_group_: adam_series/adamw
  - training: default

자세한 용례는 https://github.com/facebookresearch/hydra/blob/master/website/versioned_docs/version-1.0/advanced/overriding_packages.md 참조하시길 바랍니다.

03. Interpolation

일반적으로 python에서 yaml을 로드하면 파이썬 객체를 바로 평가할 수 없습니다. 물론 pickle 등으로 serialization 및 deserialization를 할 수 있지만 serialization 시의 파이썬 환경에 의존하는 등 불편한 점이 있습니다. Hydra에서는 OmegaConf를 통해 이를 해결하고 있습니다. 위의 예제 conf/optimizer/adam_series/adamw.yaml에서 ${training.lr} 변수는 OmegaConf가 lazy evaluation을 하기 떄문에 해석이 가능합니다.


앞서 소개한 기능 외에도 multirunlogging, tab completion 등의 다양한 기능도 있으니 자세한 내용들은 튜토리얼(https://hydra.cc/docs/tutorials/intro)을 참고하시길 바랍니다.

다음 편에서는 위의 내용들을 lightning-transformers을 사례를 보면서 정리해보겠습니다.

본 블로그는 hydra-core==1.1.0rc1, lightning-transformers==0.1 버전을 기준으로 작성되었습니다.

References

hyungjun.kim

hyungjun.kimFollow
Done is better than perfect.