모델 학습 프로세스

J. Hwang·2024년 9월 15일
0

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개의 댓글