본문 바로가기
Deep Learning/Hands On Machine Learning

GAN(Generative Adversarial Network)

by 대소기 2022. 1. 1.

GAN

* GAN은 생성적 적대 신경망이라는 이름에서 알 수 있듯이 신경망의 경쟁을 통해 더 나은 네트워크를 도출해내는 기법을 뜻한다.

Generator(생성자)

* generator random한 분포, 이를테면 Gaussian distribution과 같은 분포를 입력으로 받고, 이를 coding과 같이 취급해 이미지를 생성해내는 역할을 한다. 이는 variational autoencoder의 decoder부분과 비슷한 역할이다.

Discriminator(판별자)

* discriminator는 입력된 이미지가 training set에서 나온 real image인지, Gaussian distribution에서 추출되어 generator가 생성한 fake image인지를 판별하는 판별자이다.

훈련 과정

* 훈련 동안 discriminator는 fake, real image를 구분하는 것을 목표로, generator는 dicriminator를 속여 real image라고 믿게 만드는 것을 목표로 한다. 하지만, 이렇게 정 반대의 목표를 가지고 훈련을 진행하게 되면 dicriminator, generator 모두 제대로 된 학습을 하기 어려울 것이다. 때문에 훈련은 두 가지 단계로 구분되어 진행되고 각 단계마다 dicriminator의 가중치, generator의 가중치 중 하나만 update하게 된다.

1) 첫 번째 단계 - dicriminator 훈련

* generator를 통해 fake image의 batch를 생성하고, training set에서 동일한 size의 batch를 sampling해 두 batch를 합친다. fake image에는 0 , real image에는 1의 label을 부여하고 한 step동안 binary cross entropy 를 사용하여 discriminator의 가중치를 update한다. 이 때 generator의 가중치까지는 역전파를 시행하지 않기 때문에 generator의 가중치는 udpate되지 않는다.

2) 두 번째 단계 - generator 훈련

* generator가 fake image의 batch를 생성한다. 이 번에는 real image를 추가하지 않고 fake image batch의 label을 모두 1로 설정한 뒤 discriminator를 통해 판별을 진행한다. 역전파는 generator까지 진행되고 discriminator의 가중치를 동결시켜서 generator의 가중치만 update한다.

모델 생성

np.random.seed(42)
tf.random.set_seed(42)

codings_size = 30

generator = keras.models.Sequential([
    keras.layers.Dense(100, activation="selu", input_shape=[codings_size]),
    keras.layers.Dense(150, activation="selu"),
    keras.layers.Dense(28 * 28, activation="sigmoid"),
    keras.layers.Reshape([28, 28])
])
discriminator = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dense(150, activation="selu"),
    keras.layers.Dense(100, activation="selu"),
    keras.layers.Dense(1, activation="sigmoid")
])
gan = keras.models.Sequential([generator, discriminator])

discriminator.compile(loss="binary_crossentropy", optimizer="rmsprop")
discriminator.trainable = False
gan.compile(loss="binary_crossentropy", optimizer="rmsprop")

* generator는 autoencoder의 decoder와 비슷한 역할을 한다. dicriminator는 이진분류기이므로, 마지막 layer를 unit이 1개인 dense layer를 사용하고, loss로 binary_crossenropy를 사용한다.

batch_size = 32
dataset = tf.data.Dataset.from_tensor_slices(X_train).shuffle(1000)
dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(1)

def train_gan(gan, dataset, batch_size, codings_size, n_epochs=50):
    generator, discriminator = gan.layers
    for epoch in range(n_epochs):
        print("Epoch {}/{}".format(epoch + 1, n_epochs))             
        for X_batch in dataset:
            # phase 1 - training the discriminator
            noise = tf.random.normal(shape=[batch_size, codings_size]) #noise생성
            generated_images = generator(noise) #fake images
            X_fake_and_real = tf.concat([generated_images, X_batch], axis=0) #X data
            y1 = tf.constant([[0.]] * batch_size + [[1.]] * batch_size) #y data(label)
            discriminator.trainable = True
            discriminator.train_on_batch(X_fake_and_real, y1)
            # phase 2 - training the generator
            noise = tf.random.normal(shape=[batch_size, codings_size])
            y2 = tf.constant([[1.]] * batch_size)
            discriminator.trainable = False # discriminator 가중치 동결
            gan.train_on_batch(noise, y2)
        plot_multiple_images(generated_images, 8)                     
        plt.show()

