모델 학습 프로세스

J. Hwang·2024년 9월 15일

computer vision 분야의 이미지 분류 문제를 해결하기 위해 모델을 학습시키는 프로세스에 대해서 배워보자.


Outline

크게 dataset을 DataLoader에 로드model 정의학습손실 함수 계산optimizer로 손실 함수가 최소가 되도록 모델 파라미터 최적화 의 과정을 거치게 된다.


Dataset

  • 데이터를 load, 전처리, batch 처리 등을 용이하게 할 수 있는 class이다.
  • 필요에 따라 customize하여 다양한 데이터 타입에 대한 처리와 변형을 적용하는 등의 기능을 쉽게 추가하여 사용할 수 있다.
  • 두 가지 필수 구성 요소를 가진다.
    • __len__ : 데이터 셋의 전체 아이템 수 반환
    • __getitem__ : 주어진 인덱스에 해당하는 데이터를 학습에 적합한 형태로 변환하고 제공
import os
import pandas as pd
from torchvision.io import read_image

class CustomImageDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
        self.img_labels = pd.read_csv(annotations_file)
        self.img_dir = img_dir
        self.transform = transform
        self.target_transform = target_transform

    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
        image = read_image(img_path)
        label = self.img_labels.iloc[idx, 1]
        if self.transform:
            image = self.transform(image)
        if self.target_transform:
            label = self.target_transform(label)
        return image, label

DataLoader

  • dataset으로부터 데이터를 batch 단위로 load하는 역할
  • 이를 통해 모델 학습을 batch 단위로 진행할 수 있어 메모리 사용을 최적화하고 학습 속도를 향상시킬 수 있다.
  • shuffle, 반복, 자동 batch 생성, sampling 등을 유연하게 조정할 수 있다.
    • batch_size : 데이터를 얼마나 많은 양식 묶어 처리할지 결정
    • shuffle : 데이터를 로드할 때마다 데이터의 순서를 무작위로 섞을지 결정
    • num_workers : 데이터 로딩에 사용할 병렬처리 worker의 수
    • drop_last : 마지막 batch의 크기가 batch_size보다 작을 경우 버릴지 결정
from torch.utils.data import DataLoader

train_dataset = CustomDataset(train_data, train=True)    
test_dataset = CustomDataset(test_data, train=False)

train_dataloader = DataLoader(
	train_dataset, 
    batch_size=64,
    shuffle=True,
    num_workers=0,
    drop_last=True)
    
test_dataloader = DataLoader(
	test_dataset, 
    batch_size=64,
    shuffle=False,
    num_workers=0,
    drop_last=False)

Model

아래 코드와 같이 직접 neural network를 구성해서 model을 정의할 수도 있고, torchvision, timm 등의 라이브러리를 이용해서도 model을 정의할 수 있다.

import torch.nn as nn
import torch.nn.functional as F


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()

Loss function

  • 모델의 현재 상태에서 예측 값과 실제 값 사이의 차이를 수치적으로 나타내는 함수로써, 모델은 이 손실값을 최소화하는 방향으로 파라미터를 조정하고 학습한다.
  • 여러 손실 함수 중 문제의 유형에 적합한 손실 함수를 선택해서 사용해야 한다.
  • 이미지 분류와 같은 다중 class 분류 문제에서는 CrossEntropyLoss가 적합하다.
import torch.nn as nn

loss_fn = nn.CrossEntropyLoss(
	weight = None,     # 각 클래스에 대한 가중치를 조절하여 클래스 불균형을 다루는데 도움
    ignore_index = -100,      # 지정한 인덱스에 해당하는 label은 손실 계산에서 제외
    reduction = 'mean',       # batch 크기로 계산된 손실값을 어떻게 처리할지 결정. mean으로 되어있으면 모든 batch의 손실값들의 평균하여 하나의 값으로 반환.
    label_smoothing=0.0)    # label의 값을 0 -> 0.1, 1 -> 0.9 와 같이 완화하여 모델이 너무 확신하지 않도록 조정. 과적합 방지에 도움됨.
    
loss = loss_fn(predicts, labels)
  • 이외에도 다양한 손실 함수가 있으므로 문제에 적합한 손실 함수를 채택해야 한다.
    • CrossEntropyLoss : multi-class classification 문제에 적합
    • NLLLoss : CrossEntropyLoss에서 마지막에 log softmax 계산이 제외
    • BCELoss : binary classification에 사용하는 손실 함수
    • BCEWithLogitsLoss : BCELoss에 sigmoid 함수를 적용한 손실 함수
    • F1 Loss
      F1score=2PrecisionRecallPrecision+RecallF1\,score = \frac{2 \cdot Precision \cdot Recall}{Precision + Recall} 을 기반으로 한 손실함수로, class 분포가 불균형한 데이터에 적합한 손실 함수
    • Focal loss : CrossEntropyLoss를 기반으로 class 분포가 불균형한 데이터에 적합하도록 개선된 손실 함수

