본문 바로가기
AI Theory/Generative models

오토인코더(Autoencoder)가 뭐에요? - 4. Practice with PyTorch (AutoEncoder)

by climba 2022. 1. 31.
- Reference
Naver d2 이활석님의 '오토인코더의 모든것'
Kaist Edward Choi 교수님의 Programming for AI(AI 504, Fall2020)

Naver d2 이활석님의 '오토인코더의 모든것'과 Kaist Edward Choi 교수님의 AI 504 수업을 토대로 공부한 후 정리하였습니다.

전체 코드는 github에 정리해서 올려놓았습니다.

 

GitHub - gustn9609/dl_study: Deep Learning studying

Deep Learning studying. Contribute to gustn9609/dl_study development by creating an account on GitHub.

github.com

1. Settings

1-1 . Import required libraries

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.init as init
import torchvision.datasets as dset
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import matplotlib as mpl
  • torch.nn : 신경망 모델의 base class
    • layer 종류: nn.Linear, nn.Bilinear, nn.Fold, nn.RNN
    • activation function 종류 : nn.Sigmoid, nn.ReLU, nn.LeakyReLU, nn.Softmin
  • torch.optim : 최적화 알고리즘 구현 패키지 (ex: optim.SGD, optim.Adam .. )
  • torchvision : pytorch와 함께 사용되는 computer vision용 라이브러리
    • 이미지, 비디오 변환을 위한 유틸리티 및 일부 데이터 셋 제공

1-2. Set hyperparameters

batch_size = 256
learning_rate = 0.0002 # 일반적인 딥러닝 모델에서 learning_rate는 0.001 ~ 0.0001의 값을 사용
num_epochs = 10

- batch_size : 연산 한번에 들어가는 데이터의 크기
    - 한 번의 epoch에 모든 데이터를 집어 넣을 수 없으므로 나눠서 줘야한다. (그때의 나눠주는 size)
- epochs : 하나의 데이터셋을 몇 번 반복 학습할 지 정하는 파라미터
    - 너무 많은 epochs는 overfitting을 야기한다.

조금 더 쉽게 말하면, 1000개의 데이터가 있는데 batch_size = 10으로 설정 한다해보자.
그러면 총 10개의 batch가 그룹을 이뤄서 예측이 진행되고, 100번의 step을 통해 1epoch를 도는 것이다.
즉, 1epoch(학습1번) = 10(batch_size) * 100(step or iteration)
(batch_size가 커지면 한번에 많은양을 학습하므로 train 과정은 빨라지지만, 메모리에 따른 한계가 있다.)
(batch_size가 작아지면 너무 적은 양의 데이터로 가중치를 업데이트하므로 훈련이 불안정해진다.)

2. Data

2-1. Download Data

mnist_train = dset.MNIST("./", train=True, transform=transforms.ToTensor(), target_transform=None, download=True)
mnist_test = dset.MNIST("./", train=False, transform=transforms.ToTensor(), target_transform=None, download=True)
mnist_train, mnist_val = torch.utils.data.random_split(mnist_train, [50000, 10000]) # [50000, 10000] => [train_size, test_size]]
mnist_train[0][0].size() # (1, 28, 28)
mnist_train[0][1] # label : mnist_train[0]이 의미하는 숫자

2-2. Set DataLoader

Custom dataset / dataloader는 왜 필요한가
- 많은 양의 data를 한번에 불러와 모델을 학습시키면 메모리가 부족 할 수 있기 때문에 custom dataset을 따로 만듦
- 또한 input의 길이가 변할 수도 있기 때문에 batch를 만들기 위해서는 Dataloader를 통해 mini batch를 만들어줘야함