* 일반적인 모델 반복훈련과 달리, 2가지 단계로 나눠서 각 단계별 방법론대로 다르게 훈련시켜야 하기 때문에 사용자 정의 훈련 반복 함수를 생성하였다. 훈련 과정은 앞서 설명한 내용을 그대로 구현한 것이기 때문에 이해하기 크게 어렵지 않을 것이다.

train_gan(gan, dataset, batch_size, codings_size, n_epochs=1)

* 훈련을 1 epoch만 진행했음에도 generator를 통해 생성된 image가 꽤 그럴싸하다. 그런데 이 이상의 퀄리티의 image는 더 이상의 훈련 반복으로 구현할 수 없다. 그 이유에 대해서 알아보자.

GAN 훈련의 어려움

* GAN 논문에 따르면 GAN은 generator와 discriminator의 경쟁을 거듭하다 단 하나의 nash equilibrium에 도달하게 된다. generator가 real image와 구분되지 않는 완벽한 fake image를 생성하고, discriminator는 판별이 불가하기 때문에 1/2확률로 추측하게 되는 상태에 다다른다고 한다. 하지만, 실제로 이렇게 균형 상태로 수렴하기엔 쉽지가 않다. 그 이유는 다음과 같다.

1) mode collapse(모드 붕괴)

* 예를 들어 generator가 fashion mnist 데이터 중에 신발에 해당하는 image를 잘 만든다고 가정해보자. generator는 신발이 더 discriminator를 속이기 쉽기 때문에 신발에 해당하는 image를 점차 더 많이 생성하게 되고 결국 신발에 해당하는 image만 생성하게 된다. discriminator는 신발에 해당하는 image가 계속 입력되다 보니 신발에 해당하는 image 판별만 훈련하게 되고 다른 class의 image를 판별하는 법은 잊어버린다. discriminator가 만약 신발에 해당하는 image를 잘 구분하게 되었다고 해보자. generator는 다른 image 가령, pants image를 생성하기 시작한다. discriminator는 신발에 해당하는 image만 계속 판별해왔기 때문에 다른 class의 image 판별법을 잊어버린 상태이다. pants image가 입력되니까 discriminator는 정신을 못 차린다. 이에 따라 generator는 pants image를 점차 늘려간다. 이렇게 계속해서 한 image만 생성, 판별하다가 다른 class로 옮겨가면서 이전의 학습 내용을 망각하게 되는 현상을 mode collapse라고 한다.

2) 파라미터 변동성

* GAN은 generator와 discriminator가 복잡한 역학관계 하에 서로 영향을 주기 때문에 파라미터의 변동성이 크고 불안정해질 수 있다. 때문에 하이퍼 파라미터 튜닝이 쉽지 않다.

훈련의 어려움 극복 노력

1) Experience Replay

* generator가 생성하는 image를 특정 size의 버퍼에 저장해 놓고 size가 넘어가면 오래된 image 부터 지운다. discriminator는 generator가 이번 epoch에서 생성한 image가 아닌 버퍼에서 sampling된 image와 real image를 더한 batch를 훈련하게 된다. 즉, 최근 generator의 생성 image에 discriminator가 overfitting 될 가능성을 줄이는 것이다.

2) Mini-batch Discrimination

* batch 안에 유사한 이미지들이 얼마나 있는지에 대한 통계를 discriminator에게 전달해 다양성이 부족한 fake image batch를 거부할 수 있다. 이를 통해 generator가 더 다양한 class의 fake image를 생성할 수 있도록 유도한다.

Deep Convolutional GAN(DCGAN)

* 2014년 최초 GAN 논문에서 convolution을 사용한 GAN을 통해 이미지를 생성하였지만, 이는 작은 이미지에 불과했다. 2015년 Alec Radford 등이 더 큰 이미지를 위한 convolutional GAN을 개발하는데 성공했다.

DCGAN의 가이드라인

