IT/AI\ML

[python/Tensorflow2.0]GANs(Generative Adversarial Networks) ; basic & DCGAN

개발자 두더지 2020. 4. 22. 17:12
728x90

1. GANs(Generative Adversarial Networks, 敵対的生成ネットワーク)란?

두 개의 모델이 적대적인 과정을 통해 동시에 훈련된다. 생성자(예술가)는 진짜 처럼 보이는 이미지를 생성하도록 배우는 와중에, 감별자(예술비평가)는 가짜의 이미지로부터 진짜를 구별하게 되는 것을 배우게 되는 것을 의미한다.

훈련과정 동안 생성자는 점차 실제같은 이미지를 더욱 잘 생성하게 되고, 감별자는 진짜와 가짜를 더욱 잘 구별하게 된다. 이 과정은 감별자가 가짜 이미지에서 진짜 이미지를 더 이상 구별하지 못하게 될 때 평행 상태에 도달하게 된다.

 조금 더 구체적으로는 얘기하자면 생성자가 균일한 분포나 정규 분포로 랜덤 벡터를 생성해 가짜 이미지를 만들어 내면 감별자는 그 이미지에 대해 진짜라고 판별되면 0으로, 가짜라고 판별되면 1로 하여 그러한 판단의 연속값을 반환한다. 

 


1) Discriminator(판별자)의 학습

 Discriminator는 이미지(img)를 입력 데이터로 받아 예상 결과(가짜인지 진짜인지)를 출력하므로, 학습 데이터는 그 이미지가 진짜인지 가짜인지에 대한 라벨이 된다. 즉, 진짜의 이미지(r-img)를 img로 입력한 때를 1의 정답 라벨을, 가짜 이미지(g-img)를 img로 입력 데이터를 받았을 때는 0의 정답 라벨을 부여한다.

 가짜의 이미지 (g-img)를 생성하기 위해서는 Generator의 입력으로 z에 랜덤한 수가 발생된 데이터(noise)가 전달된다. Generator로부터 생성된 이미지를 실제로 발생된(predict메소드를 사용한다) 가짜 이미지를 img로 보낸다. 여기서 Generator는 단지 이미지를 생성하기 위해 이용하는 것일 뿐이므로, 학습 대상인 Discriminator 완전히 분리된 다는 것을 주의하라. 생성된이미지는 numpy.array의 형으로 완전히 확정되어 있기 때문에 Generator는이경우 Discriminator의 학습과 관계가 없다.

 데이터 세트에 있는 진짜의 이미지(r-img)의 경우는 단순히 판별자의 입력 img에서 실제 데이터 r-img로 보내는 것으로 OK이다.


2) Generator(생성자)의 학습

   GAN의 최종출력은 이미지가 진짜인지 아닌지(의 확률)을 표시하는 변수 Valid이기 때문이다. Genarator의 학습 때도 Valid가 목표 변수가 된다.  이 부분에서 같은 생성 모델이지만 목적변수에 자신의 입력 데이터를 취하는 VAE(Valiational Auto Encoder)과는 다르다.  따라서, Genarator의 학습 때는 Discriminator까지를 포함한 네트워크를 이용한다. 위 그림 중 Combined 네트워크로서 표시된다.

 그러나 여기서는 어디까지나 Genarator만을 학습하기 때문에, Discriminator의 가중치를 고정할 필요가 있다. 그것은 Discriminator의 옵션을 trainable = False로 하면 달성된다. 입력 데이터는 랜덤수에 의해 발생된 값 noise이다. 여기서 Combined네트워크를 학습하는 것으로, Genarator만을 학습하는 것이 가능하다.

  여기서는 한 가지 트릭을 사용한다. noise로 부터 Genarator를 이용해 생성된 이미지는 당연히 가짜이므로, 원래 정답라벨은 0이지만, 여기서는 진짜 (1)의 라벨을 사용한다. 그 이유는 Generator의 목적은 Discriminator을 속이는 것이므로, 판별 결과는 진짜로 판별하는 것이 (Generator에 있어서) loss 함수가 작아지는 방향으로 진행되기 때문이다.

Discriminator, Generator의 학습 모두, 예상을 정답과 일치할 때 값이 작아지는 loss함수를 이용해 그 함수를 최소화하도록 학습한다. 라벨은 0, 1 두 값으로 나누어져 있으므로 loss함수로 binary crossentropy를 사용하는 것이 가능하다.


2. GANs를 기본 코드로 구현

 

(1) Keras 코드의 기본 구성