dataloaders = {}
dataloaders['train'] = DataLoader(mnist_train, batch_size=batch_size, shuffle=True)
dataloaders['val'] = DataLoader(mnist_val, batch_size=batch_size, shuffle=False)
dataloaders['test'] = DataLoader(mnist_test, batch_size=batch_size, shuffle=False)
print(len(dataloaders["train"])) # 196
print(len(dataloaders["val"])) # 40
print(len(dataloaders["test"])) # 40
answer=0
for n,data in enumerate(dataloaders['train']):
    if n == 1:
        break
    print(n+1,'번째 step을 보고 계십니다.')
    print('data는 이렇게 생겼습니다.')
    print(data)
    print('data를 더 자세히 보면')
    print(i[0][0][0]) # 4차원 데이터임을 알 수 있다.
    print('len(data[0]) : ',len(data[0])) # data[0]은 vector값
    print('len(data[1]) : ',len(data[1])) # data[1]은 label값
    answer+=1
더보기
더보기

 

1 번째 step을 보고 계십니다.
data는 이렇게 생겼습니다.
[tensor([[[[0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          ...,
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.]]],


        [[[0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          ...,
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.]]],


        [[[0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          ...,
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.]]],


        ...,


        [[[0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          ...,
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.]]],


        [[[0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          ...,
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.]]],


        [[[0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          ...,
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.],
          [0., 0., 0.,  ..., 0., 0., 0.]]]]), tensor([6, 9, 6, 2, 0, 7, 0, 1, 2, 0, 5, 7, 3, 0, 1, 0, 1, 5, 5, 5, 3, 1, 3, 7,
        3, 1, 7, 9, 1, 3, 1, 0, 0, 3, 9, 2, 7, 7, 6, 5, 3, 3, 6, 1, 8, 1, 5, 4,
        1, 3, 7, 6, 3, 2, 7, 6, 9, 8, 4, 3, 6, 9, 2, 5, 1, 3, 1, 1, 3, 3, 8, 8,
        2, 3, 2, 6, 5, 7, 6, 7, 5, 0, 5, 7, 3, 4, 5, 7, 2, 3, 7, 5, 5, 6, 8, 3,
        0, 5, 4, 0, 4, 5, 4, 6, 0, 2, 8, 1, 9, 2, 1, 9, 4, 3, 3, 7, 9, 2, 7, 0,
        9, 5, 2, 5, 8, 0, 1, 8, 1, 6, 5, 9, 9, 4, 9, 8, 6, 2, 8, 2, 1, 8, 3, 7,
        0, 7, 6, 0, 5, 3, 6, 3, 3, 3, 8, 4, 9, 0, 7, 4, 6, 2, 4, 8, 4, 5, 1, 4,
        3, 9, 4, 3, 4, 2, 5, 0, 7, 9, 6, 4, 0, 1, 8, 2, 1, 6, 9, 8, 5, 0, 1, 5,
        2, 0, 4, 1, 6, 6, 6, 1, 5, 7, 8, 5, 9, 6, 2, 8, 9, 8, 6, 7, 2, 0, 8, 0,
        2, 2, 4, 6, 5, 1, 3, 6, 2, 3, 4, 4, 6, 2, 8, 6, 2, 8, 9, 0, 8, 7, 2, 4,
        6, 9, 1, 1, 5, 8, 3, 5, 1, 0, 0, 0, 1, 8, 4, 8])]