1) discriminator에 있는 pooling layer를 stride convolution으로 바꾸고, generator에 있는 pooling layer는 transpose convolution으로 바꾼다.

2) generator와 discriminator에 batch normalize를 시행한다. generator의 output layer와 discriminator의 input layer는 제외한다.

3) layer를 깊게 쌓기 위해 Dense layer는 제거한다.

4) tanh 함수를 사용해야 하는 output layer를 제외하고 generator의 모든 layer는 ReLU activation function을 사용한다.

5) Discriminator의 모든 layer는 LeakyReLU를 사용한다.

DCGAN 구현

tf.random.set_seed(42)
np.random.seed(42)

codings_size = 100

generator = keras.models.Sequential([
    keras.layers.Dense(7 * 7 * 128, input_shape=[codings_size]),
    keras.layers.Reshape([7, 7, 128]), 
    keras.layers.BatchNormalization(), 
    keras.layers.Conv2DTranspose(64, kernel_size=5, strides=2, padding="SAME",
                                 activation="selu"), # output shape = (14, 14, 64)
    keras.layers.BatchNormalization(),
    # 마지막 layer의 output range -1 to 1(tanh 사용)
    keras.layers.Conv2DTranspose(1, kernel_size=5, strides=2, padding="SAME",
                                 activation="tanh"), # output shape = (28, 28, 1)

])
discriminator = keras.models.Sequential([
    # maxpooling layer를 사용하지 않고 대신 stride를 2로 설정
    keras.layers.Conv2D(64, kernel_size=5, strides=2, padding="SAME",
                        activation=keras.layers.LeakyReLU(0.2),
                        input_shape=[28, 28, 1]),
    # batch normalize 대신 dropout 설정
    keras.layers.Dropout(0.4),
    keras.layers.Conv2D(128, kernel_size=5, strides=2, padding="SAME",
                        activation=keras.layers.LeakyReLU(0.2)),
    keras.layers.Dropout(0.4),
    keras.layers.Flatten(),
    keras.layers.Dense(1, activation="sigmoid")
])
gan = keras.models.Sequential([generator, discriminator])

discriminator.compile(loss="binary_crossentropy", optimizer="rmsprop")
discriminator.trainable = False
gan.compile(loss="binary_crossentropy", optimizer="rmsprop")

* DCGAN의 가이드라인에 따라 모델을 생성하였다.

X_train_dcgan = X_train.reshape(-1, 28, 28, 1) * 2. - 1. # reshape and rescale

batch_size = 32
dataset = tf.data.Dataset.from_tensor_slices(X_train_dcgan)
dataset = dataset.shuffle(1000)
dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(1)

train_gan(gan, dataset, batch_size, codings_size)

* X train을 reshape해준 후에 사용자 정의 반복 훈련 함수인 train_gan()을 사용하여 훈련을 진행하였다.

* 50번 훈련한 후 generator는 fashion mnist image를 거의 완벽하게 생성하였다.

대규모 얼굴 데이터셋 활용

* DCGAN을 이용하면 위와 같이 이미지의 평균 벡터를 통해 마치 이미지 자체를 연산하는 것과 같은 효과를 낼 수 있다.

* 좌항의 아래에 있는 이미지들은 각각 위 3개 이미지 vector들의 평균을 한 이미지이다. 물론 이 평균이 단순 평균을 뜻하지는 않는다. 평균 이미지 vector들을 사용해 연산을 시행하면 우항에 있는 여성이 안경을 쓴 사진을 추출할 수 있다. 정가운데 있는 이미지가 DCGAN을 통해 생성한 이미지이고 해당 이미지를 둘러싸고 있는 나머지 이미지들은 약간의 Gaussian noise를 추가한 이미지이다.

* 만약 discriminator에 이미지 class에 대한 정보를 추가적인 입력으로 제공한다면 discriminator는 어떤 의미를 띠는 이미지인지까지 학습할 수 있을 것이다.

DCGAN의 한계

* 이렇게 완벽히 이미지를 생성한 것 같은 DCGAN도 한계는 존재한다. 매우 큰 이미지를 생성할 때 국부적으로는 구분되지만 전체적인 측면에서 봤을 때 일관성 없는 이미지를 생성할 수도 있다(예를 들어 한 쪽 소매가 더 긴 셔츠). 이를 해결하기 위해 다음 내용을 더 살펴보자.

