Generative Adversarial Network (GAN)

🗝 Keyword




📑 Concept


GAN(Generative Adversarial Networks)

  • GAN(Generative Adversarial Networks, 생성적 적대 신경망)

  • 실제와 유사한 데이터를 만들어내는 생성 모델

  • like 위조지폐



생성자(Generator)

  • 실제와 동일한 데이터를 생성하는 것이 목적

  • => 비지도 학습

  • 위조지폐범

  • G



판별자(Discriminator)

  • 생성된 데이터가 진짜(Real)인지 아닌지(Fake) 판단

  • => 지도 학습

  • Fake / Real로 구분
    -> 이진 분류

  • like 진품명품

  • 탐정

  • D



  • GAN
    => 생성자판별자서로 경쟁하며 성능을 개선하는 모델




GAN의 목적

  • '분포'를 만드는 모델을 학습하는 것

  • 분포
    => 데이터의 형태, 분산

  • ex) 픽셀들의 분포에 따라 코, 눈이라는 것을 인식.
    명암이나 사진의 전체적인 채도와는 큰 상관 X.
    -> 분포를 만들어낸다는 것은, 단순히 결과값을 도출해내는 함수를 만드는 것을 넘어 '실제적인 형태' 를 갖춘 데이터를 만들어낸다는 것

  • 학습을 잘 마친 GAN 모델에서 생성자는 실제와 유사한 이미지를 생성
    ->
    판별자가 실제(Real)와 가짜(Fake)를 잘 구분해내지 못하게 됨
    => Accuracy \approx 0.5
    -> (이진 분류니까 확률이 1/2 이라서)




GAN의 학습 방법

GAN 모델의 이해와 구현




⏪ Review


  • GAN

    • 비유를 들어 설명
    • 생성자(Generator)는 무엇을 입력받아 무엇을 출력?
    • 판별자(Discriminator)는 무엇을 입력받아 무엇을 출력?

  • CycleGAN

    • CycleGAN 이 기존 Pix2Pix 에 비해 갖는 장점은?
    • 왜 A와 B가 순환되는 형태로 모델이 구성?




💻 Code


GAN 코드 분석



Example - DCGAN(Deep Convolution GAN)


GAN을 통해 MNIST 손글씨 데이터 생성

# 필요한 라이브러리 import
from tensorflow.keras import layers
from IPython import display

import glob
import imageio
import os
import PIL
import time

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
# 데이터셋 불러오기
(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
# -> Test 데이터셋은 사용하지 않기 때문에 '_' 로 처리
# 이미지 정규화
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # 이미지를 [-1, 1]로 정규화
# 이미지를 [-1, 1]의 범위로 정규화하는 이유
# -> 활성화 함수로 tanh를 사용해서. 
BUFFER_SIZE = 60000
BATCH_SIZE = 256

# 데이터 배치 생성 후 섞기
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)



모델 구축


생성자

  • Random Noise 로부터 이미지를 생성하기 위해 Transpose Convolution 을 사용해 Upsampling 수행

  • 첫 Dense 층은 Random Noise 를 입력받으며 원하는 이미지 사이즈인 28X28 이 나오도록 Conv2DTranspose를 겹겹이 쌓음

  • 은닉층 활성화 함수: ReLU 함수의 변형인 LeakyReLU 함수 사용

  • 활성화 함수 이전배치 정규화(Batch Normalization) 적용


Leaky ReLU

  • LeakyReLU 함수

    • 음의 값이 0이 아니라 음수 값
    • 리키렐루를 사용한 이유
      -> 음의 값을 표현하기 위해

def make_generator_model():
    """
    생성자 모델 구축 함수
    """
    model = tf.keras.Sequential()
    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Reshape((7, 7, 256)))
    assert model.output_shape == (None, 7, 7, 256) # 배치사이즈로 None이 주어짐

    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))
    assert model.output_shape == (None, 7, 7, 128)
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
    assert model.output_shape == (None, 14, 14, 64)
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
    assert model.output_shape == (None, 28, 28, 1)

    return model



판별자

  • 합성곱 신경망(Convolutional Neural Network, CNN) 기반의 이미지 분류기

  • 은닉층 활성화 함수: LeakyReLU 함수

  • 드롭아웃(Dropout) 적용


def make_discriminator_model():
    """
    판별자 모델 구축 함수
    """
    model = tf.keras.Sequential()
    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same', input_shape=[28, 28, 1]))
    model.add(layers.LeakyReLU())
    model.add(layers.Dropout(0.3))

    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
    model.add(layers.LeakyReLU())
    model.add(layers.Dropout(0.3))

    model.add(layers.Flatten())
    model.add(layers.Dense(1))

    return model



손실함수

  • 생성자의 손실함수
    • 전체가 1인 행렬과 fake_output을 비교해서 구함

  • 판별자의 손실함수
    • real_lossfake_loss를 더한 값
    • -> Real, Fake 둘 다를 가지로 이진 분류하는 거라서 real_lossfake_loss를 더함
    • real_loss
      • 전체가 1인 행렬과 real_output을 비교해서 구함
    • fake_loss
      • 전체가 0인 행렬과 fake_output을 비교해서 구함

cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)


def discriminator_loss(real_output, fake_output):
    """
    판별자의 손실함수 
    """
    real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    total_loss = real_loss + fake_loss
    return total_loss
  
  
def generator_loss(fake_output):
    """
    생성자의 손실함수 
    """
    return cross_entropy(tf.ones_like(fake_output), fake_output)
# 옵티마이저
# -> 생성자, 판별자 모두 Adam 적용
generator_optimizer = tf.keras.optimizers.Adam(1e-4)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)
# 모델이 저장되는 Checkpoint 설정
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
                                 discriminator_optimizer=discriminator_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)



훈련 루프 지정

  1. 생성자가 입력으로 Random Noise 를 입력받음
  2. 입력받은 Random Noise 를 사용해 이미지 생성
  3. 판별자를 사용해 Train 데이터셋의 진짜 이미지와 생성자가 만들어낸 가짜 이미지를 분류
  4. 각 모델의 손실 계산, 경사 하강법과 역전파를 통해 가중치를 업데이트

EPOCHS = 50
noise_dim = 100
num_examples_to_generate = 16

seed = tf.random.normal([num_examples_to_generate, noise_dim])
@tf.function
def train_step(images):
    """
    Iteration 마다 어떻게 각 모델이 갱신되는 지에 대한 함수

    위에서 정의한 손실함수를 바탕으로
    Iteration(=step) 마다 가중치 갱신

    Args:
        images: 훈련 데이터셋에 있는 실제 이미지
    """
    noise = tf.random.normal([BATCH_SIZE, noise_dim])

    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
        generated_images = generator(noise, training=True)

        real_output = discriminator(images, training=True)
        fake_output = discriminator(generated_images, training=True)

        gen_loss = generator_loss(fake_output)
        disc_loss = discriminator_loss(real_output, fake_output)

    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)

    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
def generate_and_save_images(model, epoch, test_input):
    """
    모델이 이미지를 생성한 후 저장하는 함수
    (중간 결과 확인하기 위해 생성된 이미지를 출력하고 저장하는 함수)
    
    Args:
        model: 이미지를 생성할 모델
        epoch: 진행 중인 Epoch 숫자
        test_input: model에 입력되는 데이터
    """

    # training=False -> 모든 층이 추론(inference)모드로 진행
    predictions = model(test_input, training=False)

    fig = plt.figure(figsize=(4,4))

    for i in range(predictions.shape[0]):
        plt.subplot(4, 4, i+1)
        plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
        plt.axis('off')

    plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
    plt.show()
def train(dataset, epochs):
    """
    학습 중 실행할 동작을 함수로 정의
    
    Args:
        dataset: (훈련) 데이터셋
        epochs: 최종 학습 Epoch
    """
    for epoch in range(epochs):
        start = time.time()

        for image_batch in dataset:
            train_step(image_batch)

        # 이미지를 생성한 뒤 저장(추후에 만들 GIF를 위함)
        display.clear_output(wait=True)
        generate_and_save_images(generator, epoch + 1, seed)

        # 15 에포크가 지날 때마다 모델을 Checkpoint에 저장
        if (epoch + 1) % 15 == 0:
            checkpoint.save(file_prefix = checkpoint_prefix)
        
        # Epoch 마다 소요 시간 출력
        print(f'Time for epoch {epoch + 1} is {time.time()-start} sec')

    # 마지막 에포크가 끝난 후 이미지 생성
    display.clear_output(wait=True)
    generate_and_save_images(generator, epochs, seed)

# 함수를 실행시켜 실제 훈련 진행

%%time
train(train_dataset, EPOCHS)

# 마지막 Checkpoint 복구
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
# gif 생성
def display_image(epoch_no):
    """
    특정 Epoch에 생성된 이미지를 불러오는 함수

    Args:
        epoch_no: 특정 Epoch에 해당하는 숫자
    """
    return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
    
 
 display_image(EPOCHS)
# 훈련 중에 저장된 이미지를 이어붙여 gif 애니메이션 만들기

anim_file = 'dcgan.gif'

with imageio.get_writer(anim_file, mode='I') as writer:
    filenames = glob.glob('image*.png')
    filenames = sorted(filenames)
    last = -1
    
    for i,filename in enumerate(filenames):
        frame = 2*(i**0.5)
        if round(frame) > round(last):
            last = frame
        else:
            continue
    
        image = imageio.imread(filename)
        writer.append_data(image)
    
    image = imageio.imread(filename)
    writer.append_data(image)
    
 
import IPython
if IPython.version_info > (6,2,0,''):
  display.Image(filename=anim_file)
# 애니메이션 다운받기
# -> 코랩에서만 가능

try:
    from google.colab import files
except ImportError:
    pass
else:
    files.download(anim_file)

좋은 웹페이지 즐겨찾기