개미의 개열시미 프로그래밍

[4주차] 전이학습(Transfer Learning) <실습 정리편> 본문

인턴쉽/동계백마인턴쉽(2021)

[4주차] 전이학습(Transfer Learning) <실습 정리편>

YunHyeok 2021. 2. 8. 18:54
728x90
반응형

전이 학습(Transfer learning)은 직접 코드를 보면서 이해하는 것이 이해하는데 도움이 많이 되었습니다.

 

실습은 PyTorch.org 사이트에서 전이 학습에 대한 코드를 Colab, Jupyter notebook, GitHub으로 공유하고 있고 이 공유된 코드를 통해 정리를 하려고 합니다.

 

tutorials.pytorch.kr/beginner/transfer_learning_tutorial.html

 

컴퓨터 비전(Vision)을 위한 전이학습(Transfer Learning) — PyTorch Tutorials 1.6.0 documentation

Note Click here to download the full example code 컴퓨터 비전(Vision)을 위한 전이학습(Transfer Learning) Author: Sasank Chilamkurthy번역: 박정환 이 튜토리얼에서는 전이학습(Transfer Learning)을 이용하여 이미지 분류를

tutorials.pytorch.kr


전에 <이론 정리편>을 이해했다면 아래와 같은 말을 이해할 수 있습니다.

 

reliablecho-programming.tistory.com/1?category=921344

 

[DL] 전이학습(Transfer Learning) <이론 정리편>

먼저, 전이학습을 공부하려는 이유는 이번 백마인턴의 주제가 유사 이미지 분류 개발이며 코드의 큰 틀은 전이학습구조로 이루어지기에 과제를 진행하기 위해 꼼꼼히 이해하는 단계가 필요하

reliablecho-programming.tistory.com

실제로 충분한 크기의 데이터셋을 갖추기는 상대적으로 드물기 때문에, (무작위 초기화를 통해) 맨 처음부터 합성곱 신경망(Convolutional Network) 전체를 학습하는 사람은 매우 적습니다. 대신, 매우 큰 데이터셋(예. 100가지 분류에 대해 120만 개의 이미지가 포함된 ImageNet)에서 합성곱 신경망(ConvNet)을 미리 학습한 후, 이 합성곱 신경망을 관심 있는 작업을 위한 초기 설정 또는 고정된 특징 추출기(fixed feature extractor)로 사용합니다.

 

 

먼저, 학습을 하는 과정을 순서대로 나열해보자면

  1. 데이터 불러오기(정규화)
  2. 모델 정의
  3. 손실함수 및 Optimizer 정의
  4. Train dataset을 통해 학습 & Test dataset을 통해 평가

 


1. 데이터 불러오기(정규화)

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])
    ]),
}

data_dir = 'data/hymenoptera_data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}

# path['train']는 train set의 경로
# path['test']는 val set의 경로

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")

데이터는 공공데이터인 hymenopetra dataset을 사용했으며 벌과 개미 두 개의 클래스를 가집니다.

 

 

hymenoptera_data - Google Drive

이 폴더에 파일이 없습니다.이 폴더에 파일을 추가하려면 로그인하세요.

drive.google.com

 

*일부 이미지(batch size=4) 시각화해보기 

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)  # 갱신이 될 때까지 잠시 기다립니다.


# 한개의 Bathc만큼 이미지를 불러온다. 배치사이를 4로 헀으니 4장이 로드된다. 
inputs, classes = next(iter(dataloaders['train']))

# 배치로부터 격자 형태의 이미지를 만듭니다.
out = torchvision.utils.make_grid(inputs)

# 이미지를 출력한다. 
imshow(out, title=[class_names[x] for x in classes])

출력 이미지

데이터셋 다운로드가 잘되었는지 판단?하기 위한 과정인 것 같습니다. batch size를 4로 설정했기 때문에 총 4개의 이미지를 출력합니다.

 

 

 

2. 모델 정의

model_ft = models.resnet18(pretrained=True) #pretrained model을 가져온다.


