가장 기본모델인 Vanilla GAN 구현하기
May 15, 2020

GAN 이 참 핫합니다. 이번에는 가장 시초격인 Vanila GAN을 구현해 보도록 하겠습니다.

이번 포스팅은 실습을 위한 내용만 다룹니다. GAN 에 대한 기본적인 개념정도는 이해하고 있다는 것을 전제로 합니다.

언어에는 BERT 그리고, 비전분야에서는 GAN이 최근 굉장이 핫한 딥러닝 분야로 떠올랐습니다.

이에 관련한 논문도 쏟아져 나왔습니다.

제 생각에는 연구 분야가 핫해지는 계기는 바로 상업성이 있어서가 아닐까요?

어찌됐건, GAN의 시초격인 Vanila GAN을 구현해 보도록 하겠습니다. 앞서 말씀드렸던 것처럼 개념 설명을 과감하게 건너 뜁니다!

실습 개요

소스코드는 아래 링크에서 다운로드 받을 수 있습니다.

깃헙

코랩

실습은 tensorflow.keras.datasets에 있는 MNIST 데이터셋을 활용합니다.

MNIST 손글씨 데이터를 활용하여 학습한 뒤, GAN으로 가짜 손글씨 데이터를 생성해 내는 것이 이번 실습의 최종 목표입니다!

미리 결과를 보여드리자면, 아래와 같이 됩니다.

Epoch 1 (초기)

captured_20200515024206

Epoch 50 (학습 후)

captured_20200515024627

손실 그래프

captured_20200515024700


Vanilla Gan Tutorial!

%%javascript
IPython.OutputArea.auto_scroll_threshold = 9999;
from tensorflow.keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

generator에서 tanh를 activation으로 활용합니다.

tanh를 활용하면 output이 -1 ~ 1 사이로 나오기 때문에 Normalize를 해줄 때 127.5로 나눈 뒤 1을 빼줍니다.

x_train = x_train / 127.5 - 1
x_test = x_test / 127.5 - 1

min 값과 max 값이 -1 ~ 1사이의 범위를 가져야 합니다.

x_train.min(), x_train.max()
(-1.0, 1.0)

x_train 값은 현재 28 * 28로 되어 있습니다. 이를 Flatten하게 펴줍니다.

x_train = x_train.reshape(-1, 784)
x_train.shape
(60000, 784)

필요한 모듈을 import 합니다.

from tensorflow.keras.layers import Dense, LeakyReLU, Dropout, Input
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.initializers import RandomNormal
import numpy as np
import matplotlib.pyplot as plt

Hyperparameters

NOISE_DIM을 정의 합니다.

NOISE_DIM은 자유롭게 설정할 수 있으며, generator의 input으로 들어갑니다.

# gan에 입력되는 noise에 대한 dimension
NOISE_DIM = 10

# adam optimizer 정의, learning_rate = 0.0002, beta_1로 줍니다.
# Vanilla Gan과 DCGAN에서 이렇게 셋팅을 해주는데
# 이렇게 해줘야 훨씬 학습을 잘합니다.
adam = Adam(lr=0.0002, beta_1=0.5)

Generator

generator를 정의합니다.

generator = Sequential([
    Dense(256, input_dim=NOISE_DIM), 
    LeakyReLU(0.2), 
    Dense(512), 
    LeakyReLU(0.2), 
    Dense(1024), 
    LeakyReLU(0.2), 
    Dense(28*28, activation='tanh'),
])
generator.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 256)               2816      
_________________________________________________________________
leaky_re_lu (LeakyReLU)      (None, 256)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 512)               131584    
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 512)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 1024)              525312    
_________________________________________________________________
leaky_re_lu_2 (LeakyReLU)    (None, 1024)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 784)               803600    
=================================================================
Total params: 1,463,312
Trainable params: 1,463,312
Non-trainable params: 0
_________________________________________________________________

Discriminator

discriminator를 정의합니다.

discriminator = Sequential([
    Dense(1024, input_shape=(784,), kernel_initializer=RandomNormal(stddev=0.02)),
    LeakyReLU(0.2), 
    Dropout(0.3), 
    Dense(512),
    LeakyReLU(0.2), 
    Dropout(0.3), 
    Dense(256),
    LeakyReLU(0.2), 
    Dropout(0.3), 
    Dense(1, activation='sigmoid')
])
discriminator.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_4 (Dense)              (None, 1024)              803840    
_________________________________________________________________
leaky_re_lu_3 (LeakyReLU)    (None, 1024)              0         
_________________________________________________________________
dropout (Dropout)            (None, 1024)              0         
_________________________________________________________________
dense_5 (Dense)              (None, 512)               524800    
_________________________________________________________________
leaky_re_lu_4 (LeakyReLU)    (None, 512)               0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_6 (Dense)              (None, 256)               131328    
_________________________________________________________________
leaky_re_lu_5 (LeakyReLU)    (None, 256)               0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_7 (Dense)              (None, 1)                 257       
=================================================================
Total params: 1,460,225
Trainable params: 1,460,225
Non-trainable params: 0
_________________________________________________________________

반드시 dicriminator를 compile 해주어야 합니다.

discriminator.compile(loss='binary_crossentropy', optimizer=adam)

Gan

generator와 discriminator를 연결합니다.