class GAN():
    def __init__(self):
        #mnistデータ用の入力データサイズ
        self.img_rows = 28 
        self.img_cols = 28
        self.channels = 1
        self.img_shape = (self.img_rows, self.img_cols, self.channels)

        # 潜在変数の次元数 
        self.z_dim = 100

        optimizer = Adam(0.0002, 0.5)

        # discriminatorモデル
        self.discriminator = self.build_discriminator()
        self.discriminator.compile(loss='binary_crossentropy', 
            optimizer=optimizer,
            metrics=['accuracy'])

        # Generatorモデル
        self.generator = self.build_generator()
        # generatorは単体で学習しないのでコンパイルは必要ない
        #self.generator.compile(loss='binary_crossentropy', optimizer=optimizer)

        self.combined = self.build_combined1()
        #self.combined = self.build_combined2()
        self.combined.compile(loss='binary_crossentropy', optimizer=optimizer)

    def build_generator(self):

        noise_shape = (self.z_dim,)
        model = Sequential()

        model.add(Dense(256, input_shape=noise_shape))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(1024))
        model.add(LeakyReLU(alpha=0.2))
        model.add(BatchNormalization(momentum=0.8))
        model.add(Dense(np.prod(self.img_shape), activation='tanh'))
        model.add(Reshape(self.img_shape))

        model.summary()

        return model

    def build_discriminator(self):

        img_shape = (self.img_rows, self.img_cols, self.channels)

        model = Sequential()

        model.add(Flatten(input_shape=img_shape))
        model.add(Dense(512))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dense(256))
        model.add(LeakyReLU(alpha=0.2))
        model.add(Dense(1, activation='sigmoid'))
        model.summary()

        return model

    def build_combined1(self):
        self.discriminator.trainable = False
        model = Sequential([self.generator, self.discriminator])
        return model

    def build_combined2(self):
        z = Input(shape=(self.z_dim,))
        img = self.generator(z)
        self.discriminator.trainable = False
        valid = self.discriminator(img)
        model = Model(z, valid)
        model.summary()
        return model

 먼저 , GAN 클래스를 정의하여 생성자에 GAN의 기본 구성을 구축해간다. 처음에는 입력 이미지의 shape를 정의한다. mnist는 높이 28, 폭 28, 1채널 (흑백)이다. 

 학습에 이용하는 optimizer를 정의한 후에는 기본 구조를 작성한다. 멤버 변수 discriminator 변수를 선언하고, bulid_discriminator 메소드의  리턴값을 격납한다. build_discriminator는 입력 데이터를 앞서 정의한 minist 데이터 모양으로 취하고 sigmoid함수에 의해 최종적으로 validity = [0, 1]를 출력하는 모델이다. 

 똑같이 bulid_generator 메소드에 의해 generator 변수를 선언한다. 원래는 이것도 컴파일하지만, generator에서는 학습하지 않으므로, 여기서의 컴파일은 필요하지 않다. 실제로 주석처리하여 무효화하여도 학습에 영향을 주지 않는다.

 그런데 generator의 학습할 때는 generator 자체가 아닌 어디까지나 discriminator를 연결하는 combined 네트워크를 이용한다고 설명했었다. combined 네트워크를 만들어보자. generator과 discriminator를 직렬로 연결한다. 여기서는 다소 중복되지만 두 가지 방법을 소개한다.

 첫 번째 방법은, 정의한 model명을 이용하여 새로운 combined model을 정의하는 방법이다. 코드의 bulid_combined1 메소드가 이러한 방법이다. Sequential[self.generator, self.discriminator]과 같이 Sequential모델을 리스트 형태로 작성하는 것으로, 이 모델의 입출력이 정해지기 때문에,어떤 모델의 출력이 다음 리스트의 모델의 입력으로 계속 전달된다. 그때 전 단계의 출력 shape와 입력 shape는 당연히 같을 필요가 있다.

 두 번째 방법은 model의 입출력 변수를 이용하여 새로운 combined model을 정의하는 방법이다. 이것은 bulid_combined2 메소드에 작성한 것과 대응된다. generator의 입력 z는 Input메소드를 이용해 선언하고, generator로부터 출력된 데이터를 img에 할당한다.  또한 생성 이미지 img를 discriminator에 전달한 출력을 valid로 데이터의  통로를 만든다. 최종적인 모델의 선언은 Model메소드에 최초의 입력과 최종의 출력을 지정하는 것으로 달성된다.

 설명이 간단한 것은 첫 번째 방법이지만, 실제의 모델 구성과 입출력을 의식하면서 설명하는 것은 두 번째 방법이다. 모델의 분기, 합류 등 복잡한 모델을 취급하는 경우는 후자의 방법이 적절하다.  keras에 익숙하지 않은 경우는 Sequential 모델과 Functional API의 설명을 참고하는 것이 좋다고 생각한다.

 

