- 서론: 왜 이 고생을 해서 Hydra를 쓰는가?
- 0. 깔쌈한 config 관리
- 1.
defaults리스트: 설정 조립의 핵심 (Merge Policy) - 2. 데코레이터
@hydra.main: 마법의 시작점 - 3.
work_dir과 로그 관리 - 4. Instantiation:
if-else지옥 탈출 (실전 코드)
서론: 왜 이 고생을 해서 Hydra를 쓰는가?
이름력 만큼 성능도 좋을까?
Argparse (노가다):
- 인자가 필요하다? parser.add_argument(‘–lr’) 추가.
- 모델을 ResNet에서 ViT로 바꾸고 싶다? parser.add_argument(‘–patch_size’) 또 추가해야함.
- 결과: train.py 상단 200줄이 인자 정의하는 코드로 도배됨.
기타 방법 (toml, json)
- 얘네들은 그나마 나은게
py파일 하나를 통으로 인자 설정하는데 안써도됨. - 그러나 정적인 로딩만 가능
- 수동으로
str다 입력해줘야 하거나, 내부 로직으로stror숫자아니면 파일 찾아서 파싱하는거 만들어야함.
- 수동으로
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
- Hydra는 defaults 리스트를 위에서부터 아래로 순서대로 실행함.
- model: resnet을 만나면:conf/model폴더로 가서resnet.yaml파일을 찾음.- 그 파일의 내용을 읽어서 메모리 상의 model이라는 방에 집어넣음.
- 결과: 최종적으로
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)
- 가로채기: 파이썬이
main()함수를 실행하려고 할 때, @hydra.main이 먼저 끼어듬. - 경로 탐색:
../conf폴더로 가서config.yaml을 찾음. - 파싱 & 병합:
- YAML 파일들을 읽어서 하나로 합침.
sys.argv(커맨드 라인 인자)를 확인.epoch=200같은 게 있으면 YAML 값을 덮어씀.
- 객체 생성 (중요: cfg의 정체):
- 합쳐진 데이터를 단순 dict가 아닌
DictConfig객체로 변환. DictConfigvsdict차이점:- Dot Notation:
cfg['model']['name']대신cfg.model.name으로 접근 가능(가독성 압살) - Interpolation: path:
${data.dir}/file.txt처럼 값 내부에서 다른 변수를 참조하면 자동으로 값을 채움. - Type Safety: 숫자 자리에 문자를 넣으려고 하면 에러를 뱉게 만들 수 있음.
- Dot Notation:
- 합쳐진 데이터를 단순 dict가 아닌
- 함수 실행: 이제야 비로소
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줄이상 쓰는거 극혐하는 사람들에게 딱좋음.
- 그러나 뭐 타고타고 찾아가려할 때 귀찮긴 할듯ㅇㅇ