ProGAN

* ProGAN은 noise로부터 작은 size의 이미지를 생성하고 점차 큰 size의 이미지를 생성하는 방법이다.

* 왼쪽이 4 x 4 컬러 이미지(color dimension : 3) 를 생성하는 generator라면, 오른쪽은 이를 확장시켜 8 x 8 컬러 이미지를 생성하는 generator이다. 기존 generator의 convolution layer 1 다음으로 upsampling layer를 추가해 8 x 8 feature map을 출력한다.(keras 구현시 UpSampling2D layer의 interpolation 파라미터 'nearest'설정)

* 8 x 8 feature map은 곧바로 output layer로 입력되는 동시에 또다른 convolution layer2에 입력된다. 이후 Conv2를 거친 feature map은 output layer로 들어가고 두 출력 값들은 가중치가 곱해져 가중합으로서 최종적으로 출력된다.

ProGAN 논문에서 소개된 mode collapse prevention 기법들

1) 미니배치 표준편차 층

* discriminator의 마지막 layer 근처에 추가한다. 입력에 있는 모든 위치에 대해 모든 채널(axis=0)과 배치의 모든 sample(axis=-1)에 걸쳐 표준편차를 계산한다(S=tf.math.reduce_std(inputs, axis=[0, -1]). 마지막으로 추가적인 특성 맵이 배치의 모든 sample에 추가되고 계산된 이 값으로 채워진다(tf.concat([inputs, tf.fill([batch_size, height, width, 1], v)], axis=-1)). 생성자가 만든 이미지에 다양성이 부족하면 판별자의 특성 맵 간의 표준편차가 작을 것이다. discriminator는 이 지표를 통해 generator에게 속을 가능성이 줄어든다. 이는 generator에게 더 다양한 class의 이미지를 생성하도록 유도할 것이고, mode collapse의 가능성이 줄어들게 될 것이다.

2) 동일한 학습 속도

* He 초기화 대신 평균이 0이고 표준편차가 1인 Gaussian Distribution을 사용해 모든 가중치를 초기화 한다. 하지만 runtime, 즉 layer가 실행될 때 마다 He 초기화에 있는 동일한 인자로 가중치의 scale을 낮춘다. 가중치를 $ \sqrt{2/n_{inputs}} $ 로 (여기서 $n_{inputs}$는 층의 입력 개수이다) 나눈다. 

 

3) 픽셀별 정규화 층

* generator의 convolution layer 뒤에 추가한다. 코드로 쓰자면 (inputs / tf.sqrt(tf.reduce_mean(tf.square(X), axis=-1, keepdims=True) + 1e-8) 이다.

 

StyleGAN

*StyleGAN은 Style Transfer 기법을 통해 생성된 이미지가 훈련된 이미지와 같은 다양한 크기의 local structure를 갖도록 만들었다.

Mapping Network

8개의 MLP가 latent representation을 vector w로 매핑한다. 이 벡터는 여러 affine transformation으로 전달되어(A로 표시된 activation function이 없는 Dense layer) vector 여러개를 생성하게 된다. 이 과정은 mapping network가 coding을 여러 style vector로 매핑하는 것을 뜻한다.

Synthesis Network

이미지 생성을 책임지는 부분이다.  훈련 시에는 역전파에 의해 입력이 일정하지 않지만, 훈련이 끝난 후에는 일정한 입력을 받을 수 있다. 입력을 convolution layer 여러 개와 upsampling층을 통과시킨다. 이 때 모든 convolution layer의 출력에는 noise가 조금씩 섞인다. noise가 섞인 다음에는 AdaIN(Adaptive INstance normalization) layer를 거치게 된다. AdaIN layer에서는 각 feature map을 독립적으로 표준화한 다음

'Deep Learning > Hands On Machine Learning' 카테고리의 다른 글

OpenAI gym  (0) 2022.01.08
강화학습 기본  (0) 2022.01.08
17.8 Variational AutoEncoder  (0) 2021.12.28
17.7 Sparse AutoEncoder  (0) 2021.12.28
17.6 Denoising AutoEncoder  (0) 2021.12.26