/ AI

11월 논문 리뷰 2주차 GAN 코드 실습 Python

11월 논문 리뷰 2주차 GAN 코드 실습 Python

Generative Adversarial Nets

1. 코드 실습하기(with Colab)

# -*- coding: utf-8 -*-
"""GAN for MNIST Tutorial의 사본

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/13chH2RWgHPgTJW5H12Oj756Eq_Js49yl

#### <b>GAN 실습</b>

* 논문 제목: Generative Adversarial Networks <b>(NIPS 2014)</b>
* 가장 기본적인 GAN 모델을 학습해보는 실습을 진행합니다.
* 학습 데이터셋: <b>MNIST</b> (1 X 28 X 28)

#### <b>필요한 라이브러리 불러오기</b>

* 실습을 위한 PyTorch 라이브러리를 불러옵니다.
"""

import torch    # 머신러닝을 위한 torch 라이브러리
import torch.nn as nn   # 생성자와 판별자의 아키텍쳐를 구별하기 위해

from torchvision import datasets        # mnist를 불러오기 위해
import torchvision.transforms as transforms       # 불러온 데이터 셋을 의도한 방향으로 변형하여 전처리하기 위해
from torchvision.utils import save_image        # 학습 과정에서 반복적으로 생성된 이미지를 출력하기 위해

"""#### <b>생성자(Generator) 및 판별자(Discriminator) 모델 정의</b>"""

latent_dim = 100        # latent 벡터를 뽑기 위한 노이즈 분포의 크기
                        # 정규분포 이용

# 생성자(Generator) 클래스 정의
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()       # 자식 클래스에서 부모 클래스의 내용을 사용하고 싶을 때 suoper 이용(객체지향)

        # 하나의 블록(block) 정의 # 블록을 원하는 만큼 사용 가능
        def block(input_dim, output_dim, normalize=True):
            layers = [nn.Linear(input_dim, output_dim)]       # 하나의 선형함수 거친후에
            if normalize:
                # 배치 정규화(batch normalization) 수행(차원 동일)
                layers.append(nn.BatchNorm1d(output_dim, 0.8))
            layers.append(nn.LeakyReLU(0.2, inplace=True))        # ReLU : 0 이하의 값은 다음 레이어에 전달하지 않습니다. 0이상의 값은 그대로 출력합니다. (한번 0을 전달하면 이후의 뉴런들의 출력값이 모두 0이 된다.) 
            return layers                                         # -> LeakyReLU : ReLU와 거의 비슷한 형태를 갖습니다. 입력 값이 음수일 때 완만한 선형 함수를 그려줍니다. 일반적으로 알파를 0.01로 설정합니다.(ax (x <= 0))    

        # 생성자 모델은 연속적인 여러 개의 블록을 가짐
        self.model = nn.Sequential(
            *block(latent_dim, 128, normalize=False),
            *block(128, 256),
            *block(256, 512),
            *block(512, 1024),
            nn.Linear(1024, 1 * 28 * 28),       # 결과적으로 1 * 1 * 28의 하나의 mnist 데이터 생성
            nn.Tanh()                           # 탄젠트로 -1 ~ 1 사이의 값을 가지게 한다
        )

    def forward(self, z):                       # 노이즈 벡터 z가 들어왔을 때 이미지 형태로 변환 후 리턴
        img = self.model(z)
        img = img.view(img.size(0), 1, 28, 28)
        return img

# 판별자(Discriminator) 클래스 정의
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()

        self.model = nn.Sequential(             # 판별자는 이미지를 받아와서 선형함수와 Activate Function 인 LeakyReLU 를 여러번 수행한다
            nn.Linear(1 * 28 * 28, 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 1),
            nn.Sigmoid(),                       # 결과적으로 Sigmoid 함수로 확률 값을 내보낼 수 있도록 한다 -> Sigmoid : 0~1로 비선형적으로 서서히 변화하는 함수(신경망 활성화 함수)
        )

    # 이미지에 대한 판별 결과를 반환
    def forward(self, img):
        flattened = img.view(img.size(0), -1)   # 이미지가 들어왔을때 벡터 형태로 나열 후
        output = self.model(flattened)          # 모델에 넣어 결과 리턴

        return output

"""#### <b>학습 데이터셋 불러오기</b>

* 학습을 위해 MNIST 데이터셋을 불러옵니다.
"""

transforms_train = transforms.Compose([
    transforms.Resize(28),
    transforms.ToTensor(),
    transforms.Normalize([0.5], [0.5])
])        # 텐서 형태로 크기에 맞춰 만든다

