Haribo ML, AI, MATH, Algorithm

Hydra (솔직히 처음봐도 이름력 개지림)

2025-12-06
Haribo
 

서론: 왜 이 고생을 해서 Hydra를 쓰는가?

이름력 만큼 성능도 좋을까?

Argparse (노가다):

  • 인자가 필요하다? parser.add_argument(‘–lr’) 추가.
  • 모델을 ResNet에서 ViT로 바꾸고 싶다? parser.add_argument(‘–patch_size’) 또 추가해야함.
  • 결과: train.py 상단 200줄이 인자 정의하는 코드로 도배됨.

기타 방법 (toml, json)

  • 얘네들은 그나마 나은게 py파일 하나를 통으로 인자 설정하는데 안써도됨.
  • 그러나 정적인 로딩만 가능
    • 수동으로 str 다 입력해줘야 하거나, 내부 로직으로 str or 숫자 아니면 파일 찾아서 파싱하는거 만들어야함.

Hydra (설계):

  • 코드 안건들. “설정 파일(yaml)”만 조립.
  • 핵심 원리: 코드는 “나는 cfg라는 객체 하나만 받겠소. 그 안에 뭐가 들어있는지는 난 몰르” 선언하고 빠짐.
    • cfg : OmegaConf가 만든 DictConfig라는 특수 부대원
  • 동적 로딩 가능
    • yaml 안에 yaml 집어넣기 가능. 알아서 파싱해줌
  • 결과: 파이썬 코드가 순수해짐. 로직(Logic)과 설정(Config)이 완벽하게 분리.

0. 깔쌈한 config 관리

my_project/
├── src/
│   └── train.py
└── conf/                  <-- 설정 파일의 본진
    ├── config.yaml        <-- [메인] 모든 설정의 조립판 (Control Tower)
    │
    ├── model/             <-- 모델 아키텍처 설정만 모음
    │   ├── resnet.yaml
    │   └── vit.yaml
    │
    ├── data/              <-- 데이터셋 경로, 배치 사이즈 등
    │   ├── imagenet.yaml
    │   └── cifar10.yaml
    │
    ├── optimizer/         <-- 최적화 기법 (LR, Weight Decay)
    │   ├── adamw.yaml
    │   └── sgd.yaml
    │
    └── experiment/        <-- [심화] 특정 실험을 위한 프리셋 (나중에 설명)
        ├── baseline.yaml
        └── new_idea_v1.yaml

  • 재사용성: adamw.yaml 하나 잘 짜두면, ResNet 돌릴 때도 쓰고 ViT 돌릴 때도 씀. (Ctrl+C, Ctrl+V 안 해도 됨)
  • 가독성: config.yaml 열었을 때 defaults 리스트만 보면 “아, 이 실험은 ResNet에 AdamW 썼구나” 하고 한눈에 들어옴.

1. defaults 리스트: 설정 조립의 핵심 (Merge Policy)

# conf/config.yaml

defaults:
  - _self_             # 1. 이 파일(config.yaml)의 내용을 베이스로 깐다.
  - model: resnet      # 2. conf/model/resnet.yaml을 가져와서 'model' value에 박는다.
  - optimizer: adamw   # 3. conf/optimizer/adamw.yaml을 가져와서 'optimizer' value에 박는다.

# 공통 설정 (Global)
batch_size: 64
  1. Hydra는 defaults 리스트를 위에서부터 아래로 순서대로 실행함.
  2. - model: resnet을 만나면:
    • conf/model 폴더로 가서 resnet.yaml 파일을 찾음.
    • 그 파일의 내용을 읽어서 메모리 상의 model이라는 방에 집어넣음.
  3. 결과: 최종적으로 cfg.model.pretrained 처럼 접근할 수 있음.

주의 _self_의 위치가 중요함

  • _self_가 맨 위에 있다? -> config.yaml 내용이 먼저 깔리고, 뒤에 오는 파일들이 덮어씀.
  • _self_가 맨 아래 있다? -> 다른 파일들이 먼저 깔리고, config.yaml 내용이 최종적으로 덮어씀 (보통은 맨 위에 둠).

2. 데코레이터 @hydra.main: 마법의 시작점

이 한 줄이 내 main() 함수를 납치(Hijacking) 함.

@hydra.main(version_base=None, config_path="../conf", config_name="config")
def main(cfg: DictConfig):
    ...