(2) loss 함수

GAN 논문에서 사용한 loss함수는 아래와 같다.

 Descriminator과 Generator의 학습에서 언급한 입력 데이터, 정답 라벨의 관계와 그것에 대한 loss함수에 대해 한 번 살펴보자. 위 식은 GAN논문에 기재된 loss함수이다. 

 먼저 Discriminator부터 살펴보자면 Generator를 고정한 후에 위 식을 최대화한다. 우변의 제 1항은 진짜 데이터를 이용한 케이스이다. 이 항목을 최대화하는 것은 log의 내부를 최대화, 즉 Discriminator의 식별 결과로써 1이 출력되도록 학습한다.

 우변의 제 2항은 Generator에 의해 생성된 데이터를 표시한 것이다. log 내부를 최대화하기 위해서는 Discriminator의 출력을 최소화, 즉 0이 출력하도록 하는 것이 좋다. 이것은 Discriminator의 학습에 대해 언급한 입력 데이터의 조합과 정답 라벨의 조합과 일치한다.

 다음은 Generator에 대한 것이다. Discriminator은 고정하여 Generator에 대해서만 의존하므로 제 2항목만을 고려한다. G에 대해 최소화하기 때문에 로그항의 중간이 1이 되도록 학습하고자한다는 것을 알 수 있다. 이것은 Generator의 학습 장소로 Generator로부터 생성된 가짜 이미지에 진짜 라벨 (1)을 붙여 학습하는 것을 의미한다.

 약간 관념적인 설명이 되어버렸지만, GAN의 로그함수의 정의와 실제 이용하는 각 네트워크의 loss함수에 대해 알 것이라고 생각한다.

 

(3) 학습

def train(self, epochs, batch_size=128, save_interval=50):

        # mnistデータの読み込み
        (X_train, _), (_, _) = mnist.load_data()

        # 値を-1 to 1に規格化
        X_train = (X_train.astype(np.float32) - 127.5) / 127.5
        X_train = np.expand_dims(X_train, axis=3)

        half_batch = int(batch_size / 2)

        for epoch in range(epochs):

            # ---------------------
            #  Discriminatorの学習
            # ---------------------

            # バッチサイズの半数をGeneratorから生成
            noise = np.random.normal(0, 1, (half_batch, self.z_dim))
            gen_imgs = self.generator.predict(noise)


            # バッチサイズの半数を教師データからピックアップ
            idx = np.random.randint(0, X_train.shape[0], half_batch)
            imgs = X_train[idx]

            # discriminatorを学習
            # 本物データと偽物データは別々に学習させる
            d_loss_real = self.discriminator.train_on_batch(imgs, np.ones((half_batch, 1)))
            d_loss_fake = self.discriminator.train_on_batch(gen_imgs, np.zeros((half_batch, 1)))
            # それぞれの損失関数を平均
            d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)


            # ---------------------
            #  Generatorの学習
            # ---------------------

            noise = np.random.normal(0, 1, (batch_size, self.z_dim))

            # 生成データの正解ラベルは本物(1) 
            valid_y = np.array([1] * batch_size)

            # Train the generator
            g_loss = self.combined.train_on_batch(noise, valid_y)

            # 進捗の表示
            print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss))

            # 指定した間隔で生成画像を保存
            if epoch % save_interval == 0:
                self.save_imgs(epoch)

 논문의 알고리즘대로 구현해보았다. 논문과 조금씩 다른 점은 discriminator의 학습할 때 교사 데이터(진짜 데이터)와 generator로부터 생성돤 가짜 데이터를 각각 학습하는 부분이다.

 이번의 구현에서는 실제 큰 영향은 없지만, GAN를 효율 좋게 학습시키기 위한 방법으로 Batch Normalization이라는 방법이 있다. 입력 데이터를 미니배치 단위로 정규화하는 방법으로 실제 데이터와 (특히 학습 초기의 대부분의 랜덤 수와 같은) 생성 데이터를 섞어서 정규화하는 것은 바람직하지 않기 때문에, 나누어 처리하고 있다고 생각해달라.

 

(4) 결과

생성한 이미지를 표시한다.

400 iteration 후

1000 iteration후

5000 iteration후

15000 iteration후

점점 문자 모양으로 생성되고 있다.


3. GANs를 이용한 DCGAN 구현 (Tensorflow 공식 예제)

 DCGAN(Deep Convolutional Generative Adversarial Networks)는 기존의 GANs를 아래의 방법으로 이미지의 화질을 향상시킨 방법이다.

