본문 바로가기
AI_basic/Pytorch

[Pytorch] 작물 잎 분류 Pre-trained model(resnet50)

by hits_gold 2022. 2. 7.
반응형

1. 데이터 전처리

 폴더 구성은 이전 글의 base model과 같고, 이미 학습된 resnet 모델을 불러옵니다

import torch
import torchvision.transforms as transforms
from torchvision.datasets import ImageFolder
import os

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

batch_size = 256
epoch = 30

data_transforms = {
    "train" : transforms.Compose([
        transforms.Resize([64, 64]),  
        transforms.RandomHorizontalFlip(),  # base model과 다르게 augmentation을 적용하는데, 
        transforms.RandomVerticalFlip(),    # pre_trained model이라 데이터 양을 더 늘린 것 같음(자료에 이유가 안 나와있다..) 
        transforms.RandomCrop(52),  # 이미지 일부를 52*52로 잘라내어씀
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],  # 정규화에 쓰일 각 채널의 평균값
                            [0.229, 0.224, 0.225])   # 정규화에 쓰일 각 채널의 표준편차값
    ]),
    "val" : transforms.Compose([
        transforms.Resize([64, 64]),
        transforms.RandomHorizontalFlip(),
        transforms.RandomVerticalFlip(),
        transforms.RandomCrop(52),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],
                            [0.229, 0.224, 0.225])
    ]),
    
}

data_dir = path + "/splitted"

dataset = {x : ImageFolder(root = os.path.join(data_dir, x), transform = data_transforms[x]) for x in ["train", "val"]}
loader = {x : torch.utils.data.DataLoader(dataset[x], batch_size = batch_size, shuffle = True, num_workers = 4) for x in ["train", "val"]}
dataset_sizes = {x : len(dataset[x]) for x in ["train", "val"]}
class_names = dataset["train"].classes

 -> base model과 데이터 전처리를 다르게 해주는데, Augmentation을 통해 pre-trained모델에 들어갈 데이터를 증강

 -> transforms 정규화 부분에서 평균과 표준편차값은 모델의 학습에 사용된 ImageNet 데이터의 값!!

 

2. pre-trained model 불러오기

from torchvision import models
resnet = models.resnet50(pretrained = True) # False가 되면 모델의 구조만 가져오고 초깃값은 랜덤 설정
num_ftrs = resnet.fc.in_features # fc는 모델의 마지막 layer를, in_features는 해당 층의 입력 채널 수 반환
resnet.fc = nn.Linear(num_ftrs, 33) # 마지막 fc층의 출력 채널을 클래스 수에 맞게 변환
resnet = resnet.to(device)

criterion = nn.CrossEntropyLoss()
optimizer_ft = optim.Adam(filter(lambda p : p.requires_grad, resnet.parameters()), lr=0.001)
# filter와 lambda를 통해 requires_grad=True인 layer만 파라미터 업데이트

from torch.optim import lr_scheduler
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size = 7, gamma = 0.1)
# 에폭에 따라 lr변경 -> 7에폭마다 0.1씩 곱해짐

 -> 모델을 불러오는 부분은 큰 어려움 없음

 -> 모델의 마지막 출력을 해당 데이터셋 클래스 개수에 맞게 변환해주는게 중요

ct = 0
for child in resnet.children():  # model.children() -> 모델의 layer정보
  ct += 1
  if ct < 6:
    for param in child.parameters():
      param.requires_grad = False

 

 

3. 모델 학습 및 평가 한 번에