data를 더 자세히 보면
tensor([[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0431,
         0.5569, 1.0000, 0.3294, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.6824,
         0.9961, 0.8118, 0.0980, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0235, 0.4745, 0.9765,
         0.8314, 0.0902, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.3725, 0.9961, 0.9961,
         0.3529, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0902, 0.8980, 0.9961, 0.6353,
         0.0314, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.4627, 0.9961, 0.9961, 0.0745,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.2706, 0.9608, 0.9843, 0.4196, 0.0039,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.2353, 0.9451, 0.9961, 0.7373, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.7137, 0.9961, 0.8627, 0.0588, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.1922, 0.9216, 0.9961, 0.8431, 0.0353, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.6039, 0.9961, 0.9961, 0.9961, 0.6588, 0.0196, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.3490, 0.9843, 0.9961, 0.6627, 0.8431, 0.9961, 0.7765, 0.6118,
         0.3020, 0.1020, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.7137, 0.9961, 0.7176, 0.0235, 0.1137, 0.8275, 0.9961, 0.9961,
         0.9961, 0.6980, 0.0510, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.2353, 0.9373, 0.9961, 0.3882, 0.0000, 0.0000, 0.0902, 0.4627, 0.5804,
         0.9098, 0.9961, 0.2510, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.7216, 0.9961, 0.7490, 0.0588, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.7922, 0.9961, 0.4706, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.8471, 0.9961, 0.4118, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0275,
         0.8196, 0.9961, 0.4706, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.8471, 0.9961, 0.4118, 0.0000, 0.0000, 0.0000, 0.0118, 0.2784, 0.7843,
         0.9961, 0.8745, 0.1412, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.8471, 0.9961, 0.7059, 0.2667, 0.2667, 0.3373, 0.7922, 0.9961, 0.9961,
         0.8706, 0.1216, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.3216, 0.9961, 0.9961, 0.9961, 0.9961, 0.9961, 0.9961, 0.9529, 0.5137,
         0.1412, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0235, 0.6392, 0.9961, 0.9961, 0.8745, 0.6157, 0.4549, 0.1686, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000],
        [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
         0.0000]])
len(data[0]) :  256
len(data[1]) :  256

3. Model & Optimizer

3-1. Model

# in my case: 784(28*28) -> 100 -> 30 -> 100 -> 784(28*28)

class Autoencoder(nn.Module):
    def __init__(self):
        super(Autoencoder,self).__init__() # 이렇게 해줘야 부모모듈(nn.Module)의 __init__()을 사용 할 수 있다.
        self.encoder = nn.Sequential(
            nn.Linear(28*28, 100),    
            nn.ReLU(), # activation function
            nn.Linear(100, 30),
            nn.ReLU() # activation function
        )
        
        self.decoder = nn.Sequential(
            nn.Linear(30, 100),
            nn.ReLU(), # activation function
            nn.Linear(100, 28*28),
            nn.Sigmoid() # activation function
        )

    # forwaed : 순전파 (<-> backward : 역전파)
    def forward(self, x): # x: (batch_size, 1, 28, 28) 
        batch_size = x.size(0)
        x = x.view(-1, 28*28) # reshape to 784(28*28)-dimensional vector
        # 28*28 이미지 60000개를 batch_size인 256으로 나누다보면 자투리(나머지)가 남는데 그 것을 자동으로 채워준다.
        encoded = self.encoder(x) # hidden vector
        out = self.decoder(encoded).view(batch_size,1,28,28) # final output. resize to input's size
        return out, encoded

Decoder의 마지막 activation functionSigmoid를 사용해야함!!

AutoEncoder는 input과 output이 동일해야한다.
input으로 0~1을 사용했으므로 output으로도 0~1을 출력해야 제대로 reconstruction을 해 주는것이다.
mnist_train[0][0]를 보면 Sigmoid minist value가 discrete value가 아니고 continuous value이므로 마지막 layer의 activation function은 sigmoid를 써야 output이 0~1 사잇값으로 input과 동일하게 출력한다.

3-2. Loss func & Optimizer

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# GPU 사용이 가능하다면 "cuda:0"이, 아니면 "cpu"를 device에 저장
print(device)
# 데이터 뒤에서 .to()를 사용
model = Autoencoder().to(device)
# 모델에서 사용하는 input Tensor들은 input = input.to(device) 을 호출해야 한다.

loss_func = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

loss function으로는 MSE(Mean Squared Error) 사용

optimizer로는 Adam 사용
-> '어떤 Optimizer를 써야되는지 잘 모르겠다면 Adam을 써라'라는 말도 있다 => optimizer은 나중에 따로 정리

4. Train

import time
import copy

