')">

인공지능 모형 만들기 II

모형의 학습과 모니터링

Posted by Jong-June Jeon on June 29, 2024

실행 argument 의 처리

다음 코드를 run_2.py 파일에 저장하고 파이썬에서 실행하여 봅니다.

# 실행코드의 작성 (run_2.py)
import argparse

def parse_option():

    parser = argparse.ArgumentParser('argument for training')
    parser.add_argument('--batch_size', type=int, default=128,
                          help='batch_size')
    parser.add_argument('--optimizer', type=str, default="SGD",
                          help='Optimizer', choices=["SGD", "ADAM", "ADAMW"])
    parser.add_argument('--lr_decay_epochs', type=str, default='10,20,30', 
                        help='where to decay lr, can be a list')
    opt = parser.parse_args()
    
    iterations = opt.lr_decay_epochs.split(',')
    opt.lr_decay_epochs = list([])
    for it in iterations:
        opt.lr_decay_epochs.append(int(it))
    return opt

def main():

    opt = parse_option()
    print("Batch Size:", opt.batch_size)
    print("Optimizer:", opt.optimizer)
    print("Decay Epoch:", opt.lr_decay_epochs)


if __name__ == '__main__':
    main()
    

main 함수는 opt 에 저장된 값을 출력해주는 간단한 함수입니다. opt에 저장된 값들은 parse_option()의 실행결과로 만들어집니다. parse_option()는 python 을 터미널에서 실행시킬 때, 터미털의 커맨드로 부터 변수를 받을 수 있습니다. 여기서는 --batch_size, --optimizer, --lr_decay_epochs 세 개의 옵션을 받을 수 있습니다. 커맨드 창에 이 옵션들 뒤에 오는 값들을 각각 opt의 변수, opt.batch_size, opt.optimizer, opt.lr_decay_epochs 로 저장합니다. data type 을 정할 수 있으며, defaul 값을 미리 정할 수 있습니다. choices 의 값을 미리 정함으로써 정해진 값들 중 하나만 받을 수 있도록 할 수 있습니다.

다음을 터미널에서 실행해봅시다.

python run_2.py --batch_size 64 --lr_decay_epochs 30,50,80,100 --optimizer ADAM

아래와 같은 결과를 확인할 수 있습니다.

Batch Size: 64
Optimizer: ADAM
Decay Epoch: [30, 50, 80, 100]

이제 터미널에서 학습 파라메터들을 받아서 파이썬 파일을 실행할 수 있게 되었습니다.

모형 성능의 모니터링

wandb 를 통해 학습 모형의 성능을 모니터링하고 비교합시다. 먼저 https://wandb.ai/ 에 가입을 하고 다음을 실행합니다.

# wandb 설치
pip install wandb
# wandb 로그인
wandb login

먼저 사용할 패키지를 불러옵니다.

# 패키지 로드 
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import wandb

wandb 프로젝트를 초기화합니다. wandb.init() 은 W&B Run으로 동기화하고 기록하는 백그라운드 프로세스를 생성합니다. 저는 project 이름을 CIFAR10 Classification으로 하였습니다.

# project 파라미터는 실험이 속할 프로젝트의 이름을 지정합니다. 
wandb.init(project='CIFAR10 Classification')

https://wandb.ai/home 에서 My projects 를 확인해보면 CIFAR10 Classification 프로젝트가 생긴것을 확인할 수 있습니다. 저는 https://wandb.ai/jj-jeon/projects 에 프로젝트가 생겼습니다.

다음으로 프로젝트의 설정을 저장하겠습니다. wandb.config는 wandb 프로젝트의 설정을 저장하는 객체입니다. args 를 dict 으로 정의한 후 update(args)로 args에 포함된 키-값 쌍을 wandb.config에 추가하거나 업데이트하였습니다.

# Hyperparameters
batch_size = 128
learning_rate = 0.05
epochs = 50
args = {
    "learning_rate": learning_rate,
    "epochs": epochs,
    "batch_size": batch_size
}
wandb.config.update(args)  