def train_resnet(model, criterion, optimizer, scheduler, num_epochs = 25):
    
    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0
    
    for epoch in range(num_epochs):                           # 에폭마다 for문
        print(f"---------- epoch {epoch + 1} ----------")
        since = time.time()
        
        for phase in ["train", "val"]:                        # train과 val데이터 동시에-> for문인거 신경 안써도됨
            if phase == "train":
                model.train()
            else:
                model.eval()
            running_loss = 0.0
            running_corrects = 0.0
            
            for inputs, labels in loader[phase]:             # 배치단위마다 for문
                inputs = inputs.to(device)
                labels = labels.to(device)
                
                optimizer.zero_grad()
                
                with torch.set_grad_enabled(phase == "train"):
                    outputs = model(inputs)
                    x, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)
                    
                    if phase == "train":
                        loss.backward() 
                        optimizer.step()       
                        
                running_loss += loss.item() * inputs.size(0)       # 교차엔트로피 계산 deafualt값이 mean이므로 각 데이터 마다의 손실 평균이 저장되있음
                                                                   # 따라서 배치 사이즈를 곱해줘 한 배치 사이즈의 loss 총합을 계산!
                running_corrects += torch.sum(preds == labels.data)  # -----------------여기까진 base model과 구조가 동일----------------------------
            if phase == "train":
                scheduler.step()
                l_r = [x["lr"] for x in optimizer_ft.param_groups]
                print("learning rate : ", l_r)
                 
            epoch_loss = running_loss/dataset_sizes[phase]          # 전체 데이터 loss합을 각 데이터셋 전체 크기로 나눠주어 loss계산
            epoch_acc = running_corrects.double() / dataset_sizes[phase]
            
            print("{} Loss: {:4f} Acc: {:.4f}".format(phase, epoch_loss, epoch_acc))
            
            if phase == "val" and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())
                
        time_elapsed = time.time() - since
        print("Completed in {:.0f}m {:0f}s".format(time_elapsed // 60, time_elapsed % 60))
        
    print("Best val Acc: {:.4f}".format(best_acc))
    
    model.load_state_dict(best_model_wts)
    
    return model

 -> base 모델에 적용한 함수와 차이점은 스케쥴러가 추가된 모습인데, 이를 제외하면 사실 학습 함수와 평가 함수를 합쳐논 꼴

model_resnet50 = train_resnet(resnet, criterion, optimizer_ft, exp_lr_scheduler, num_epochs=epoch)
torch.save(model_resnet50, "resnet50.pt")

 

4. 학습, 검증 데이터셋 학습및 평가 결과

 -> 최종 에폭에서 train 정확도 99% val 정확도 98%로 base model보다 많이 향상되었다.

 

 -> 내가 주목한 부분은 첫 에폭인데, pre-trained가 가중치 initialization의 개념이라 첫 평가지표부터 높게 나올 줄 알았는데, base model 첫 에폭 결과가 train 정확도 81% val 정확도 78%인 것에 비해 크게 향상되지 않은 느낌이 들었다.

 -> 이 때 두 번째 에폭을 봤는데, base model의 최종 에폭보다 val 정확도가 높다.

 -> 첫 에폭은 크게 향상되지 않았으나 두 번째 학습에서 폭발적으로 정확도 증가

 -> 이 현상에 대해 두 가지 가정을 내리고 더 알아볼 것으로 추가

       1. adam optimizer를 스케쥴러를 통해 lr을 조절한 것이 영향을 주었다.

       2. pre-trained 모델의 가중치 초깃값은 이 데이터셋에 랜덤 초깃값보다 맞지 않았으나,

          손실함수에서 경사하강법에 더 유리한 위치에 있었다

 

 

 

 

 

결과 ++

 

epoch을 30으로 늘린 결과 val 정확도가 99%까지 증가

 

 

6. base model과 pre_trained model test_data평가

 1. test 데이터 전처리

test_base = ImageFolder(root = path + "/splitted/test", transform = transform_base)

test_loader_base = torch.utils.data.DataLoader(test_base,
                                          batch_size = batch_size, shuffle = True, num_workers = 4)


trans_resnet =  transforms.Compose([
        transforms.Resize([64, 64]),
        transforms.RandomCrop(52),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],
                            [0.229, 0.224, 0.225])])
test_resnet = ImageFolder(root = path + "/splitted/test", transform = trans_resnet)
test_loader_resnet = torch.utils.data.DataLoader(test_resnet, batch_size = batch_size, shuffle = True, num_workers = 4)

 -> test 데이터는 Augmentation 적용 X

 

 2. 각 모델 평가

  -> base model의 test 데이터 정확도가 높게 나와 당황하여 데이터셋 분할을 다시 확인해봤는데 이상이 없었다...

  -> 모든 결과는 알지 못하는 데이터셋과 모델의 차이점으로 생기는 것으로 넘어감

  -> 결과적으로 pre_trained 모델이 base model보다 정확도가 더 높게 나옴 약 2%p

반응형

'AI_basic > Pytorch' 카테고리의 다른 글

[Pytorch] 작물 잎 분류 non Pre_trained model  (0) 2022.02.06
[Pytorch Part.5] Augmentation과 CNN  (0) 2022.01.14
[Pytorch Part.4] AutoEncoder  (0) 2022.01.14
[Pytorch Part.2] AI Background  (0) 2022.01.06
[Pytorch Part.1] Basic Skill  (0) 2022.01.05