Simple GAN (하이브리드 실습)
DATA SET LOCATION
https://github.com/googlecreativelab/quickdraw-dataset/blob/master/README.md
NPY STORAGE
https://console.cloud.google.com/storage/browser/quickdraw_dataset/full/numpy_bitmap;tab=objects?pli=1&prefix=&forceOnObjectsSortingFiltering=false
자, Simple GAN을 한번 만들어 보려고 합니다. 이게 GAN을 한번 해보면, 남들이 만들어 놓은 되는 모델을 쓰면 잘 되는데, 직접 만들어 보면 잘 안됩니다. 여러가지 이유가 있는데 어쨌든 Simple GAN을 만들어 보려고 합니다.
일단, Discriminator는 CNN으로, Generator는 FC 신경망으로만 구성해서 만들어 보는 것이 목적입니다. 후후.
그리고, Discriminator는 진짜로 batch 만큼 한번, 가짜로 batch 만큼 한번 학습을 시킨 후에, Generator는 이 Discriminator에 출력물을 넣어보고 분류 결과에 대해서 학습을 하는 것으로 구성했습니다.
어쨌든 DCGAN처럼 Discriminator와 Generator를 모두 CNN으로 구성하지 않고 따로 따로 만드는 네트워크에 대한 도전이었습니다.
일단 코드를 대충~ 보고나서 시사점과 고생점을 마지막에 정리해 보려고 합니다. 이번 글은 아무래도 원리를 설명하기 보다는 한번 만들어 본다는 뭐 그런 의의와 사명을 가지는 글이라고 생각해 주시면 고맙겠습니다.
목표는 28by28의 천사 Quick Drawing을 GAN이 만들어 내는 것이 목표입니다. 하.
import os
import numpy as np
from tqdm import tqdm
import tensorflow as tf
import tensorflow.keras
import tensorflow.keras.backend as K
from tensorflow.keras.layers import Conv2D, Activation, Dropout, Flatten, Dense, BatchNormalization, Reshape, UpSampling2D, Input
from tensorflow.keras.optimizers import RMSprop
from tensorflow.keras.preprocessing.image import array_to_img
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.layers import LeakyReLU, ReLU
from tensorflow.keras.datasets import mnist
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import initializers
import warnings ; warnings.filterwarnings('ignore')
# 훈련데이터입니다.
x_train = np.load('full_numpy_bitmap_angel.npy')
print(len(x_train))
print(x_train[0].shape)
149736 (784,)
# 데이터를 읽어들이고, reshape하고, 255로 normalization하고! 이것은 discriminator의 입력입니다.
file_data = 'full_numpy_bitmap_angel.npy'
x_train = np.load(file_data)
x_train = np.reshape(x_train, (len(x_train), 28 * 28))#, 1))
x_train = x_train / 255
# train 데이터를 batch_size 로 나눕니다.
batch_size = 64 # 학습할 batch 크기
batch_count = x_train.shape[0] // batch_size # 전체 batch 개수
random_dim = int(28*28/2) # Generator에 넣어줄 noise vector 크기
# Optimizer를 설정합니다.
def get_optimizer():
return RMSprop(lr=0.00005) # Adam(lr=0.0001)도 괜찮지 않을까 합니다.
# Discriminator는 아무래도 이미지 판별에 능한 CNN이 좋겠죠?
def get_discriminator(optimizer):
disc_input = Input(shape=(28, 28, 1)) # 28, 28, 1 차원으로 데이터가 있음
x = Conv2D(filters=64, kernel_size=5, strides=2, padding='same')(disc_input)
x = Activation('relu')(x)
x = Dropout(rate=0.1)(x)
x = Conv2D(filters=256, kernel_size=5, strides=1, padding='same')(x)
x = Activation('relu')(x)
x = Dropout(rate=0.1)(x)
x = Flatten()(x)
disc_output = Dense(units=1, activation='sigmoid', kernel_initializer='he_normal')(x)
discriminator = Model(disc_input, disc_output) # compile은 하지 않음
return discriminator
# Generator 만들기 DCGAN이 아닌 새로운 시도로 그냥 Fully Connectecd로 만들어 보았습니다.
def get_generator(optimizer):
generator = Sequential()
generator.add(Dense(28*28, input_dim=random_dim, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
generator.add(BatchNormalization(momentum=0.99))
generator.add(LeakyReLU(0.3))
# 0.2 → 0.3
generator.add(Dense(28*28*2))
generator.add(BatchNormalization(momentum=0.99))
generator.add(LeakyReLU(0.3))
generator.add(Dense(28*28*5))
generator.add(BatchNormalization(momentum=0.99))
generator.add(LeakyReLU(0.3))
generator.add(Dense(28*28*5))
generator.add(BatchNormalization(momentum=0.99))
generator.add(LeakyReLU(0.3))
generator.add(Dense(28*28*2))
generator.add(BatchNormalization(momentum=0.99))
generator.add(LeakyReLU(0.3))
generator.add(Dense(784, activation='sigmoid'))
gen_dense_size = (28, 28, 1)
generator.add(Reshape(gen_dense_size))
return generator
# Discriminator와 Generator를 엮어서 GAN network를 만들어 봅시다.
def get_gan_network(discriminator, random_dim, generator, optimizer):
# Generator와 Discriminator를 동시에 물려서 학습할 때는 Discriminator를 trainable을 False로 설정합니다.
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer)
discriminator.trainable = False
# GAN 입력 (노이즈)설정했구요,
gan_input = Input(shape=(random_dim,))
# Generator의 결과는 이미지 입니다.
x = generator(gan_input)
# Discriminator의 결과는 이미지가 진짜인지 가짜인지에 대한 확률입니다.
# Discriminator의 입력으로는 Generator의 출력입니다.
gan_output = discriminator(x)
gan = Model(inputs=gan_input, outputs=gan_output)
gan.compile(loss='binary_crossentropy', optimizer=optimizer)
return gan
# GAN 네트워크를 만들어 봅시다.
optimizer = get_optimizer()
# Discriminator와 Generator를 만들고!
discriminator = get_discriminator(optimizer)
generator = get_generator(optimizer)
# 두개를 붙여서 만들어 봅시다.
gan_model = get_gan_network(discriminator, random_dim, generator, adam)
def train_discriminator(x_train, batch_size):
# 진짜와 가까를 0과 1로 labeling해 주고,
valid = np.full((batch_size, 1), 1)
fake = np.zeros((batch_size, 1))
# discriminator를 먼저 진짜로 학습
idx = np.random.randint(0, len(x_train), batch_size)
true_imgs = x_train[idx]
true_data = true_imgs.reshape(batch_size, 28, 28, 1)
discriminator.train_on_batch(true_data, valid)
noise = np.random.normal(0, 1, (batch_size, random_dim)) # 784 100 input
gen_imgs = generator.predict(noise)
# discriminator를 noise로 image를 만든 후에 가짜로 학습
discriminator.train_on_batch(gen_imgs, fake) #, verbose=1)
def train_generator(batch_size):
# generator는 GAN 전체 네트워크에 넣어서 학습
# Discriminator가 뭔가로 판단한 것과 Label 1의 차이 loss를 genenerator에 back propagation
# 점점 generator의 결과물의 discriminator의 출력이 1에 가까워 지도록 학습한다는 의미임.
valid = np.ones((batch_size, 1))
noise = np.random.normal(0, 1, (batch_size, random_dim))#28*28)) # 100?
gan_model.train_on_batch(noise, valid)
# 얏호! 3000 epoch만큼 학습 시작! 두근두근
for epoch in tqdm(range(3000), position=0, leave=True):
train_discriminator(x_train, batch_count)
train_generator(batch_count)
100%|██████████| 6000/6000 [58:14<00:00, 1.72it/s]
# 원래 훈련 데이터가 어땠는지 랜덤으로 한번 보고요,
import matplotlib.pyplot as plt
print(x_train.shape)
idx = np.random.randint(0, len(x_train), 1)
print(idx)
img = x_train[idx].reshape(28,28,1)
original=array_to_img(img)
plt.imshow(original, cmap='gray')
(149736, 784) [134579]
# Generator가 어떻게 만드는지 한번 보고요.
random_noise=np.random.normal(0, 1, (1, random_dim))
gen_result=generator.predict(random_noise)
gen_result[29].shape
gen_img=array_to_img(gen_result[29].reshape(28,28,1))
plt.imshow(gen_img, cmap='gray', interpolation='nearest')
아.. 뭐 대충 이상하지만, 천사 모양이 나오긴 나왔습니다. 우아!!! 하는 감흥이 좀 없긴 한데 놀라운 결과가 안나와서 그런 것 같습니다.
사실 전체 데이터를 살펴보면 mode collapse도 좀 있긴 한데, 어쨌든 학습을 시키긴 했습니다. 헤헤.
이 GAN 모형을 학습시킬 때 몇가지 고려해야 하는 시사점과 고생점을 정리해 보면 다음과 같습니다.
① Generator의 마지막 activation은 흑백 이미지이고, 마이너스값을 다루지 않을 거라서 tanh보다는 sigmoid가 더 낫습니다.
② 굉장히 중요한 사실이 있습니다. ★★★★★ Discriminator가 너무 똑똑하면 안됩니다. Discriminator가 너무 똑똑하면 Generator가 학습을 잘 못합니다. 둘이 비슷하게 멍청해야 합니다. 대체로 Discriminator가 조금 덜 똑똑한 편이 학습이 잘 됩니다. 너무 멍청하면 또 안되고요. 어쩄든 Binary Decision GAN을 만들 때는 이 부분을 꼭 기억하면 좋겠습니다. - 그래서 CNN Layer도 조금 더 단순하게 만들었습니다. -
③ Generator의 표현력을 높이기 위해 layer가 Deep하고 Wide해서 parameter가 많은 편이 낫습니다.만 조금 나아 보입니다.
④ 학습을 제대로 시키기 위해서는 batchnormalization layer를 사용하면 좀 더 안정적으로 학습합니다.
④ 학습을 할 때는 fit도 좋지만, train_on_batch를 사용하는 편도 괜찮습니다.
⑥ Discriminator가 조금은 과적합되는 정도가 되면 약간 멍청해 지니까 Dropout을 너무 쎄게 주지 않는 것도 성능을 개선합니다.
⑦ batch_size는 너무 큰 것 보다는 작은 편이 낫습니다.
⑧ 비슷한 이야기 일 수도 있는데 Learning Rate가 너무 크면 학습이 잘 안됩니다. 꽤 작은 값으로 해서 Epoch를 늘리는 편이 낫습니다.
⑨ Mode Collapse는 이 구조에서는 피할 수 없는 것 같아서, cGAN등을 고려해 봐야 할 것 같습니다.
대체로 GAN이라는 아아디어는 엄청난 것이기도 하지만, 구현하고 학습시킬 때에 가로막히는 문제가 꽤나 어쩌면 좋지? 하게 됩니다. 그래도 한번 쯤 해 보면 이거 별거 아니잖아? 생각이 들 수도 있으니까 한번쯤 테스트해 보세요.
GAN을 이해할 떄 Disriminator를 Generator가 속인다는 건 결과론적인 이야기입니다. 이렇게만 이해하고 있으면 왜 학습을 이런 식으로 시키는지 이해하기 어렵습니다. 학습은 Discriminator가 뭔가로 판단한 것과 Label 1의 차이인 loss를 genenerator에 back propagation시켜서 학습을 시키고, 점점 generator의 결과물의 discriminator의 출력의 1에 가까워 지도록 학습한다는 의미라고 이해해야 조금 더 쉽게 이해할 수 있습니다. 요컨대 Generator가 어떤 입력을 했을 때 discriminator가 왜 1로 판단하지 않는거지?의 기준으로 학습한다고 생각해야 합니다.
댓글