Optimizer

  • 모델이 학습 과정에서 손실 함수를 최소화하는 모델 파라미터를 찾는 과정을 자동화 해준다.
  • 데이터의 크기, 노이즈의 정도, batch 크기 등에 따라 최적화되는 속도나 성능이 달라진다.
  • 수렴 속도가 중요한지, 안정적인 수렴이 중요한지, batch size를 어떻게 설정했는지 등에 따라 적합한 optimizer가 달라진다.
  • 주요 최적화 알고리즘
    • Stochastic Gradient Descent (SGD) : 각 step마다 무작위로 선택된 하나의 데이터 샘플에 대한 gradient를 계산. 일반화 측면에서 Adam보다 낫다는 연구 결과가 있음.
    • Adaptive Moment Estimation (Adam) : 각 파라미터의 학습률을 적응적으로 조정하며 초기 학습 속도를 가속화하고 최적화의 안정성을 높임.
import torch.optim as optim

optimizer = optim.Adam(
	model.parameters(),
    lr=0.001,
    betas=(0.9, 0.999),
    weight_decay=0.01)

Learning rate scheduler

  • 학습률은 모델의 파라미터 업데이터 크기를 결정한다.
  • 스케줄러는 초기에 높은 학습률을 설정하여 빠르게 학습을 진행하고, 점차 학습률을 낮춰서 수렴 과정을 안정화 해준다.
  • 주로 사용되는 스케줄러
    • StepLR : 학습률을 일정 주기마다 감소시킨다. 모델이 빠르게 초기 수렴을 이뤘을 때 주기적으로 학습률을 줄여서 수렴을 안정화하고자 할 때 쓰인다.
    • ExponentialLR : 학습률이 일정 주기마다 지수적으로 감소하며, 학습 초기부터 학습률을 부드럽게 감소시키고자 할 때 쓰인다.
    • CosineAnnealingLR : 학습률을 cos 함수의 형태로 조절하여 높은 학습률에서 시작하여 감소시킨 후 다시 증가시키는 형태이다. 장기간에 걸쳐 최적화 성능을 개선하고자 할 때 적합하다.
    • ReduceLROnPlateau : validation set을 기준으로 성능 개선이 이루어지지 않으면 학습률을 감소시킨다. overfitting을 방지하고 성능 향상이 정체되었을 때 학습률을 조정하여 추가적으로 학습 성능을 개선하고자 할 때 쓰인다.

Training

  • 데이터를 GPU 혹은 CPU로 이동
  • 손실 계산 및 back propagation
  • optimizer로 가중치 업데이트
  • scheduler로 learning rate 조정
  • 최종적으로 총 손실을 batch 수로 나누어 평균 손실을 반환
# train for one epoch
def train_epoch(self) -> float:
        self.model.train()
        
        total_loss = 0.0
        progress_bar = tqdm(self.train_loader, desc="Training", leave=False)
        
        for images, targets in proegress_bar:
            images, targets = images.to(self.device), targets.to(self.device)
            self.optimizer.zero_grad()
            outputs = self.model(images)
            loss = self.loss_fn(outputs, targets)
            loss.backward()
            self.optimizer.step()
            self.scheduler.step()
            total_loss += loss.item()
            progress_bar.set_postfix(loss=loss.item())
        
        return total_loss / len(self.train_loader)

위의 코드는 한 epoch의 training이고, 이 training을 적절히 반복하여 모델을 학습시켜야 한다. epoch 수에 따라 training과 validation을 반복적으로 수행한다.

def train(self) -> None:
	for epoch in range(self.epochs):
    	train_loss = self.train_epoch()
        val_loss = self.validate()
        
        self.save_model(epoch, val_loss)
        self.scheduler.step()

✯ step과 epoch

  • step : 하나의 minibatch가 모델을 통과하고 gradient가 업데이트 될 때까지의 과정
  • epoch : 전체 training set이 모델을 한 번 통과하는 과정. 즉 모든 minibatch가 한 번씩 모두 처리되면 하나의 epoch이 완료되는 것이다.

Validation

  • self.model.eval()을 이용해 모델을 평가 모드로 전환
  • 총 손실을 batch 수로 나누어 평균 손실 반환

def validate(self) -> float:
	self.model.eval()
    
    total_loss = 0.0
    progress_bar = tqdm(self.val_loader, desc='Validating', leave=False)
    
    with torch.no_grad():
    	for images, targets in progress_bar:
        	images, targets = images.to(self.device), targets.to(self.device)
            outputs = self.model(images)
            loss = self.loss_fn(outputs, targets)
            total_loss += loss.item()
            progress_bar.set_postfix(loss=loss.item())
            
	return total_loss / len(self.val_loader)

References

https://pytorch.org/tutorials/beginner/basics/data_tutorial.html
https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html
https://gaussian37.github.io/dl-pytorch-lr_scheduler/#steplr-1
https://sanghyu.tistory.com/113

profile
Let it code

0개의 댓글