1. Pooling층을 보폭이 있는 Convolution층으로 대체함
2. 언샘플링으로 Deconvolution(逆畳み込み層)을 사용함
3. CNN층을 삭제
4. 모든 층에 대해 배치 정규화 실시 (Generator의 입력층과 Discriminator의 입력층 이외)
5. Generator에 있어서, 출력층 이외에는 Relu를 사용함 (출력층에는 tanh을 이용)
6. Discriminator로는 LeakyReLU를 사용함

자세한 설명은 링크를 참고

 MNIST 데이터 세트와 GANs를 이용하여 위 그림을 Deep Convolutional Generative Adversarial Networks(DCGAN)을 구현하는 코드이다. 참고로 애니메이션은 50 에포크(epoch)동안 훈련한 생성자가 생성해낸 연속된 이미지들이다. 이미지들은 랜덤한 노이즈로부터 시작되었고, 점차 시간이 지나감에 따라 손으로 쓴 숫자와 비슷해져간다.

1) Importing libraries

from __future__ import absolute_import, division, print_function, unicode_literals
!pip install -q tensorflow-gpu==2.0.0-rc1
import tensorflow as tf
# GIF를 만들기위해 설치합니다.
!pip install -q imageio
import glob
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
from tensorflow.keras import layers
import time

from IPython import display

2) Preparing Dataset

생성자와 감별자를 훈련하기위해 MNIST 데이터셋을 사용할것입니다. 생성자는 손글씨 숫자 데이터를 닮은 숫자들을 생성할 것입니다.

(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # 이미지를 [-1, 1]로 정규화합니다.

BUFFER_SIZE = 60000
BATCH_SIZE = 256

# 데이터 배치를 만들고 섞습니다.
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)

3) Modeling

생성자와 감별자는 keras Sequential API를 이용해 정의된다.

(1) 생성자

생성자는 시드값 (seed; 랜덤한 잡음)으로부터 이미지를 생성하기 위해, tf.keras.layers.Conv2DTranspose (업샘플링) 층을 이용합니다. 처음 Dense층은 이 시드값을 인풋으로 받는다. 그 다음 원하는 사이즈 28x28x1의 이미지가 나오도록 업샘플링을 여러번 한다. tanh를 사용하는 마지막 층을 제외한 나머지 각 층마다 활성함수로 tf.keras.layers.LeakyReLU 사용하고 있음을 주목하자.

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

아직 훈련이 되지않은 생성자를 이용해 이미지를 생성해보자.

generator = make_generator_model()

noise = tf.random.normal([1, 100])
generated_image = generator(noise, training=False)

plt.imshow(generated_image[0, :, :, 0], cmap='gray')

 

(2) 감별자

감별자는 합성곱 신경망(Convolutional Neural Network, CNN) 기반의 이미지 분류기이다.

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

아직까지 훈련이 되지 않은 감별자를 사용하여, 생성된 이미지가 진짜인지 가짜인지 판별합니다. 모델은 진짜 이미지에는 양수의 값 (positive values)을, 가짜 이미지에는 음수의 값 (negative values)을 출력하도록 훈련되어집니다.

discriminator = make_discriminator_model()
decision = discriminator(generated_image)
print (decision)

tf.Tensor([[0.00077136]], shape=(1, 1), dtype=float32)


4) Loss function & Optimizer

두 모델의 손실함수와 옵티마이저를 정의한다.

# 이 메서드는 크로스 엔트로피 손실함수 (cross entropy loss)를 계산하기 위해 헬퍼 (helper) 함수를 반환합니다.
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

(1) 감별자 손실함수

이 메서드는 감별자가 가짜 이미지에서 얼마나 진짜 이미지를 잘 판별하는지 수치화한다. 진짜 이미지에 대한 감별자의 예측과 1로 이루어진 행렬을 비교하고, 가짜로 생성된 이미지에 대한 감별자의 예측과 0으로 이루어진 행렬을 비교합니다.

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

 

(2) 생성자 손실함수

생성자의 손실함수는 감별자를 얼마나 잘 속였는지에 대해 수치화를 한다. 직관적으로 생성자가 원활히 수행되고 있다면, 감별자는 가짜 이미지를 진짜 (또는 1)로 분류를 할 것이다. 여기서 우리는 생성된 이미지에 대한 감별자의 결정을 1로 이루어진 행렬과 비교를 할 것이다.

def generator_loss(fake_output):
    return cross_entropy(tf.ones_like(fake_output), fake_output)

 