내부 동작 시나리오 (Under the hood)

  1. 가로채기: 파이썬이 main() 함수를 실행하려고 할 때, @hydra.main이 먼저 끼어듬.
  2. 경로 탐색: ../conf 폴더로 가서 config.yaml을 찾음.
  3. 파싱 & 병합:
    • YAML 파일들을 읽어서 하나로 합침.
    • sys.argv(커맨드 라인 인자)를 확인. epoch=200 같은 게 있으면 YAML 값을 덮어씀.
  4. 객체 생성 (중요: cfg의 정체):
    • 합쳐진 데이터를 단순 dict가 아닌 DictConfig 객체로 변환.
    • DictConfig vs dict 차이점:
      • Dot Notation: cfg['model']['name'] 대신 cfg.model.name 으로 접근 가능(가독성 압살)
      • Interpolation: path: ${data.dir}/file.txt 처럼 값 내부에서 다른 변수를 참조하면 자동으로 값을 채움.
      • Type Safety: 숫자 자리에 문자를 넣으려고 하면 에러를 뱉게 만들 수 있음.
  5. 함수 실행: 이제야 비로소 main(cfg) 함수를 호출하면서 완성된 cfg 객체를 던져줌.

결론 yaml 파싱 코드를 한 줄도 짤 필요가 없슴. 데코레이터가 다 해결함.


3. work_dir과 로그 관리

Hydra를 쓰면 내가 저장한 “로그 파일이 어디 갔노?” 하게됨. 지맘대로 현재 dir./outputs 로 바꿔버려서 내가 뭐 저장시킬 때 다 여기위로 저장되어버림.

  • 읽어올 때 문제가됨. 학습 도중 sampling 하거나 그럴 때

자동 로그 관리 (Output Directory)

  • Hydra는 실행할 때마다 자동으로 날짜/시간별 폴더를 들어줌.
  • 다만 기본 경로가 이렇게됨 outputs/2025-12-08/14-30-00/
    • 현재 config.yaml 상태를 덤프(.hydra/config.yaml) 해서 박제함.
    • 모든 로그를 이 폴더안에 가둠.

경로 문제의 정석 해결

데이터 로딩용

import hydra

# 1. 원래 프로젝트 루트 경로를 가져온다. (절대 경로로 변환됨)
orig_cwd = hydra.utils.get_original_cwd()

# 2. 데이터 경로는 이걸 기준으로 잡는다.
# 결과: "/home/captain/my_project/data/my_data.txt" (안전함)
path = f"{orig_cwd}/data/my_data.txt"

데이터 저장용

# [상대 경로] 하이드라의 축복을 받음 (추천)
torch.save(model.state_dict(), "model.pt") 
# -> 저장 위치: outputs/2024-12-09/14-00-00/model.pt

# [절대 경로] 하이드라 무시하고 마이웨이 (비추)
torch.save(model.state_dict(), "/shared/model.pt")
# -> 저장 위치: /shared/model.pt (로그 파일이랑 이산가족 됨)

4. Instantiation: if-else 지옥 탈출 (실전 코드)

좆밥 코드

# train.py
# 모델 하나 추가하려면 코드 수정하고 배포해야 함. (OCP 위반)
def get_model(args):
    if args.model_name == 'resnet':
        return ResNet(depth=args.depth, pretrained=args.pretrained)
    elif args.model_name == 'vit':
        return ViT(patch_size=args.patch_size, dim=args.dim)
    elif args.model_name == 'efficientnet':  # <--- 끊임없이 늘어나는 if문
        return ...
    else:
        raise ValueError("그런 모델 없다")

호날두 코드

yaml 파일만 추가해주고

# conf/model/my_new_gan.yaml
_target_: src.models.gan.SuperGAN  # <--- 이 클래스를 로딩해라
latent_dim: 128                    # <--- __init__에 들어갈 인자들
generator_layers: [64, 128, 256]
# train.py
import hydra

def get_model(cfg):
    # cfg.model 안에 _target_과 인자들이 다 들어있음.
    # Hydra가 알아서 import하고 객체 생성(init)까지 해서 리턴함.
    return hydra.utils.instantiate(cfg.model)

로직(코드)을 건드리지 않고 데이터(YAML)만으로 프로그램의 동작을 바꿈. 나처럼 모듈하나에 코드 100줄이상 쓰는거 극혐하는 사람들에게 딱좋음.

  • 그러나 뭐 타고타고 찾아가려할 때 귀찮긴 할듯ㅇㅇ

Similar Posts

다음 포스트 일좀 같이 합시다!!

Comments