본문 바로가기
Machine Learning/개념 정리

[파이토치] 7. Hydra with lightning

by W_log 2024. 1. 3.

배경

1. 필요성

컴퓨터 프로그래밍과 동일하게 모델도 역시 코드로 동작한다. 따라서 주로 변경되는 변수들이나 환경설정들을 관리하는 부분에 대한 어려움이 있다. 특히 실험할때마다 바뀌는 파라미터와 하이퍼파라미터는 코드 내부에 직접 작성하기보다는 한번에 변경하면 모델이 변경된 채로 적용되는 것이 가장 바람직하다.

 

이러한 기능을 지원해주는 것이 Hydra이다.

 

2. yaml

 

특징

 

목적 자체는 파라미터에 해당하는 값을 전달해주기 위함이므로, hydra에서는 key-value 형태로 값을 전달한다. 이를 전달하기 위한 데이터 포맷으로 yaml을 사용하는데 이는 아래와 같은 특성을 가진다. hydra를 사용하려면 우선 config라는 이름의 yaml 파일을 생성해야한다.

 

1) key-value 구성으로 작성된 파일

2) 들여쓰기로 구조를 구분

3) 주석 사용 가능

4) XML, JSON 데이터 포맷에 비해 가독성이 좋음

 

문법

 

1) 주석은 #으로 표시.

2) 상위 하위 개념상에 들여쓰기로 기본적으로 2칸을 지원한다.

3) key:value 형식으로 정의한다.

4) 배열(list)는 '-'로 표시한다.

person:
    name: June
    job : Data Analyst
    skills:
    	- BigQuery
        - pytorch

 

 

파이썬 데코레이터

https://dojang.io/mod/page/view.php?id=2427

 

함수에 공통적으로 어떤 처리를 하고 싶을 때, 오른쪽과 같이 함수를 인자로 받아서 실행하는 것을 데코레이터라고 한다. 이 때 이걸 좀 간편하게 @trace라는 형태로 쉽게 대체할 수 있다. 

 

hydra에서는 @hydra.main(config_path = './configs', config_name = 'config')를 선언해주고 클래스에서 __init__(self, cfg)나 함수 인자로 cfg를 활용해서 이를 가져올 수 있다. 

 

 

 

Hydra란?

소개

 

Hydra is an open-source Python framework that simplifies the development of research and other complex applications. The key feature is the ability to dynamically create a hierarchical configuration by composition and override it through config files and the command line. The name Hydra comes from its ability to run multiple similar jobs - much like a Hydra with multiple heads.

 

파라미터가 많아지면서 코드 구조화, 코드 관리 문제를 해결하기 위해 별도의 설정 파일을 작성하여 관리하는데 사용하는 오픈소스 프레임워크

 

 

특징

 

  • 변수 접근, 변수 참조를 쉽게 사용할 수 있다.
  • 커맨드 라인을 통해서 쉽게 값들을 수정할 수 있다.
  • 각 파트별로 여러 설정 파일이 있을 수 있는데 이를 하나의 설정 파일처럼 관리할 수 있다.
  • 서로 다른 설정 파일에 있는 조합을 하나의 커맨드라인으로 실행할 수 있다.

 

전체 플로우

 

Hydra를 통해 하려는 건 다양한 변수들의 값을 저장하고 관리할 수 있게 해주는 것이다. 이를 위해서는 yaml 파일을 정리하고, 이를 configs파일을 중심으로 묶어서 사용해야한다. 그리고 Decorator를 통해서 코드에서 활용할 수 있게 해주고, 이를 호출해서 사용만 하면 된다.

 

 

 

1. yaml 파일 작성하기

 

다음은 Yaml 파일의 예시이다.

 

# ./configs/model/model.yaml

model:
  learning_rate: 0.001
  batch_size: 32

 

learning_rate에 접근하기 위해서 우리는 cfg.model.model.learning_rate로 접근할 수 있다. 물론 Decorator로 지정해줘야한다.

 

2. 여러개의 yaml 파일을 활용하고 싶은 경우

 

아래를 보면 Configs폴더 안에 config.yaml이란 파일이 있다. 각 목적에 맞게 optimizer, data, callback 폴더로 yaml 파일을 저장하는데 이 config 파일의 역할은 뭘까? 바로 이렇게 여러 파일들의 경로를 컴퓨터가 잘 찾아갈 수 있도록 나침반 역할을 한다고 보면 된다.

 

그 코드는 defaults에 담아주면 되는데, key, value를 각각 폴더명, 파일명으로 지정해주면 cfg 차원에서 cfg.key를 통해서 해당 yaml 파일로 접근할 수 있다. 

 

이에 대해서는 아래 그림을 통해서 자세하게 설명해두었다.

 

 

3. Decorator 지정



만일 위와같이 Configs라는 폴더에 여러개의 config 파일들을 활용하려고 한다면 아래와 같이 설정해줘야한다.

@hydra.main(config_path = 'configs', config_name = 'config')

 

 

만일 하나의 파일만 하고 싶다면, 이렇게 설정해주면 된다.(파일경로가 "./configs/resnet18.yaml"인 경우)

 

@hydra.main(config_path = 'configs', config_name = 'resnet18')

 

*config_name는 .yaml을 생략해도 된다.

 

 

4 & 5. cfg를 호출하고 값 가져오기

 