for param in model_ft.parameters(): # False로 설정함으로써 마지막 classifier를 제외한 모든 부분을 고정하여 backward()중에 경사도 계산이 되지 않도록 합니다.
    param.requires_grad = False

num_ftrs = model_ft.fc.in_features # ResNet18모델의 마지막 단에서, 출력 노드의 갯수를 구해주는 함수이다.
model_ft.fc = nn.Linear(num_ftrs, 2) 
# 이렇듯 Pretrained-model의 끝단에 Fully connected layer를 추가로 삽입하고 노드를 연결시켜 주는 것이 우리가 사용할 모델
# 본 예제에서는 Fully connected layer 즉 Linear layer의 출력 노드 갯수는 Class의 갯수와 같다.(개미와 벌 == 2)

model_ft = model_ft.to(device)

첫번째 줄에서 pretrained를 True로 설정함으로써 학습된 모델을 불러올 때 자동으로 Weights 값을 불러올 수 있습니다.

 

(주의) param.requires_grad 를 True로 설정하는 경우 모델 전체에 대해 backward계산을 진행합니다.

 

 

 

3. 손실함수 및 Optimizer 정의

criterion = nn.CrossEntropyLoss()

optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)

exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
# Learning Rate Scheduler를 사용하면 좀 더 효율적으로 Global Minimam에 수렴할 수 있다.
# 예를 들면 특정 Epoch마다 Learning Rate를 다르게 해서 좀 더 효율 적으로 Global minima
# 에 수렴할 수 있도록 도와준다. 

 

 

4. Train dataset을 통해 학습 & Test dataset을 통해 평가

def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
    since = time.time() # 시작 시간을 기록(총 소요 시간 계산을 위해)

    # fc를 초기화한 model의 parameter를 저장 
    best_model_wts = copy.deepcopy(model.state_dict()) 
    best_acc = 0.0

    #지정한 epoch을 돌며 training
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1)) #epoch를 카운트 
        print('-' * 10)

        # 각 에폭(epoch)은 학습 단계와 검증 단계를 갖습니다.
        for phase in ['train', 'val']: # train mode와 validation mode순으로 진행
            if phase == 'train':
                model.train()  # 모델을 train 모드로 설정
            else:
                model.eval()   # 모델을 eval(=val) 모드로 설정

            running_loss = 0.0
            running_corrects = 0

            # 데이터를 반복
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)

                # 매개변수 Gradiant를 0으로 초기화
                optimizer.zero_grad()

                # 순전파(foward)
                # 학습 시(train)에만 연산 기록을 추적
                # train의 경우 끝 fc부분만 진행이된다. 
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, 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)함
            # val을 수행하고 
            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
    #코드에서 보면 확인할 수 있듯이 학습과 검사를 동시에 진행을 하여 epoch마다 Training acc와 Test acc를 확인할 수 있습니다.  최적의 epoch값을 찾기에 좋은 것 같습니다. 

코드에서 보면 확인할 수 있듯이 학습과 검사를 동시에 진행을 하여 epoch마다 Training acc와 Test acc를 확인할 수 있

습니다. 최적의 epoch값을 찾기에 좋은 것 같습니다.

 

 

model_ft = train_model(model_ft, criterion, optimizer_ft,
                         exp_lr_scheduler, num_epochs=25)

이제 위의 코드를 통해 train, test의 정확도를 확인해봅니다.

결과 부분

이전에 학습이 잘된 모델의 weights를 통해서 새 모델을 생성해주는 전이 학습기법을 통해 95% 이상의 높은 결과가 나온 것을 확인할 수 있었습니다.

 

이번 인턴과제인 '유사이미지 분류' 또한 전이 학습을 이용하기에 실습으로 정리를 해보았습니다. 다음은 지금까지 정리한 전이 학습(Transfer learning)과 Densnet을 통해 구현한 과제를 정리해보도록 하겠습니다.

728x90
반응형
Comments