def train_model(model, dataloaders, criterion, optimizer, num_epochs=10):
    """
    model: model to train
    dataloaders: train, val, test data's loader
    criterion: loss function
    optimizer: optimizer to update your model
    """
    since = time.time() # 시간 측정

    # visualize를 위한 history를 저장할 lists
    train_loss_history = [] 
    val_loss_history = []
    
    best_model_wts = copy.deepcopy(model.state_dict()) # 가장 좋은 파라미터 저장을 위한 deepcopy
    best_val_loss = 100000000 # 굉장히 큰 수를 val_loss를 저장하고 이것보다 작으면 update(처음엔 무조건 저장)

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

        # Each epoch has a training and validation phase
        for phase in ['train', 'val']:
            if phase == 'train':
                model.train()           
            else:
                model.eval()          

            running_loss = 0.0

            # Iterate over data.
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device) # transfer inputs to GPU 

                # zero the parameter gradients
                optimizer.zero_grad()

                # forward
                # track history if only in train
                with torch.set_grad_enabled(phase == 'train'):

                    outputs, encoded = model(inputs)
                    loss = criterion(outputs, inputs) # calculate a loss


                    # backward + optimize 는 train phase일 때만
                    if phase == 'train':
                        loss.backward() # perform back-propagation from the loss (train일때만)
                        optimizer.step() # perform gradient descent with given optimizer

                # statistics
                running_loss += loss.item() * inputs.size(0)                    

            epoch_loss = running_loss / len(dataloaders[phase].dataset)

            print('{} Loss: {:.4f}'.format(phase, epoch_loss))
            
            # deep copy the model
            if phase == 'train':
                train_loss_history.append(epoch_loss)
            
            if phase == 'val':
                val_loss_history.append(epoch_loss)

            if phase == 'val' and epoch_loss < best_val_loss:
                best_val_loss = epoch_loss
                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 Loss: {:4f}'.format(best_val_loss))

    # load best model weights
    model.load_state_dict(best_model_wts)
    return model, train_loss_history, val_loss_history
best_model, train_loss_history, val_loss_history = train_model(model, dataloaders, loss_func, optimizer, num_epochs=num_epochs)
# train Loss는 모델이 정말 크면 0으로 만들 수 있다
# => validation Loss는 그렇지 않다 ,, 따라서 validation Loss가 더이상 떨어지지 않을때! 까지 학습시켜야한다.
# => validation data set이 필요한 이유
'''
Epoch 0/9
----------
train Loss: 0.1165
val Loss: 0.0703

Epoch 1/9
----------
train Loss: 0.0668
val Loss: 0.0608

Epoch 2/9
----------
train Loss: 0.0530
val Loss: 0.0469

Epoch 3/9
----------
train Loss: 0.0437
val Loss: 0.0409

Epoch 4/9
----------
train Loss: 0.0385
val Loss: 0.0369

Epoch 5/9
----------
train Loss: 0.0352
val Loss: 0.0336

Epoch 6/9
----------
train Loss: 0.0319
val Loss: 0.0306

Epoch 7/9
----------
train Loss: 0.0293
val Loss: 0.0283

Epoch 8/9
----------
train Loss: 0.0272
val Loss: 0.0265

Epoch 9/9
----------
train Loss: 0.0256
val Loss: 0.0249

Training complete in 0m 42s
Best val Loss: 0.024902
'''
# Visualize learning curve
plt.plot(train_loss_history, label = 'train')
plt.plot(val_loss_history, label = 'val')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend()
plt.show()

Learning curve

5. Check with Test Image