1) class에서 값을 사용하는 경우

class SimpleCNN(LightningModule):
  def __init__(self, cfg): #__init__ 에서 cfg를 인자로 전달
    super().__init__()
    self.learning_rate = cfg.optimizer.lr
    self.accuracy = torchmetrics.Accuracy(task = 'multiclass', num_classes = cfg.model.num_classes)
    self.criterion = nn.CrossEntropyLoss()
    self.optimizer = cfg.optimizer #config의 defaults를 통해 접근
    self.scheduler = cfg.scheduler #config의 defaults를 통해 접근
    
    self.dropout_ratio = cfg.model.model.dropout_ratio #config의 defaults를 통해 접근
    self.num_classes = cfg.model.model.num_classes #config의 defaults를 통해 접근

    self.layer = nn.Sequential(
        nn.Conv2d(in_channels = 3, out_channels = 16, kernel_size = 5),
        nn.ReLU(),
        nn.Conv2d(in_channels = 16, out_channels = 32, kernel_size = 5),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size = 2),
        nn.Dropout(self.dropout_ratio),
        nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 5),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size = 2),
        nn.Dropout(self.dropout_ratio)
    )


    self.fc_layer = nn.Linear(1024, self.num_classes)
    self.softmax = nn.Softmax(dim = 1)

 

__init __(self, cfg)를 통해서 cfg를 넣어주면 추후에 다른 클래스의 메소드에서 self만 인자로 받으면 cfg를 활용할 수 있게 된다.

 

 

2) 함수에서 값을 사용하는 경우

 

def main(cfg):
  train_dataset = CIFAR10(cfg.data.data_dir, train = True, download = True, transform = T.ToTensor())
  test_dataset = CIFAR10(cfg.data.data_dir, train = False, download = True, transform = T.ToTensor())

  train_num, val_num = int(len(train_dataset)*(1-cfg.data.valid_split)), int(len(train_dataset)*(cfg.data.valid_split))

  train_dataset, val_dataset = torch.utils.data.random_split(train_dataset, [train_num, val_num])

  train_dataloader = DataLoader(dataset = train_dataset, batch_size = cfg.data.batch_size, shuffle = True)
  val_dataloader = DataLoader(dataset = val_dataset, batch_size = cfg.data.batch_size, shuffle = False)
  test_dataloader = DataLoader(dataset = test_dataset, batch_size = cfg.data.batch_size, shuffle = False)

 

함수의 인자에 바로 cfg를 넣어줘야 작동한다.

 

 

 

추가 기능

 

1. Instantiate

 

지금까지의 예시들은 모두 숫자나 string을 제공해줬다면 단순히 값만 받는게 아니라 객체를 받아서 생성할 수 있다. 

 

예를 들어, torch.nn.CrossEntropyLoss를 적어도 일반적으로는 문자열일 뿐이지만 Instantiate를 사용하면 로스 펑션을 바로 생성할 수 있다. 대신 이를 만들려면 설정파일에 '_target_'을 명시하고, '_target_'에 객체 이름을 명시하면 작동한다.

 

예시로 코드를 작성해보았다.

#./configs/config.yaml
defaults : 
	optimizer: adam
	

#./configs/optimizer/adam.yaml
_target_ : torch.optim.Adam
lr: 0.001
weight_decay: 1e-4

 

yaml 파일은 이렇게 작성했고, Instantiate를 통해서 생성한 것과 그냥 Adam을 실행한 코드를 두개 작성해보았다.

코드를 비교해보면 optimizer에 이미 lr, weight_decay가 있기 때문에 instantiate를 통해 self.optimzier로 해당 인자를 Config에서 가져와서 Optim.Adam 옵티마이저를 가져온다고 보면 된다.

#Instantiate 사용시
class Resnet(LightningModule):
    def __init__(self, cfg):
        self.optimizer = cfg.optimizer # 이게 먼저 선언되어야 함
        
    def configure_optimizers(self):
    	optimizer = instantiate(self.optimizer, self.parameters())
	    scheduler = instantiate(self.scheduler, optimizer)
    
    

#일반 코드 사용시
def configure_optimizers(self):
    optimizer = optim.Adam(self.parameters(), lr = self.learning_rate)
    scheduler = optim.lr_scheduler.StepLR(optimizer, step_size = 1, gamma = 0.9)

 

 

2. Multi-run

 

이는 터미널 커맨드 라인에서 사용하는 옵션으로 여러 설정 파일의 조합을 한번에 여러번 실행할 수 있는 기능이다.

 

아래의 코드를 보면 바로 이해할 수 있을 것이다. 이렇게 실행하면 모델 2개, loss function 2개로 총 4개의 결과가 나온다.

 

python test.py --multirun model=resnet18,resnet34 lossfunction=mse_loss,cross_entropy_loss

 

 

3. terminal 커맨드 라인으로 값 조정하기

 

하나만 생각하면 된다. ++를 붙이는 경우 기존 설정값을 변경하거나 새로운 설정값을 추가할 수 있다. 여기서 test.py는 hydra.main 데코레이터를 선정한 파일을 지정하면 된다.

python test.py ++optimizer.lr = 0.05

python test.py ++data.valid_split=0.2

 

 

참고 링크

1. https://hydra.cc/docs/tutorials/intro/