# 학습 데이터를 불러와 위의 형태로 전처리
train_dataset = datasets.MNIST(root="./dataset", train=True, download=True, transform=transforms_train)  
# 하나의 batch에 이미지 128개
dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=4)          
"""#### <b>모델 학습 및 샘플링</b>

* 학습을 위해 생성자와 판별자 모델을 초기화합니다.
* 적절한 하이퍼 파라미터를 설정합니다.
"""

# 생성자(generator)와 판별자(discriminator) 초기화
generator = Generator()
discriminator = Discriminator()

# .cuda() -> GPU 이용
generator.cuda()
discriminator.cuda()

# 손실 함수(loss function)
# 손실함수 BCELoss : 마지막 레이어의 값이 0~1(시그모이드 함수 사용 필수) 
# <-> CrossEntropy : 마지막 레이어의 노드 수가 2개 이상
adversarial_loss = nn.BCELoss()       
adversarial_loss.cuda()

# 학습률(learning rate) 설정
lr = 0.0002

# 생성자와 판별자를 위한 최적화 함수
# 옵티마이저는 학습 데이터(Train data)셋을 이용하여 모델을 학습 할 때
# 데이터의 실제 결과와 모델이 예측한 결과를 기반으로 잘 줄일 수 있게 만들어주는 역할을 한다.
# (Optimize : 최대한 좋게 만들다)
optimizer_G = torch.optim.Adam(generator.parameters(), lr=lr, betas=(0.5, 0.999))       
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=lr, betas=(0.5, 0.999))
# betas 파라미터는 가장 많이 사용되는 하이퍼 파라미터

"""* 모델을 학습하면서 주기적으로 샘플링하여 결과를 확인할 수 있습니다."""

import time

# 200번 반복
n_epochs = 200 # 학습의 횟수(epoch) 설정
sample_interval = 2000 # 몇 번의 배치(batch)마다 결과를 출력할 것인지 설정
start_time = time.time()

for epoch in range(n_epochs):
    # enumerate : 몇 번째 반복인지 튜플로 저장하여 알려줌 (print(i))
    for i, (imgs, _) in enumerate(dataloader):        

        # 진짜(real) 이미지와 가짜(fake) 이미지에 대한 정답 레이블 생성
        real = torch.cuda.FloatTensor(imgs.size(0), 1).fill_(1.0) # 진짜(real): 1
        fake = torch.cuda.FloatTensor(imgs.size(0), 1).fill_(0.0) # 가짜(fake): 0

        real_imgs = imgs.cuda()

        """ 생성자(generator)를 학습합니다. """
        optimizer_G.zero_grad()

        # 랜덤 노이즈(noise) 샘플링
        # 이미지의 개수 만큼 노이즈를 뽑아서 생성자에 넣는다
        z = torch.normal(mean=0, std=1, size=(imgs.shape[0], latent_dim)).cuda()        

        # 이미지 생성
        generated_imgs = generator(z)

        # 생성자(generator)의 손실(loss) 값 계산
        # real 이미지로 만들 수 있도록 학습
        g_loss = adversarial_loss(discriminator(generated_imgs), real)                  

        # 생성자(generator) 업데이트
        g_loss.backward()       # backward : 기울기 계산
        optimizer_G.step()      # step : 반복적으로 작업을 실행

        """ 판별자(discriminator)를 학습합니다. """
        optimizer_D.zero_grad()

        # 판별자(discriminator)의 손실(loss) 값 계산
        # 실제 이미지는 실제로 분류
        real_loss = adversarial_loss(discriminator(real_imgs), real)   
        # 생성자의 이미지는 가짜로 분류                 
        fake_loss = adversarial_loss(discriminator(generated_imgs.detach()), fake)      
        d_loss = (real_loss + fake_loss) / 2

        # 판별자(discriminator) 업데이트, 원래 논문은 판별자 업데이트 후에 생성자 업데이트
        d_loss.backward()
        optimizer_D.step()

        done = epoch * len(dataloader) + i
        if done % sample_interval == 0:
            # 생성된 이미지 중에서 25개만 선택하여 5 X 5 격자 이미지에 출력
            save_image(generated_imgs.data[:25], f"{done}.png", nrow=5, normalize=True)

    # 하나의 epoch이 끝날 때마다 로그(log) 출력
    print(f"[Epoch {epoch}/{n_epochs}] [D loss: {d_loss.item():.6f}] 
    [G loss: {g_loss.item():.6f}] [Elapsed time: {time.time() - start_time:.2f}s]")

"""* 생성된 이미지 예시를 출력합니다."""

from IPython.display import Image

Image('92000.png')