본문 바로가기

인공지능/DeepLearning

[Computer Vision] Fine-Tuning

학습을 할 때 일반적으로 대규모의 이미지 데이터 셋이 필요하다.

하지만 이미지 데이터 셋을 만들기에는 시간은 둘째치고 비용이 상당히 많이 든다.

또한 새로운 데이터셋이 크지 않다면 그만큼 학습을 하기 어려울 것이고 오버핏 될 가능성이 커지게 된다.

이러한 문제를 해결하기 위한 한 가지 방법이 Fine-Tuning(미세 조정)이다.

 

딥러닝 모델의 중요한 성격 중 하나가 바로 "계층적인 특정"을 "스스로" 학습한다는 점이다.

모델의 첫 번째 층은 "일반적인(general)"특징을 추출하도록 하는 학습이 이루어지는 반면에,

마지막 층에 가까울수록 특정 데이터셋 나타날 수 있는 "구체적인(specific)"특징을 추출하도록 학습이 이루어진다.

기존의 데이터셋의 학습 내용을 새로운 모델에 적용한 후 새로운 데이터셋을 통해 모델을 미세 조정하는 것이다.

 

비록 기존의 데이터셋이 새로운 학습 데이터와 관련이 없을 수도 있지만,

일반적인 이미지 기능을 추출하여, 전체적인 형태, 구성, 질감 등을 식별하는데 도움이 될 것이다.


CNN, 합성곱 신경망

CNN 구조는 크게 2가지 파트로 구성되어 있다.

* Convolutional base

      목표 : 이미지로부터 특징을 효과적으로 추출하는 것(feature extraction)

* Classifier

      목표 : 추출된 특징을 잘 학습해서 이미지를 알맞은 카테고리로 분류하는 것(image classification)

 

결론적으로,

Convlutional base 부분, 그중에도 특히 낮은 레벨의 계층(input에 가까운 계층)일수록 일반적인 특징을 추출할 것이고,

 

그와 반대로

Convolutional base의 높은 레벨의 계층(output에 가까운 계층)과 Classifier는 보다 특유한 특징들을 추출할 것이다.


Fine-Tuning

사전에 학습된 모델을 새로운 프로젝트에 재사용할 경우, 먼저 이전 모델의 classifier를 재정의하는 것으로 시작한다.

이전의 classifier를 삭제하고, 새로운 프로젝트의 목적에 맞는 새로운 classifier를 학습하는 것이다.

그 후 세 가지 전략 중 한 가지 방법을 이용해 Fine-Tuning을 진행한다.

모두 학습시키거나, Convolutional base의 일부분과 classifier를 학습시키거나, classifier만 학습하는 방법이다.

전략은 위의 그림을 기준으로 상황에 맞춰 알맞은 전략을 선택해야 한다.

 

classifier만 학습을 시킬 경우 그냥 진행하면 되지만, 전략 1과 전략 2의 경우 learning rate를 신경 써서 조절해야 한다.

 

learning rate가 자칫 크게 설정될 경우,

이전 모델의 Convolutional base에 학습된 일반적인 특징들이 사라져 새로운 데이터에 overfitting 될 위험이 존재한다.

learning rate가 너무 작게 설정될 경우,

새로운 데이터에 대해 학습이 이루어지지 않고, 이전 학습의 지식만을 보존해 새로운 데이터에 적응을 못할 경우가 있다.

따라서 적절한 조절이 필요하다.

 

학습 후 일부분만 가중치 조절을 하기 때문에 모든 가중치를 조절하는 모델에 비해 시간이 빠르다.


%matplotlib inline
from d2l import torch as d2l
from torch import nn
import torch
import torchvision
import os
d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL+'hotdog.zip',
                         'fba480ffa8aa7e0febbb511d181409f899b9baa5')

data_dir = d2l.download_extract('hotdog')
train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'))
test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'))
hotdogs = [train_imgs[i][0] for i in range(8)]
not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]
d2l.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4);
# We specify the mean and variance of the three RGB channels to normalize the
# image channel
normalize = torchvision.transforms.Normalize(
    [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])

train_augs = torchvision.transforms.Compose([
    torchvision.transforms.RandomResizedCrop(224),
    torchvision.transforms.RandomHorizontalFlip(),
    torchvision.transforms.ToTensor(),
    normalize])

test_augs = torchvision.transforms.Compose([
    torchvision.transforms.Resize(256),
    torchvision.transforms.CenterCrop(224),
    torchvision.transforms.ToTensor(),
    normalize])

Normalize

일반 이미지는 0-255사이의 값과 (H, W, C)의 형태를 갖는 반면 pytorch는 0-1사이의 값과 (C, H, W)의 형태를 갖는다.

평균을 없애고 표준편차의 범위를 1에 가깝게 조정하여 데이터를 이분화한다.

(한곳에 집중되어있는 RGB 값들을 고르게 분산시키는 효과도 존재)

 

(이미지의 이상적인 정규화 수치)

이미지 수치를 mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225]의 정규화를 이용해 조정

finetune_net = torchvision.models.resnet18(pretrained=True)
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2)
nn.init.xavier_uniform_(finetune_net.fc.weight);
# If `param_group=True`, the model parameters in fc layer will be updated
# using a learning rate ten times greater, defined in the trainer.

pertrained=True

사전에 가중치 값이 이미 학습된 모델을 불러온다.

def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5, param_group=True):

    train_iter = torch.utils.data.DataLoader(
    	torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'), transform=train_augs),
        batch_size=batch_size, shuffle=True)
    test_iter = torch.utils.data.DataLoader(
    	torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'), transform=test_augs),
        batch_size=batch_size)
        
    devices = d2l.try_all_gpus()
    loss = nn.CrossEntropyLoss(reduction="none")

    if param_group:
        params_1x = [param for name, param in net.named_parameters()
             		if name not in ["fc.weight", "fc.bias"]]
        trainer = torch.optim.SGD([{'params': params_1x},
                                   {'params': net.fc.parameters(),
                                    'lr': learning_rate * 10}], 
                                    lr=learning_rate, weight_decay=0.001)
    else:
        trainer = torch.optim.SGD(net.parameters(), lr=learning_rate, weight_decay=0.001)
    
    d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)

param_group = True

사전에 학습된 그룹이 있을 경우, params_1x에 fc의 가중치와 편향 값만을 저장하여, optimzer를 실행한다.

학습률은 사전 학습된 지식을 최대한 유지하기 위해 learning_rate를 낮춰주었다.

 

param_group = False

모든 파라미터를 업데이트하기 때문에, learning_rate는 그대로 유지하였다.

train_fine_tuning(finetune_net, 5e-5)

사전 학습된 모델에 미세 조정

scratch_net = torchvision.models.resnet18()
scratch_net.fc = nn.Linear(scratch_net.fc.in_features, 2)
train_fine_tuning(scratch_net, 5e-4, param_group=False)

전부 학습시킨 모델

일부만을 업데이트하기 때문에 사전 학습된 모델이 속도면에서 더 빠르며, 정확도 또한 일반화를 더 잘 시켜 더 높다.


ref. Dive into Deep Learning, Aston Zhang and Zachary C. Lipton and Mu Li and Alexander J. Smola, 2020
    https://jeinalog.tistory.com/13 [Retrieved Jan. 12 2021 from]
    https://blog.naver.com/moheom_writer/221980549621 [Retrieved Jan. 12 2021 from]