# discriminator는 학습을 하지 않도록 하며, Gan 모델에서는 generator만 학습하도록 합니다.
discriminator.trainable = False
gan_input = Input(shape=(NOISE_DIM,))
x = generator(inputs=gan_input)
output = discriminator(x)

gan 모델을 정의합니다.

gan = Model(gan_input, output)
gan.summary()
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 10)]              0         
_________________________________________________________________
sequential (Sequential)      (None, 784)               1463312   
_________________________________________________________________
sequential_1 (Sequential)    (None, 1)                 1460225   
=================================================================
Total params: 2,923,537
Trainable params: 1,463,312
Non-trainable params: 1,460,225
_________________________________________________________________

Compile

gan.compile(loss='binary_crossentropy', optimizer=adam)

Batch

이미지 batch를 생성합니다. MNIST 이미지 batch가 차례대로 생성됩니다.

def get_batches(data, batch_size):
    batches = []
    for i in range(int(data.shape[0] // batch_size)):
        batch = data[i * batch_size: (i + 1) * batch_size]
        batches.append(batch)
    return np.asarray(batches)

시각화를 위한 유틸 함수 정의

def visualize_training(epoch, d_losses, g_losses):
    # 오차에 대한 시각화
    plt.figure(figsize=(8, 4))
    plt.plot(d_losses, label='Discriminator Loss')
    plt.plot(g_losses, label='Generatror Loss')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.legend()
    plt.show()

    print('epoch: {}, Discriminator Loss: {}, Generator Loss: {}'.format(epoch, np.asarray(d_losses).mean(), np.asarray(g_losses).mean()))
    
    #샘플 데이터 생성 후 시각화
    noise = np.random.normal(0, 1, size=(24, NOISE_DIM))
    generated_images = generator.predict(noise)
    generated_images = generated_images.reshape(-1, 28, 28)
    
    plt.figure(figsize=(8, 4))
    for i in range(generated_images.shape[0]):
        plt.subplot(4, 6, i+1)
        plt.imshow(generated_images[i], interpolation='nearest', cmap='gray')
        plt.axis('off')
    plt.tight_layout()
    plt.show()

학습

BATCH_SIZE = 128
EPOCHS= 50
# discriminator와 gan 모델의 loss 측정을 위한 list 입니다.
d_losses = []
g_losses = []

for epoch in range(1, EPOCHS + 1):
    # 각 배치별 학습
    for real_images in get_batches(x_train, BATCH_SIZE):
        # 랜덤 노이즈 생성
        input_noise = np.random.uniform(-1, 1, size=[BATCH_SIZE, NOISE_DIM])
        
        # 가짜 이미지 데이터 생성
        generated_images = generator.predict(input_noise)
        
        # Gan에 학습할 X 데이터 정의
        x_dis = np.concatenate([real_images, generated_images])
        
        # Gan에 학습할 Y 데이터 정의
        y_dis = np.zeros(2 * BATCH_SIZE)
        y_dis[:BATCH_SIZE] = 0.9
        
        # Discriminator 훈련
        discriminator.trainable = True
        d_loss = discriminator.train_on_batch(x_dis, y_dis)
        
        # Gan 훈련
        noise = np.random.uniform(-1, 1, size=[BATCH_SIZE, NOISE_DIM])
        y_gan = np.ones(BATCH_SIZE)
        
        # Discriminator의 판별 학습을 방지합니다
        discriminator.trainable = False
        g_loss = gan.train_on_batch(noise, y_gan)
        
    d_losses.append(d_loss)
    g_losses.append(g_loss)
    
    if epoch == 1 or epoch % 5 == 0:
        visualize_training(epoch, d_losses, g_losses)
epoch: 1, Discriminator Loss: 0.37829217314720154, Generator Loss: 2.2353384494781494
epoch: 5, Discriminator Loss: 0.38584211468696594, Generator Loss: 2.676882028579712
epoch: 10, Discriminator Loss: 0.43150606751441956, Generator Loss: 2.3001761436462402
epoch: 15, Discriminator Loss: 0.4741574227809906, Generator Loss: 2.0772454738616943
epoch: 20, Discriminator Loss: 0.5015758275985718, Generator Loss: 1.942042350769043
epoch: 25, Discriminator Loss: 0.5174500942230225, Generator Loss: 1.841748833656311
epoch: 30, Discriminator Loss: 0.5320706963539124, Generator Loss: 1.7638866901397705
epoch: 35, Discriminator Loss: 0.5399990081787109, Generator Loss: 1.6962034702301025
epoch: 40, Discriminator Loss: 0.546463668346405, Generator Loss: 1.6465957164764404
epoch: 45, Discriminator Loss: 0.5529017448425293, Generator Loss: 1.6096774339675903
epoch: 50, Discriminator Loss: 0.5575451850891113, Generator Loss: 1.5767810344696045


관련 글 더보기

- TensorFlow2.0 GradientTape의 활용법

- TensorFlow2.0으로 오토인코더 구현 (MNIST)

- VGG16 Transfer Learning 구현과 CNN 모델과 성능 비교

- TensorBoard 활용법 및 colab에서 로드하기

- TensorFlow Datasets API 활용법

데이터 분석, 머신러닝, 딥러닝의 대중화를 꿈 꿉니다.