# train case와 비슷하나 backpropagation이나 gradient descent step이 없다.
with torch.no_grad():
    running_loss = 0.0
    for inputs, labels in dataloaders["test"]:
        inputs = inputs.to(device)

        outputs, encoded = best_model(inputs)
        test_loss = loss_func(outputs, inputs)
        
        running_loss += test_loss.item() * inputs.size(0)

    test_loss = running_loss / len(dataloaders["test"].dataset)
    print(test_loss)
    
  # 0.02443529677391052
  • torch.no_grad() : gradient 연산을 하지 않는다
    • 메모리 사용을 아껴줌 + 계산속도 증가
  • model.eval() : Dropout이나 Batchnorm 등 학습할때 사용하는 개념들은 비활성화
    • 해당 모델의 모든 레이어가 eval mode에 들어가게 해준다.
out_img = torch.squeeze(outputs.cpu().data)
print(out_img.size())

for i in range(5):
    plt.subplot(1,2,1)
    plt.imshow(torch.squeeze(inputs[i]).cpu().numpy(),cmap='gray')
    plt.subplot(1,2,2)
    plt.imshow(out_img[i].numpy(),cmap='gray')
    plt.show()

epoch = 1(좌) vs epoch = 10(우)

6. Visualizing MNIST

np.random.seed(42)
from sklearn.manifold import TSNE
test_dataset_array = mnist_test.data.numpy() / 255
test_dataset_array = np.float32(test_dataset_array)
labels = mnist_test.targets.numpy()
test_dataset_array = torch.tensor(test_dataset_array)
inputs = test_dataset_array.to(device)
outputs, encoded = best_model(inputs) # 여기서 뽑은 encoded는 30dimension
encoded = encoded.cpu().detach().numpy()
tsne = TSNE()   
X_test_2D = tsne.fit_transform(encoded) # 30 dimension짜리 enocded를 2 dimension으로 바꾸어야함
X_test_2D = (X_test_2D - X_test_2D.min()) / (X_test_2D.max() - X_test_2D.min())

AutoEncoder로 30차원으로 줄여준 후 t-SNE로 다시 2차원으로 줄여서 시각화 하는 이유는?
(=> 처음부터 t-SNE로 2차원으로 줄이던가, AutoEncoder로 2차원으로 바로 줄이면 안되나?)
처음부터 t-SNE로 2차원으로 줄이면 => 굉장히 오래 걸릴 것이다.
AutoEncoder로 2차원으로 바로 줄이면 => 분류가 되긴 하지만 잘 안 될 것이다.

AutoEncoder의 목적은 reconstruction error minimize, t-SNE친밀도(Similarity)가 가까운 값끼리 묶는 것이다.
plt.scatter(X_test_2D[:, 0], X_test_2D[:, 1], c=labels, s=10, cmap="tab10")
plt.axis("off")
plt.show()

MNIST 시각화

# adapted from https://scikit-learn.org/stable/auto_examples/manifold/plot_lle_digits.html
plt.figure(figsize=(10, 8))
cmap = plt.cm.tab10
plt.scatter(X_test_2D[:, 0], X_test_2D[:, 1], c=labels, s=10, cmap=cmap)
image_positions = np.array([[1., 1.]])
for index, position in enumerate(X_test_2D):
    dist = np.sum((position - image_positions) ** 2, axis=1)
    if np.min(dist) > 0.02: # if far enough from other images
        image_positions = np.r_[image_positions, [position]]
        imagebox = mpl.offsetbox.AnnotationBbox(
            mpl.offsetbox.OffsetImage(torch.squeeze(inputs).cpu().numpy()[index], cmap="binary"),
            position, bboxprops={"edgecolor": cmap(labels[index]), "lw": 2})
        plt.gca().add_artist(imagebox)
plt.axis("off")
plt.show()

MNIST 시각화 (label추가)

7. + Denoising Autoencoder

train_model()에서 noise를 추가해준 것 빼고는 일반적인 AutoEncoder 코드와 동일하다.

 