(3) 옵티마이저

generator_optimizer = tf.keras.optimizers.Adam(1e-4)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)

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

6) Training loop

EPOCHS = 50
noise_dim = 100
num_examples_to_generate = 16

# 이 시드를 시간이 지나도 재활용하겠습니다. 
# (GIF 애니메이션에서 진전 내용을 시각화하는데 쉽기 때문입니다.) 
seed = tf.random.normal([num_examples_to_generate, noise_dim])

훈련 루프는 생성자가 입력으로 랜덤시드를 받는 것으로부터 시작된다. 그 시드값을 사용하여 이미지를 생성합니다. 감별자를 사용하여 (훈련 세트에서 갖고온) 진짜 이미지와 (생성자가 생성해낸) 가짜이미지를 분류한다. 각 모델의 손실을 계산하고, 그래디언트 (gradients)를 사용해 생성자와 감별자를 업데이트한다.

# `tf.function`이 어떻게 사용되는지 주목해 주세요.
# 이 데코레이터는 함수를 "컴파일"합니다.
@tf.function
def train_step(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 train(dataset, epochs):
  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 에포크가 지날 때마다 모델을 저장합니다.
    if (epoch + 1) % 15 == 0:
      checkpoint.save(file_prefix = checkpoint_prefix)
    
    # print (' 에포크 {} 에서 걸린 시간은 {} 초 입니다'.format(epoch +1, time.time()-start))
    print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))

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

7) Generating Images

def generate_and_save_images(model, epoch, test_input):
  # `training`이 False로 맞춰진 것을 주목하세요.
  # 이렇게 하면 (배치정규화를 포함하여) 모든 층들이 추론 모드로 실행됩니다. 
  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()

8) Training model

위에 정의된 train() 메서드를 생성자와 감별자를 동시에 훈련하기 위해 호출한다. 생성적 적대 신경망을 학습하는 것은 매우 까다로울 수 있다. 여기서 생성자와 감별자가 서로를 제압하지 않는 것이 중요하다. (예를 들어 학습률이 비슷하면 한쪽이 우세해진다.) 훈련 초반부에는 생성된 이미지는 랜덤한 노이즈처럼 보이지만 훈련이 진행될수록, 생성된 숫자는 점차 진짜처럼 보일 것이다. 약 50 에포크가 지난 후, MNIST 숫자와 닮은 이미지가 생성된다. 

%%time
train(train_dataset, EPOCHS)

마지막 체크포인트를 복구한다.

checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))

9) Generating GIF Image

# 에포크 숫자를 사용하여 하나의 이미지를 보여줍니다.
def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
display_image(EPOCHS)

imageio로 훈련 중에 저장된 이미지를 사용해 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)

(cf) DCGAN에 대한 보충설명; DCCGAN이 대단한 이유

 Minimax혹은 saddle problem을 풀어야하는 GAN의 특성상 불안정할 수 밖에 없는데, 이론적으로는 fixed solution으로 수렴하는 것으로 보장되어 있지만 실제 적용시에는 이론적 가정이 깨지면서 이러한 불안정한 구조를 보이고 있었다. DCCGAN은 이러한 문제를 해결하기 위해 Convolution구조를 GAN에 녹였다. 그 결과 아래와 같은 결과를 보여줬다.

- 대부분의 상황에서 언제나 안정적으로 학습이 가능하다.

- word2vec와 같이 DCGAN으로 학습된 Generator가 벡터 산술 연산이 가능한 성징을 갖고 그로인해 semantic 수준에서의 sample generation을 해 볼 수 있다.

- DCCGAN이 학습한 filter들은 시각화하여 보여주고 특정 filter들이 이이미지의 특정 물체를 학습했다는 것을 보여줬다.

- 이렇게 학습된 Discriminator가 다른 비지도 학습 알고리즘들과 비교하여 비슷한 이미지 분류 성능을 보여줬다.


또다른 예제 코드

https://github.com/carpedm20/DCGAN-tensorflow


참고자료

http://jaejunyoo.blogspot.com/2017/02/deep-convolutional-gan-dcgan-1.html

https://qiita.com/taku-buntu/items/0093a68bfae0b0ff879d

https://qiita.com/triwave33/items/1890ccc71fab6cbca87e

https://www.youtube.com/watch?v=2rC2_-HtpsQ

https://www.tensorflow.org/tutorials/generative/dcgan#%EC%83%9D%EC%84%B1%EC%A0%81_%EC%A0%81%EB%8C%80_%EC%8B%A0%EA%B2%BD%EB%A7%9Dgans%EC%9D%80_%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94

728x90