다음으로 'run'의 이름을 설정하겠습니다. 실행이란 주어진 프로젝트에서 wandb 프로세스가 우리가 설정하여 전달하는 정보를 처리하는 단위를 의미합니다. 우리는 모형학습 하면서 epoch 마다 결과값을 wandb 프로세스에 전달하는데, 이 정보를 기록하는 단위를 'run' 이라 부릅니다. 시뮬레이션 세팅 하나가 될 수도 있고 어떤 한 개의 seed 결과가 될 수도 있습니다. 첫번째 run 이니 이름을 'first wandb' 라고 하겠습니다.

# 실행 이름 설정
wandb.run.name = 'first wandb'
# 현재 실행의 상태를 저장합니다. 
wandb.run.save()

다음으로 CIFAR-10 적합코드를 실행합니다.

# 데이터 로드/ 클래스 정의/ 모형 정의
transform = transforms.Compose(
    [transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                      download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
                                        shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                     download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size,
                                       shuffle=False, num_workers=2)

# 2. Define a CNN
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print('device:', device)

class Net(nn.Module):
  def __init__(self):
      super().__init__()
      self.conv1 = nn.Conv2d(3, 6, 5)
      self.pool = nn.MaxPool2d(2, 2)
      self.conv2 = nn.Conv2d(6, 16, 5)
      self.fc1 = nn.Linear(16 * 5 * 5, 120)
      self.fc2 = nn.Linear(120, 84)
      self.fc3 = nn.Linear(84, 10)

  def forward(self, x):
      x = self.pool(F.relu(self.conv1(x)))
      x = self.pool(F.relu(self.conv2(x)))
      x = torch.flatten(x, 1) # flatten all dimensions except batch
      x = F.relu(self.fc1(x))
      x = F.relu(self.fc2(x))
      x = self.fc3(x)
      return x

net = Net().to(device)

wandb.log({"Training loss": running_loss / 20}) 는 dict 형태로 wandb 프로세스에 running_loss / 20을 전달할 수 있습니다.

# 3. Define a Loss Function and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=learning_rate, momentum=0.9)

# 4. Train the network
for epoch in range(epochs):  # loop over the dataset multiple times

  running_loss = 0.0
  for i, (inputs, labels) in enumerate(trainloader):
      inputs, labels = inputs.to(device), labels.to(device)

      # zero the parameter gradients
      optimizer.zero_grad()

      # forward + backward + optimize
      outputs = net(inputs)
      loss = criterion(outputs, labels)
      loss.backward()
      optimizer.step()

      # print statistics
      running_loss += loss.item()
      if i % 20 == 19:    # print every 20 mini-batches
          print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 20:.3f}')
          wandb.log({"Training loss": running_loss / 20})
          running_loss = 0.0

print('Finished Training')
wandb.finish()

torch.save(net.state_dict(), 'cifar_weight.pth')
torch.save(net, 'cifar_net.pth')

wandb 홈페이지 CIFAR10 Classification 프로젝트에서 실험결과를 확인할 수 있습니다.

wandb.finish() 를 통해 wandb 프로세스를 종료합니다. 마지막에 torch.save 는 각각 학습된 모형의 가중치를 저장하는 코드 입니다. cifar_weight.pth 에는 weight 가, cifar_net.pth 에는 학습된 모델이 저장되어 있습니다. 이 파일은 다음 장에서 사용할 것입니다.

run.name 을 second wandb 로 변경하고, learning_rate 를 0.01 로 수정합니다. 그 다음 터미널에서 실행해보겠습니다. 저는 이 코드를 wandb_run.py 로 저장했습니다.

python wandb_run.py

wandb 홈페이지 CIFAR10 Classification 프로젝트에서 실험결과를 다시 확인해보겠습니다.

마지막으로 커맨드 창에 argument 를 받아서 model parameter 를 조정하여 프로젝트를 만들고, 5번의 run 을 저장하는 코드를 짜 봅시다. 해답은 (여기로!)