model_D = Autoencoder().to(device)
loss_func = nn.MSELoss()
optimizer = torch.optim.Adam(model_D.parameters(), lr=learning_rate)
def train_model_D(model, dataloaders, criterion, optimizer, num_epochs=10):
    """
    model: model to train
    dataloaders: train, val, test data's loader
    criterion: loss function
    optimizer: optimizer to update your model
    """
    since = time.time() # 시간 측정

    train_loss_history = []
    val_loss_history = []

    best_model_wts = copy.deepcopy(model.state_dict()) # 가장 좋은 파라미터 저장을 위한 deepcopy
    best_val_loss = 100000000 # 굉장히 큰 수를 val_loss를 저장하고 이것보다 작으면 update(처음엔 무조건 저장)

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

        # Each epoch has a training and validation phase
        for phase in ['train', 'val']:
            if phase == 'train':
                model.train() # Set model to training mode
            else:
                model.eval() # Set model to evaluate mode

            running_loss = 0.0

            # Iterate over data.
            for inputs, labels in dataloaders[phase]:
                noise = torch.zeros(inputs.size(0), 1, 28, 28) # DAE에서 추가된 부분
                nn.init.normal_(noise, 0, 0.1) # DAE에서 추가된 부분
                # noise는 1이 하얀색이므로 0에 가까운(굉장히 작은) 값을 noise로 넣어줘야
                # 1이 7이되거나 6이 8이되는 등의 변형을 막을 수 있다.
                noise = noise.to(device)
                inputs = inputs.to(device) # transfer inputs to GPU 
                noise_inputs = noise + inputs
                # inputs과 noise_inputs을 구분해주는 이유는 loss를 구할때 noise를 넣지 않은 inputs이 필요하기 때문
                
                # zero the parameter gradients
                optimizer.zero_grad()

                # forward
                # track history if only in train
                with torch.set_grad_enabled(phase == 'train'):

                    outputs, encoded = model(inputs)
                    loss = criterion(outputs, inputs) # calculate a loss


                    # backward + optimize only if in training phase
                    if phase == 'train':
                        loss.backward() # perform back-propagation from the loss (train일때만)
                        optimizer.step() # perform gradient descent with given optimizer

                # statistics
                running_loss += loss.item() * inputs.size(0)                    

            epoch_loss = running_loss / len(dataloaders[phase].dataset)

            print('{} Loss: {:.4f}'.format(phase, epoch_loss))
            
            # deep copy the model
            if phase == 'train':
                train_loss_history.append(epoch_loss)
            
            if phase == 'val':
                val_loss_history.append(epoch_loss)

            if phase == 'val' and epoch_loss < best_val_loss:
                best_val_loss = epoch_loss
                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 Loss: {:4f}'.format(best_val_loss))

    # load best model weights
    model.load_state_dict(best_model_wts)
    return model, train_loss_history, val_loss_history
best_model_D, train_loss_history_D, val_loss_history_D = train_model_D(model_D, dataloaders, loss_func, optimizer, num_epochs=20)
plt.plot(train_loss_history_D, label = 'train')
plt.plot(val_loss_history_D, label = 'val')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend()
plt.show()

with torch.no_grad():
  running_loss = 0.0
  for inputs, labels in dataloaders['test']:
      noise = nn.init.normal_(torch.FloatTensor(inputs.size(0), 1, 28, 28), 0, 0.1)
      noise = noise.to(device)
      inputs = inputs.to(device)
      noise_inputs = inputs + noise

      outputs, encoded = best_model_D(noise_inputs)
      test_loss = loss_func(outputs, inputs)
      
      running_loss += test_loss.item()* inputs.size(0)

  test_loss = running_loss / len(dataloaders['test'].dataset)
  print(test_loss)
  # 0.01693590996414423
out_img = torch.squeeze(outputs.cpu().data)
print(out_img.size())

for i in range(5):
    plt.subplot(1,2,1)
    plt.imshow(torch.squeeze(noise_inputs[i]).cpu().numpy(),cmap='gray')
    plt.subplot(1,2,2)
    plt.imshow(out_img[i].numpy(),cmap='gray')
    plt.show()

Denoising Auto Encoder의 input(좌)과 output(우)

댓글