-
[Pytorch] transfer_Learning 전이학습딥러닝(deep Learning) 2021. 5. 17. 02:22반응형
0. 들어가기 전에
- 파이토치의 전이학습 을 통하여 모델은 Resnet18 로 벌과 개미를 구분하는 모델을 만들어보겠습니다.
1. 모듈 임포트
In [103]:from __future__ import print_function, division import torch import torch.nn as nn import torch.optim as optim from torch.optim import lr_scheduler import numpy as np import torchvision from torchvision import datasets, models, transforms import matplotlib.pyplot as plt import time import os import copy plt.ion() # 대화형 모드
Out[103]:<matplotlib.pyplot._IonContext at 0x1f905510dc8>
2. 데이터를 불러오기 위한 torchvision 과 torch.utils.data
- 데이터 조작을 위한 Transfrom 을 작성해주고, train 과 같은 경우는 Ramdomresized 와 holizontalFlip 을 통해 데이터 augmentation 을 진행해주었다.In [104]:data_transforms = { 'train': transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), 'val': transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), }
- 여기서 ImageFolder 함수를 이용해 데이터를 로드해주었고, 최종적으로 Dataloaders를 dict형태로 만들어주어서 x = 'train' 일때 훈련용, 아닐때, x='val' 일때 테스트용으로 사용해주었다.In [105]:data_dir = './hymenoptera_data' image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'val']} dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4, shuffle=True, num_workers=4) for x in ['train', 'val']} dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']} class_names = image_datasets['train'].classes device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
In [106]:def imshow(inp, title=None): """Imshow for Tensor.""" inp = inp.numpy().transpose((1, 2, 0)) mean = np.array([0.485, 0.456, 0.406]) std = np.array([0.229, 0.224, 0.225]) inp = std * inp + mean inp = np.clip(inp, 0, 1) plt.imshow(inp) if title is not None: plt.title(title) plt.pause(0.001) # 갱신이 될 때까지 잠시 기다립니다. # 학습 데이터에서 input 과 class 를 받는다 여기서 , dataloader 는 generator 함수이기 때문에 iter 와 next를 사용해주었다. inputs, classes = next(iter(dataloaders['train'])) # 배치로부터 격자 형태의 이미지를 만듭니다. out = torchvision.utils.make_grid(inputs) imshow(out, title=[class_names[x] for x in classes])
4. 모델 학습하기¶
- 이제 모델을 학습해보자 .
- criterion 과 optim 을 받을 것이고, 모두 class 형태로 제작되었다. 그리고 epoch가 끝날때마다 sheduler 를 실행하여, optim의 계수를 낮춰주었다.
- model.state_dict( ) 함수는 model 의 param 에 대한 데이터 형태를 dict로 반환 하는데 이를 best_model_wts에 저장해준다.
In [107]:def train_model(model, criterion, optimizer, scheduler, num_epochs=25): since = time.time() best_model_wts = copy.deepcopy(model.state_dict()) best_acc = 0.0 for epoch in range(num_epochs): print('Epoch {}/{}'.format(epoch, num_epochs - 1)) print('-' * 10) # 각 에폭(epoch)은 학습 단계와 검증 단계를 갖습니다. for phase in ['train', 'val']: if phase == 'train': model.train() # 모델을 학습 모드로 설정 else: model.eval() # 모델을 평가 모드로 설정 running_loss = 0.0 running_corrects = 0 # 데이터를 반복 for inputs, labels in dataloaders[phase]: inputs = inputs.to(device) labels = labels.to(device) # 매개변수 경사도를 0으로 설정 optimizer.zero_grad() # 순전파 # 학습 시에만 연산 기록을 추적 with torch.set_grad_enabled(phase == 'train'): outputs = model(inputs) # torch.max(outputs,1) 은 _ image의 확률값 그리고, preds = 최고 확률값은 num 값이다 _, preds = torch.max(outputs, 1) loss = criterion(outputs, labels) # 학습 단계인 경우 역전파 + 최적화 if phase == 'train': loss.backward() optimizer.step() # 통계 running_loss += loss.item() * inputs.size(0) running_corrects += torch.sum(preds == labels.data) if phase == 'train': scheduler.step() epoch_loss = running_loss / dataset_sizes[phase] epoch_acc = running_corrects.double() / dataset_sizes[phase] print('{} Loss: {:.4f} Acc: {:.4f}'.format( phase, epoch_loss, epoch_acc)) # 모델을 깊은 복사(deep copy)함 if phase == 'val' and epoch_acc > best_acc: best_acc = epoch_acc best_model_wts = copy.deepcopy(model.state_dict()) print() time_elapsed = time.time() - since print('Training complete 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
In [108]:def visualize_model(model, num_images=6): was_training = model.training model.eval() images_so_far = 0 fig = plt.figure() with torch.no_grad(): for i, (inputs, labels) in enumerate(dataloaders['val']): inputs = inputs.to(device) labels = labels.to(device) outputs = model(inputs) _, preds = torch.max(outputs, 1) for j in range(inputs.size()[0]): images_so_far += 1 ax = plt.subplot(num_images//2, 2, images_so_far) ax.axis('off') ax.set_title('predicted: {}'.format(class_names[preds[j]])) imshow(inputs.cpu().data[j]) if images_so_far == num_images: model.train(mode=was_training) return model.train(mode=was_training)
5. 모델 생성
In [109]:model_ft = models.resnet18(pretrained=True) num_ftrs = model_ft.fc.in_features # 여기서 각 출력 샘플의 크기는 2로 설정합니다. # num_ftrs 는 model_ft.fc 즉 fully_connected_layer의 최종 output 부분이다. 따라서 여기서는 다시 2개의 sample 분류로 바꿔준다. model_ft.fc = nn.Linear(num_ftrs, 2) model_ft = model_ft.to(device) criterion = nn.CrossEntropyLoss() # 모든 매개변수들이 최적화되었는지 관찰 optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9) # 7 에폭마다 0.1씩 학습률 감소 exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
In [101]:model_ft
Out[101]:ResNet( (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False) (layer1): Sequential( (0): BasicBlock( (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) (1): BasicBlock( (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (layer2): Sequential( (0): BasicBlock( (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (downsample): Sequential( (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False) (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (1): BasicBlock( (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (layer3): Sequential( (0): BasicBlock( (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (downsample): Sequential( (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False) (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (1): BasicBlock( (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (layer4): Sequential( (0): BasicBlock( (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (downsample): Sequential( (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False) (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (1): BasicBlock( (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (avgpool): AdaptiveAvgPool2d(output_size=(1, 1)) (fc): Linear(in_features=512, out_features=2, bias=True) )
In [110]: # 모델을 훈련시켜준다.model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,num_epochs=25)
Epoch 0/24 ---------- train Loss: 0.4881 Acc: 0.7500 val Loss: 0.4100 Acc: 0.8366 ... Epoch 24/24 ---------- train Loss: 0.2144 Acc: 0.9016 val Loss: 0.2656 Acc: 0.9085 Training complete in 2m 12s Best val Acc: 0.921569
In [111]:visualize_model(model_ft)
6. 고정된 특징 추출기로써의 신경망
- 앞에서는 모든 param들을 수정해줬지만, 이제는 모든 param 들이 아닌 fully_connected_layer 부분만 훈련 시켜주자.In [113]:model_conv = torchvision.models.resnet18(pretrained = True) # model_conv 객체의 parameters()에 모든 param들을 훈련 못하도록 params.required_grad= False로 해준다. for param in model_conv.parameters(): param.requires_grad = False # 그다음 1000 개에서 2 개로 분류해주는 num_ftrs 의 fully_connected_layer = required_grad= True 상태로 나오게 된다. num_ftrs = model_conv.fc.in_features model_conv.fc = nn.Linear(num_ftrs,2) model_conv = model_conv.to(device) criterion = nn.CrossEntropyLoss() # 마찬가지로 optim 또한 모든 파라매터들의 값이 아닌 # model_conv.fc 의 param 즉 fully_connected_layer 부분만 update 해준다. optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9) # 7 에폭마다 0.1씩 학습률 감소 exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)
In [114]:model_conv = train_model(model_conv, criterion, optimizer_conv, exp_lr_scheduler, num_epochs=25)
Epoch 0/24 ---------- train Loss: 0.6836 Acc: 0.6434 val Loss: 0.1822 Acc: 0.9477 ... Epoch 24/24 ---------- train Loss: 0.3584 Acc: 0.8156 val Loss: 0.1714 Acc: 0.9542 Training complete in 1m 50s Best val Acc: 0.954248
In [115]:visualize_model(model_conv) plt.ioff() plt.show()
7. 결론
- 결론부터 말하자면, fully_connected 모델의 param 만 조정하는 것이 정확도가 더 상승하였지만, 이미 resnet18 데이터가 벌과 개미의 데이터를 학습하였기 때문이지 않을까..라는 생각을 해본다.
- 아무튼 요번 시간에서는 pytorch의 전이학습 모델(모든 param 을 수정하기 && FC 만 수정하기)을 다뤄봤습니다!
반응형