본문 바로가기
DL/CNN

[DL][CNN]전이학습의 미세조정 기법(Fine - Tuning) 개념 및 PyTorch 예제

by 어떻게든 되겠지~ 2025. 1. 14.

앞선 글에서 Transfer Learning의 개념과 Transfer Learning의 한 종류인 Feature Extraction에 대해 알아보았습니다.

이제 Transfer Learning의 또 다른 기법인 Fine - Tuning에 대해 알아보겠습니다.

 

전이학습(Transfer Learning) 및 특성추출(Feature Extraction) 정리 ,CNN(resnet18) PyTorch 예제

1. 전이 학습(Transfer Learning)이란?일반적으로 CNN 기반의 딥러닝 모델을 제대로 훈련시키려면 많은 양의 데이터가 필요하다. 하지만 큰 데이터셋을 얻는 것은 현실적으로 어렵기 때문에 아주 큰 데

self-objectification.tistory.com

1. 미세 조정 기법(Fine - Tuning)이란?

특성 추출 기법에서 더 나아가 Pre-trained model과 합성곱층, 데이터 분류기의 가중치를 업데이트하여 Train 하는 방법이다.
특성 추출은 목표 특성을 잘 추출했다는 전제 하에 좋은 성능을 낼 수 있지만, 특성이 잘못 추출되었다면 Fine-tuning 기법으로 새로운 데이터를 사용하여 네트워크의 가중치를 업데이트해서 다시 특성 추출을 할 수 있다.       
즉, Pre-Trained model을 목적에 맞게 재학습시키거나 학습된 가중치의 일부를 재학습하는 것이다.       

 

  • 데이터셋이 크고 Pre-Trained Model과 유사성이 작을 경우 : 모델 전체 재학습
    • 데이터 셋이 크기 때문에 재학습하는 것이 좋다
  • 데이터셋이 크고 Pre-Trained Model과 유사성이 클 경우 : 합성곱층 뒷부분과 데이터 분류기를 학습
    • 강한 특징이 나타나는 합성곱층 뒷부분, 데이터분류기만 새로 학습
  • 데이터셋이 작고 Pre-Trained Model과 유사성이 작을 경우 : 합성곱층 일부분과 데이터 분류기를 학습
    • 데이터가 작기 때문에 일부 게층에 Fine Tuning 기법을 적용한다고 해도 효과가 없을 수 있다.
    • 따라사, 합성곱층 중 어디까지 새로 학습시켜야 할지 적당히 설정해 주어야한다.
  • 데이터셋이 작고 Pre-Trained Model과 유사성이 클 경우 : 데이터 분류기만 학습
    • 데이터셋이 작기 때문에 많은 계층에 Fine Funing 기법을 적용하면 과적합이 발생할 수 있다.
    • 따라서 Fully Connected layer에만 적용한다(Feature Extraction)

2. PyTorch 예제

1) 라이브러리 호출

import numpy as np
import os
import time
import copy
import glob
# OpenCV 라이브러리
# Open Sourch Computer Vision Library
import cv2
import shutil

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader

import torchvision
import torchvision.transforms as transforms
import torchvision.models as models

from torchsummary import summary

import matplotlib.pyplot as plt

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

2) 데이터 전처리

data_path = '../data/catanddog/train'
## Resize, RandomResizeCrop
# Resize : 합성곱층을 통과하기 위해 이미지 크기를 조정하는 전처리 과정
# RandomResizeCrop : 데이터 확장 용도

transform = transforms.Compose([
    transforms.Resize([256, 256]),
    transforms.RandomResizedCrop(224), # Image를 랜덤한 크기 및 비율로 조정
    transforms.RandomHorizontalFlip(), # Image를 랜덤하게 수평으로 뒤집는다
    transforms.ToTensor()
])


# DataLoader가 데이터를 불러온 대상(혹은 경로)과 방법(혹은 전처리)를 정의
train_dataset = torchvision.datasets.ImageFolder(
    data_path,
    transform=transform
)

train_loader = DataLoader(
    train_dataset,
    batch_size=32,
    num_workers=8,
    shuffle=True
)

print(len(train_dataset))

3) Feature Extraction Summary

# Feature Extraction

resnet18 = models.resnet18(pretrained=True)

for param in resnet18.parameters():
    param.requires_grad = False

resnet18.fc = nn.Linear(512, 2)

# Fully Connected Layer는 학습
for param in resnet18.fc.parameters():
    param.requires_grad = True
    
print(summary(resnet18,(3, 32, 32)))

----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1           [-1, 64, 16, 16]           9,408
       BatchNorm2d-2           [-1, 64, 16, 16]             128

 

                                             ...


             ReLU-65            [-1, 512, 1, 1]               0
       BasicBlock-66            [-1, 512, 1, 1]               0
AdaptiveAvgPool2d-67            [-1, 512, 1, 1]               0
           Linear-68                    [-1, 2]           1,026
================================================================
Total params: 11,177,538
Trainable params: 1,026
Non-trainable params: 11,176,512
----------------------------------------------------------------

...

 

torchsummay는 Pytorch 모델의 구조를 손쉽게 확인해 볼 수 있는 라이브러리입니다.

Feature Extraction을 적용하면 Trainable Parameter가 1026개 인것을 확인할 수 있습니다.

4) Fine - Tuning Summary

# Fine Tuning
resnet18 = models.resnet18(pretrained=True)

for param in resnet18.parameters():
    param.requires_grad = False

# Hidden Layer 일부 학습 가능하게 만들기
for param in resnet18.layer4[1].parameters():
    param.requires_grad = True

resnet18.fc = nn.Linear(512, 2)

# Fully Connected Layer는 학습
for param in resnet18.fc.parameters():
    param.requires_grad = True

# Total params: 11,177,538
# Trainable params: 4,721,666
# 마지막 Layer만 사용하더라도 parameter 수가 굉장히 많다
print(summary(resnet18, (3,32,32)))

----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1           [-1, 64, 16, 16]           9,408
       BatchNorm2d-2           [-1, 64, 16, 16]             128
                                     

                                                ...


             ReLU-65            [-1, 512, 1, 1]               0
       BasicBlock-66            [-1, 512, 1, 1]               0
AdaptiveAvgPool2d-67            [-1, 512, 1, 1]               0
           Linear-68                    [-1, 2]           1,026
================================================================
Total params: 11,177,538
Trainable params: 4,721,666
Non-trainable params: 6,455,872
----------------------------------------------------------------
...

 

ResNet18의 마지막 Layer의 parameter만 학습 가능하도록 한 Fine - Tuning 기법입니다.

Feature Extraction보다 더 많은 Parameter를 학습하게 되는 것을 알 수 있습니다.(약 400만개)

 

5) Optimizer 및 Loss Function  정의

optimizer = optim.Adam(resnet18.parameters())
criterion = nn.CrossEntropyLoss()

6) Model Train 함수 정의

def train_model(model, dataloaders, criterion, optimizer, device, num_epochs=13, is_trained=True):
    since = time.time()
    acc_history = []
    loss_history = []
    best_acc = 0.0

    for epoch in range(num_epochs):
        print("Epoch {}/{}".format(epoch, num_epochs-1))
        print('-'*10)

        running_loss = 0.0
        running_corrects = 0

        for inputs, labels in dataloaders:
            inputs, labels, model = inputs.to(device), labels.to(device), model.to(device)

            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            # torch.max(input, dim) : input tensor에서 max 값을 가지는 값과 index를 return
            _, preds = torch.max(outputs, 1)
            loss.backward()
            optimizer.step()    

            running_loss += loss.item() * inputs.size(0)
            running_corrects += torch.sum(labels.data == preds)
        epoch_loss = running_loss / len(dataloaders.dataset)
        epoch_acc = running_corrects.double()/ len(dataloaders.dataset)

        print("Loss : {:.4f} Acc : {:.4f}".format(epoch_loss, epoch_acc))

        if epoch_acc > best_acc:
            best_acc = epoch_acc
            
        loss_history.append(epoch_loss)
        acc_history.append(epoch_acc)
        
    time_elapsed = time.time() - since
    print("Training complete in {:.0f}m {:.0f}s".format(time_elapsed//60, time_elapsed%60))
    print("Best Acc : {:.4f}".format(best_acc))
    return acc_history, loss_history

7) Model Train

train_acc_hist, train_loss_hist = train_model(resnet18, train_loader, criterion, optimizer, device)

 

Epoch 0/12
----------
Loss : 0.3968 Acc : 0.8519
Epoch 1/12
----------
Loss : 0.2272 Acc : 0.9195
Epoch 2/12
----------
Loss : 0.1765 Acc : 0.9455
Epoch 3/12
----------
Loss : 0.2024 Acc : 0.9247
Epoch 4/12
----------
Loss : 0.1372 Acc : 0.9351
Epoch 5/12
----------
Loss : 0.1263 Acc : 0.9429
Epoch 6/12
----------
Loss : 0.1169 Acc : 0.9481
Epoch 7/12
----------
Loss : 0.0910 Acc : 0.9636
Epoch 8/12
----------
Loss : 0.1162 Acc : 0.9506
Epoch 9/12
----------
Loss : 0.0646 Acc : 0.9740
Epoch 10/12
----------
Loss : 0.0931 Acc : 0.9688
Epoch 11/12
----------
Loss : 0.1169 Acc : 0.9481
Epoch 12/12
----------
Loss : 0.1055 Acc : 0.9558
Training complete in 6m 9s
Best Acc : 0.9740

 

앞선 Feature Extraction 글에서는 Train과정에서 Accuracy가 약 93%였지만 ResNet18의 마지막 Layer를 추가하여 학습한 결과 약 97% Accuracy로 높아진 것을 확인할 수 있습니다.

반응형