GAN을 사용해 봅시다.

12 분 소요

GAN이 뭡니까.

  • 이 포스트는 이 포스트를 참고하여 작성되었음을 명확하게 밝힙니다.

  • GAN은 Generative Adversarial Network의 약자인데, 이를 한국말로 말하면, “생성적 적대적 네트워크”라고 할 수 있는데, 그냥 영어로 GAN 갠 혹은 간 이라고 읽읍시다.
  • 우리는 데이터를 가지고 있습니다. 어떤 크기인지, 뭐 그런건 중요하지 않고요, 아무튼 가지고 있습니다. 이 데이터를 이용해서 비슷한 데이터들을 만들고 싶어요. 정확히는, 비슷한 데이터를 만들어내는 놈을 만들고 싶습니다.
    • 예를 들어서, 피카소의 그림을 마구마구 넣어서 학습을 시키면, 피카소 짝퉁 그림을 그려주는 놈, 그런 놈을 만들고 싶다는 이야기죠.
  • 이걸 위해서는 두 가지 뉴럴넷을 설계하는 것이 필요합니다.
    • 뉴럴넷 G는 생성하는 놈입니다.
    • 뉴럴넷 A는 분류하는 놈입니다.
  • 이 둘이 막 싸웁니다.
    • G는 noise data(random gen)로부터 fake data를 생성해냅니다.
      • G의 경우는 A가 고정된 상황에서, A를 잘 속이지 못할수록 feedback을 받습니다.
    • A는 G가 생성해낸 fake data와 original data를 받아서, 이 두가지를 구분합니다.
      • A의 경우는 이미 y_data(fake or not)가 있기 때문에, 잘 맞추는지 못 맞추는지를 피드백받아서 계속 학습해나갑니다.
  • 이를 반복하면, G도 똑똑해지고, A도 똑똑해집니다. 생각해보면, 아주 단순한데, 정말 창의적인 생각인 것 같아요. 물론 목적은, 좋은 A를 찾는 것이라기 보다, 좋은 G를 찾는 것이 중요한 목적이죠.
  • 또한 이는 unsupervised learning입니다. 쓸모가 많다는 이야기겠죠. 일종의 simulation을 하기 위해서도 유용하게 쓰일 수 있을 것 같아요.

Generator nn

  • 우리는 랜덤으로 생성한 데이터로부터, 그 데이터와 그나마 비슷한, mnist image를 만들어주는 generator를 만들려고 합니다.
    • 100 차원의 랜덤한 np.array를 만들고, 그 데이터를
    • Dense 로 차원을 (7/7/128)로 확장 그리고 reshape로 (7, 7, 128)로 만듬
    • Batch normalization
    • Upsampling2d 로 (7, 7, 128)을 (14, 14), 128로 확장
    • Convolution2D 로 (14, 14, 128) 을 (14, 14, 64)로 변환.
    • Activation
  • 전체 과정은 마치, 약간 CNN을 역으로 수행하는 과정이라고 생각해도 괜찮습니다.

  • 최적화 해야 하는 parameter가 진짜 많아서 압박이 심하네요 허허허헛.
import numpy as np 
import matplotlib.pyplot as plt 

from keras.datasets import mnist

from keras.models import Sequential, Model

from keras.layers import Input, Dense, Reshape, Flatten, Dropout
from keras.layers import BatchNormalization, Activation, ZeroPadding2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import UpSampling2D, Conv2D

from keras.optimizers import Adam

## create generator 
generator_ = Sequential([
    Dense(128 * 7 * 7, activation="relu", input_shape=(100,)), 
    Reshape((7, 7, 128)), 
    
    BatchNormalization(momentum=0.8), # what is batch normalization?? 
    UpSampling2D(), # what is upsampling?? 
    Conv2D(128, kernel_size=3, padding="same"),
    Activation("relu"), 
    
    BatchNormalization(momentum=0.8), 
    UpSampling2D(), 
    Conv2D(64, kernel_size=3, padding="same"), 
    Activation("relu"), 
    
    BatchNormalization(momentum=0.8), 
    Conv2D(1, kernel_size=3, padding="same"), 
    Activation("tanh"), 
])

noise_input = Input(shape=(100,), name="noise_input")
generator = Model(noise_input, generator_(noise_input), name="generator")

generator_.summary()# summary가 매우 유용하군요. 

optimizer = Adam(0.0002, 0.5)
generator.compile(loss='binary_crossentropy', optimizer=optimizer)
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_17 (Dense)             (None, 6272)              633472    
_________________________________________________________________
reshape_9 (Reshape)          (None, 7, 7, 128)         0         
_________________________________________________________________
batch_normalization_35 (Batc (None, 7, 7, 128)         512       
_________________________________________________________________
up_sampling2d_17 (UpSampling (None, 14, 14, 128)       0         
_________________________________________________________________
conv2d_48 (Conv2D)           (None, 14, 14, 128)       147584    
_________________________________________________________________
activation_24 (Activation)   (None, 14, 14, 128)       0         
_________________________________________________________________
batch_normalization_36 (Batc (None, 14, 14, 128)       512       
_________________________________________________________________
up_sampling2d_18 (UpSampling (None, 28, 28, 128)       0         
_________________________________________________________________
conv2d_49 (Conv2D)           (None, 28, 28, 64)        73792     
_________________________________________________________________
activation_25 (Activation)   (None, 28, 28, 64)        0         
_________________________________________________________________
batch_normalization_37 (Batc (None, 28, 28, 64)        256       
_________________________________________________________________
conv2d_50 (Conv2D)           (None, 28, 28, 1)         577       
_________________________________________________________________
activation_26 (Activation)   (None, 28, 28, 1)         0         
=================================================================
Total params: 856,705
Trainable params: 856,065
Non-trainable params: 640
_________________________________________________________________

discriminator nn

  • discriminator는 우리가 이미 알고 있던 image classifier와 같습니다.
  • 단, 우리가 원래 mnist를 사용할 때는 0…9 까지를 구분하는 형태로 학습했고 따라서, cross_entropy를 사용했던 것을 알 수 있습니다. 마지막에도 softmax를 사용했구요.
  • 그러나 여기서는 binary_crossentropy를 사용하는 것을 알 수 있습니다.
  • 앞서 말씀드린 바와 같이, 이 discriminator의 목적은, fake와 real을 구분하는 데 있습니다. 그래서, 이렇게 세팅이 되죠.
### create discriminator
discriminator_ = Sequential([
    Conv2D(32, kernel_size=3, strides=2, input_shape=(28, 28, 1), padding="same"), 
    LeakyReLU(alpha=0.2), 
    Dropout(0.25), 
    
    Conv2D(64, kernel_size=3, strides=2, padding="same"), 
    ZeroPadding2D(padding=((0,1),(0,1))), 
    LeakyReLU(alpha=0.2), 
    Dropout(0.25), 
    BatchNormalization(momentum=0.8), 
    
    Conv2D(128, kernel_size=3, strides=2, padding="same"), 
    LeakyReLU(alpha=0.2), 
    Dropout(0.25), 
    BatchNormalization(momentum=0.8), 
    
    Conv2D(256, kernel_size=3, strides=1, padding="same"), 
    LeakyReLU(alpha=0.2), 
    Dropout(0.25), 
    Flatten(), 
    Dense(1, activation='sigmoid'), 
])
image_input = Input(shape=(28, 28, 1), name="image_input")

discriminator = Model(image_input, discriminator_(image_input), name="discriminator")
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])
discriminator_.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_51 (Conv2D)           (None, 14, 14, 32)        320       
_________________________________________________________________
leaky_re_lu_25 (LeakyReLU)   (None, 14, 14, 32)        0         
_________________________________________________________________
dropout_25 (Dropout)         (None, 14, 14, 32)        0         
_________________________________________________________________
conv2d_52 (Conv2D)           (None, 7, 7, 64)          18496     
_________________________________________________________________
zero_padding2d_4 (ZeroPaddin (None, 8, 8, 64)          0         
_________________________________________________________________
leaky_re_lu_26 (LeakyReLU)   (None, 8, 8, 64)          0         
_________________________________________________________________
dropout_26 (Dropout)         (None, 8, 8, 64)          0         
_________________________________________________________________
batch_normalization_38 (Batc (None, 8, 8, 64)          256       
_________________________________________________________________
conv2d_53 (Conv2D)           (None, 4, 4, 128)         73856     
_________________________________________________________________
leaky_re_lu_27 (LeakyReLU)   (None, 4, 4, 128)         0         
_________________________________________________________________
dropout_27 (Dropout)         (None, 4, 4, 128)         0         
_________________________________________________________________
batch_normalization_39 (Batc (None, 4, 4, 128)         512       
_________________________________________________________________
conv2d_54 (Conv2D)           (None, 4, 4, 256)         295168    
_________________________________________________________________
leaky_re_lu_28 (LeakyReLU)   (None, 4, 4, 256)         0         
_________________________________________________________________
dropout_28 (Dropout)         (None, 4, 4, 256)         0         
_________________________________________________________________
flatten_8 (Flatten)          (None, 4096)              0         
_________________________________________________________________
dense_18 (Dense)             (None, 1)                 4097      
=================================================================
Total params: 392,705
Trainable params: 392,321
Non-trainable params: 384
_________________________________________________________________

combined model

  • 이제 앞서 만든 generator와 discriminator를 결합해야 합니다.
  • 정확히는, 이 결합모델은 generator를 학습시키기 위해서 사용된다라고 보는 것이 맞습니다.
    • generator가 랜덤하게 받은 noise input으로부터 fake image를 만들어내고
    • 이 fake image는 (고정된, parameter가 변하지 않는) discriminator로부터 판별되고,
    • binary_crossentropy를 계산해서, generator를 학습합니다.
  • 즉, 이 과정을 반복하면, generator는 discriminator를 점점 더 잘 속일수 있게 변화합니다.
### Combined Model
noise_input2 = Input(shape=(100,), name="noise_input2")
"""
model과 sequential의 차이는?? 
가설1: 레이어를 쌓는 것이 sequential 이라면, sequential을 쌓는 것이 model인가???

1) 다음 모델의 경우는 랜덤으로 만든 이미지로부터 학습해서 새로운 이미지를 만들어내는 generator의 데이터를 
2) discriminator가 분류하는 형식으로 진행된다. 
"""
combined = Model(noise_input2, discriminator(generator(noise_input2)))
combined.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])
combined.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
noise_input2 (InputLayer)    (None, 100)               0         
_________________________________________________________________
generator (Model)            (None, 28, 28, 1)         856705    
_________________________________________________________________
discriminator (Model)        (None, 1)                 392705    
=================================================================
Total params: 1,249,410
Trainable params: 856,065
Non-trainable params: 393,345
_________________________________________________________________

show image

  • generator가 얼마나 학습이 잘 되었는지는 단지 scoring만으로는 파악이 어렵습니다. 극단적으로는 둘다 멍청해도 score는 높게 나올 수 있으니까요.
  • 그래서, 직접 image를 확인해봐야 하는데, 이를 아래 코드를 통해 해결합니다.
#noise_data = np.random.normal(0, 1, (32, 100))
#generated_images = 0.5 * generator.predict(np.random.normal(0, 1, (32, 100))) + 0.5

def show_images(generated_images, n=4, m=8, figsize=(9, 5)):
    f, axes = plt.subplots(n, m, figsize=figsize)
    #plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05)
    for i in range(0, n):
        for j in range(0, m):
            ax = axes[i][j]
            ax.imshow(generated_images[i * m + j][:, :, 0], cmap=plt.cm.bone)
            ax.grid(False)
            ax.xaxis.set_ticks([])
            ax.yaxis.set_ticks([])
    plt.tight_layout()
    plt.savefig('../../assets/images/markdown_img/180621_1554_gan_digit_img.svg')
    plt.show()   
#show_images(0.5 * generator.predict(np.random.normal(0, 1, (32, 100))) + 0.5)

reading and traing data

  • 보통 학습할 때, .fit을 사용하는데, 여기서는 .train_on_batch를 사용햇씁니다.
    • .fit의 경우, epochs, batch_size를 한번에 넘겨주는데 반해
    • .train_on_batch의 경우는 현재 전달받은 데이터를 모두 활용해서 gradient vector를 계산해서 업데이트 합니다.
    • GAN에서는 매번 Generator가 새로운 fake image를 만들기 때문에, epoch마다 새로운 데이터를 넘겨주어야 합니다. .train_on_batch를 사용하는 것이 더 좋습니다. 또한 train_on_batch는 리턴값이 score라서 관리하기 편하기도 하구요.
  • discriminator를 학습한 다음, combined 모델을 학습할 때, discriminator.trainable = False가 있는 것을 알 수 있습니다. generator를 학습해야 하는데, 그 결과 피드백이 discriminator에도 적용이 된다면, 좀 난감하지 않을까요? 이건, discriminator를 멍청하게 만드는 것이죠. 그래서 학습이 되지 않도록 값을 변경해줍니다.
## read image
(X_train, _), (_, _) = mnist.load_data()
# Rescale -1 to 1
X_train = (X_train.astype(np.float32) - 127.5) / 127.5
X_train = np.expand_dims(X_train, axis=3)

## training
"""
- 이 코드에서는 fit을 사용한 것이 아니라, train_on_batch를 사용했음. 
- train_on_batch와의 차이점?을 구글에 검색해보니, 큰 차이가 없다고 하긴 하는데
    - train_on_batch의 경우, 넘겨 받은 데이터에 대해서 gradient vector를 계산해서 적용하고 끝내는 것이고(1epoch)
    - fit의 경우는 epoch과 batch_size를 한번에 모두 넘겨준다는 것 정도가 차이가 된다. 
- GAN의 경우, discriminator의 학습시 마다 generator가 생성하는 데이터가 변화하게 된다. 
    - 즉 처음부터 모든 데이터가 존재하고 이를 한번에 학습시키는 fit과는 다르게, 한번씩 업데이트를 할때마다 모델이 변화하므로, 
    - train_on_batch를 사용하는 것이 매우 합당함.
"""
batch_size = 256
half_batch = batch_size // 2

def train(epochs, print_step=10):
    history = []
    for epoch in range(epochs):
        # discriminator 트레이닝 단계
        #######################################################################3
        # 데이터 절반은 실제 이미지, 절반은 generator가 생성한 가짜 이미지
        # discriminator가 실제 이미지와 가짜 이미지를 구별하도록 discriminator를 트레이닝
        discriminator.trainable = True
        d_loss_real = discriminator.train_on_batch(X_train[np.random.randint(0, X_train.shape[0], half_batch)], 
                                                   np.ones((half_batch, 1)))
        d_loss_fake = discriminator.train_on_batch(generator.predict(np.random.normal(0, 1, (half_batch, 100))), 
                                                   np.zeros((half_batch, 1)))
        d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
        # generator 트레이닝 단계
        #######################################################################3
        # 전부 generator가 생성한 가짜 이미지를 사용. 
        # discriminator가 구별하지 못하도록 generator를 트레이닝
        
        """
        generator를 트레이닝할 때는, 반드시 discriminator가 필요함. 
        generator가 만든 image를 평가해야 하고, 그래야 feedback이 생겨서 generator가 학습됨. 
        따라서, generator는 combined model을 통해 학습시키는데, 이때, discriminator도 함께 학습되면 안되기 때문에
        discriminator.trainable 을 False로 변경시켜 둔다. 
        """
        noise = np.random.normal(0, 1, (batch_size, 100))
        discriminator.trainable = False 
        g_loss = combined.train_on_batch(noise, np.ones((batch_size, 1)))
        # 기록
        record = (epoch, d_loss[0], 100 * d_loss[1], g_loss[0], 100 * g_loss[1])
        history.append(record)
        if epoch % print_step == 0:
            print("%5d [D loss: %.3f, acc.: %.2f%%] [G loss: %.3f, acc.: %.2f%%]" % record)
    return history
history100 = train(100, 10)
show_images(0.5 * generator.predict(noise_data) + 0.5)
  • 학습이 잘 되었다면, discriminator의 결과가 50%가 되어야 합니다. 즉, G가 만든 fake_image를 discriminator가 잘 구별하지 못한다는 말이니까요.
  • 또한, 아래처럼 학습이 잘되지 않습니다. 아래는 제가 이미 여러번 학습한 결과로 이정도 비슷하게 나오는 거구요. 실제로는 약 5000번은 돌려야 좀 비슷하게 나오는 것 같아요.

    0 [D loss: 1.862, acc.: 50.00%] [G loss: 0.205, acc.: 100.00%]
   10 [D loss: 1.880, acc.: 50.00%] [G loss: 0.191, acc.: 100.00%]
   20 [D loss: 1.763, acc.: 50.00%] [G loss: 0.218, acc.: 100.00%]
   30 [D loss: 1.794, acc.: 50.00%] [G loss: 0.209, acc.: 100.00%]
   40 [D loss: 1.817, acc.: 50.00%] [G loss: 0.194, acc.: 100.00%]
   50 [D loss: 1.768, acc.: 50.00%] [G loss: 0.205, acc.: 100.00%]
   60 [D loss: 1.715, acc.: 50.00%] [G loss: 0.214, acc.: 100.00%]
   70 [D loss: 1.712, acc.: 50.00%] [G loss: 0.212, acc.: 100.00%]
   80 [D loss: 1.665, acc.: 50.00%] [G loss: 0.217, acc.: 100.00%]
   90 [D loss: 1.678, acc.: 50.00%] [G loss: 0.216, acc.: 99.61%]
  100 [D loss: 1.654, acc.: 50.00%] [G loss: 0.203, acc.: 100.00%]
  110 [D loss: 1.586, acc.: 50.00%] [G loss: 0.206, acc.: 100.00%]
  120 [D loss: 1.626, acc.: 50.00%] [G loss: 0.196, acc.: 100.00%]
  130 [D loss: 1.557, acc.: 50.00%] [G loss: 0.206, acc.: 100.00%]
  140 [D loss: 1.523, acc.: 50.00%] [G loss: 0.205, acc.: 100.00%]
  150 [D loss: 1.491, acc.: 50.00%] [G loss: 0.193, acc.: 100.00%]
  160 [D loss: 1.510, acc.: 50.00%] [G loss: 0.196, acc.: 100.00%]
  170 [D loss: 1.478, acc.: 50.00%] [G loss: 0.196, acc.: 100.00%]
  180 [D loss: 1.434, acc.: 50.00%] [G loss: 0.195, acc.: 100.00%]
  190 [D loss: 1.428, acc.: 50.00%] [G loss: 0.200, acc.: 100.00%]
  200 [D loss: 1.425, acc.: 50.00%] [G loss: 0.210, acc.: 100.00%]
  210 [D loss: 1.406, acc.: 50.00%] [G loss: 0.201, acc.: 100.00%]
  220 [D loss: 1.398, acc.: 50.00%] [G loss: 0.200, acc.: 100.00%]
  230 [D loss: 1.427, acc.: 50.00%] [G loss: 0.200, acc.: 100.00%]
  240 [D loss: 1.399, acc.: 50.00%] [G loss: 0.203, acc.: 100.00%]
  250 [D loss: 1.395, acc.: 50.00%] [G loss: 0.195, acc.: 100.00%]
  260 [D loss: 1.371, acc.: 50.00%] [G loss: 0.196, acc.: 100.00%]
  270 [D loss: 1.334, acc.: 50.00%] [G loss: 0.197, acc.: 100.00%]
  280 [D loss: 1.321, acc.: 50.00%] [G loss: 0.203, acc.: 100.00%]
  290 [D loss: 1.324, acc.: 50.00%] [G loss: 0.202, acc.: 100.00%]
  300 [D loss: 1.352, acc.: 50.00%] [G loss: 0.205, acc.: 100.00%]
  310 [D loss: 1.326, acc.: 50.00%] [G loss: 0.205, acc.: 100.00%]
  320 [D loss: 1.320, acc.: 50.00%] [G loss: 0.191, acc.: 100.00%]
  330 [D loss: 1.337, acc.: 50.00%] [G loss: 0.202, acc.: 100.00%]
  340 [D loss: 1.315, acc.: 50.00%] [G loss: 0.196, acc.: 100.00%]
  350 [D loss: 1.304, acc.: 50.00%] [G loss: 0.200, acc.: 100.00%]
  360 [D loss: 1.306, acc.: 50.00%] [G loss: 0.190, acc.: 100.00%]
  370 [D loss: 1.338, acc.: 50.00%] [G loss: 0.223, acc.: 100.00%]
  380 [D loss: 1.324, acc.: 50.00%] [G loss: 0.196, acc.: 100.00%]
  390 [D loss: 1.300, acc.: 50.00%] [G loss: 0.197, acc.: 100.00%]
  400 [D loss: 1.277, acc.: 50.00%] [G loss: 0.198, acc.: 100.00%]
  410 [D loss: 1.260, acc.: 50.00%] [G loss: 0.208, acc.: 100.00%]
  420 [D loss: 1.258, acc.: 50.00%] [G loss: 0.199, acc.: 100.00%]
  430 [D loss: 1.254, acc.: 50.00%] [G loss: 0.199, acc.: 100.00%]
  440 [D loss: 1.291, acc.: 50.00%] [G loss: 0.192, acc.: 100.00%]
  450 [D loss: 1.262, acc.: 50.00%] [G loss: 0.193, acc.: 100.00%]
  460 [D loss: 1.240, acc.: 50.00%] [G loss: 0.203, acc.: 100.00%]
  470 [D loss: 1.245, acc.: 50.00%] [G loss: 0.206, acc.: 100.00%]
  480 [D loss: 1.260, acc.: 50.00%] [G loss: 0.205, acc.: 100.00%]
  490 [D loss: 1.239, acc.: 50.00%] [G loss: 0.203, acc.: 100.00%]

wrap-up

  • GAN이 무엇인지, 어떻게 쓰는지 대략의 그림은 그렸는데, 세부적으로는 놓치는 부분이 좀 있는 것 같습니다.
  • 또한, 어떻게 활용할 수 있을지에 대해서도 좀 고민을 해봐야 할 것 같네요.

reference

raw code

import numpy as np 
import matplotlib.pyplot as plt 

from keras.datasets import mnist

from keras.models import Sequential, Model

from keras.layers import Input, Dense, Reshape, Flatten, Dropout
from keras.layers import BatchNormalization, Activation, ZeroPadding2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import UpSampling2D, Conv2D

from keras.optimizers import Adam

#noise_data = np.random.normal(0, 1, (32, 100))
#generated_images = 0.5 * generator.predict(np.random.normal(0, 1, (32, 100))) + 0.5

def show_images(generated_images, n=4, m=8, figsize=(9, 5)):
    f, axes = plt.subplots(n, m, figsize=figsize)
    #plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05)
    for i in range(0, n):
        for j in range(0, m):
            ax = axes[i][j]
            ax.imshow(generated_images[i * m + j][:, :, 0], cmap=plt.cm.bone)
            ax.grid(False)
            ax.xaxis.set_ticks([])
            ax.yaxis.set_ticks([])
    plt.tight_layout()
    plt.savefig('../../assets/images/markdown_img/180621_1554_gan_digit_img.svg')
    plt.show()   
#show_images(0.5 * generator.predict(np.random.normal(0, 1, (32, 100))) + 0.5)


## create generator 
generator_ = Sequential([
    Dense(128 * 7 * 7, activation="relu", input_shape=(100,)), 
    Reshape((7, 7, 128)), 
    
    BatchNormalization(momentum=0.8), # what is batch normalization?? 
    UpSampling2D(), # what is upsampling?? 
    Conv2D(128, kernel_size=3, padding="same"),
    Activation("relu"), 
    
    BatchNormalization(momentum=0.8), 
    UpSampling2D(), 
    Conv2D(64, kernel_size=3, padding="same"), 
    Activation("relu"), 
    
    BatchNormalization(momentum=0.8), 
    Conv2D(1, kernel_size=3, padding="same"), 
    Activation("tanh"), 
])

noise_input = Input(shape=(100,), name="noise_input")
generator = Model(noise_input, generator_(noise_input), name="generator")

generator_.summary()# summary가 매우 유용하군요. 

optimizer = Adam(0.0002, 0.5)
generator.compile(loss='binary_crossentropy', optimizer=optimizer)

### create discriminator
discriminator_ = Sequential([
    Conv2D(32, kernel_size=3, strides=2, input_shape=(28, 28, 1), padding="same"), 
    LeakyReLU(alpha=0.2), 
    Dropout(0.25), 
    
    Conv2D(64, kernel_size=3, strides=2, padding="same"), 
    ZeroPadding2D(padding=((0,1),(0,1))), 
    LeakyReLU(alpha=0.2), 
    Dropout(0.25), 
    BatchNormalization(momentum=0.8), 
    
    Conv2D(128, kernel_size=3, strides=2, padding="same"), 
    LeakyReLU(alpha=0.2), 
    Dropout(0.25), 
    BatchNormalization(momentum=0.8), 
    
    Conv2D(256, kernel_size=3, strides=1, padding="same"), 
    LeakyReLU(alpha=0.2), 
    Dropout(0.25), 
    Flatten(), 
    Dense(1, activation='sigmoid'), 
])
image_input = Input(shape=(28, 28, 1), name="image_input")

discriminator = Model(image_input, discriminator_(image_input), name="discriminator")
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])
discriminator_.summary()

### Combined Model
noise_input2 = Input(shape=(100,), name="noise_input2")
"""
model과 sequential의 차이는?? 
가설1: 레이어를 쌓는 것이 sequential 이라면, sequential을 쌓는 것이 model인가???

1) 다음 모델의 경우는 랜덤으로 만든 이미지로부터 학습해서 새로운 이미지를 만들어내는 generator의 데이터를 
2) discriminator가 분류하는 형식으로 진행된다. 
"""
combined = Model(noise_input2, discriminator(generator(noise_input2)))
combined.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])

## read image
(X_train, _), (_, _) = mnist.load_data()
# Rescale -1 to 1
X_train = (X_train.astype(np.float32) - 127.5) / 127.5
X_train = np.expand_dims(X_train, axis=3)

## training
"""
- 이 코드에서는 fit을 사용한 것이 아니라, train_on_batch를 사용했음. 
- train_on_batch와의 차이점?을 구글에 검색해보니, 큰 차이가 없다고 하긴 하는데
    - train_on_batch의 경우, 넘겨 받은 데이터에 대해서 gradient vector를 계산해서 적용하고 끝내는 것이고(1epoch)
    - fit의 경우는 epoch과 batch_size를 한번에 모두 넘겨준다는 것 정도가 차이가 된다. 
- GAN의 경우, discriminator의 학습시 마다 generator가 생성하는 데이터가 변화하게 된다. 
    - 즉 처음부터 모든 데이터가 존재하고 이를 한번에 학습시키는 fit과는 다르게, 한번씩 업데이트를 할때마다 모델이 변화하므로, 
    - train_on_batch를 사용하는 것이 매우 합당함.
"""
batch_size = 256
half_batch = batch_size // 2

def train(epochs, print_step=10):
    history = []
    for epoch in range(epochs):
        # discriminator 트레이닝 단계
        #######################################################################3
        # 데이터 절반은 실제 이미지, 절반은 generator가 생성한 가짜 이미지
        # discriminator가 실제 이미지와 가짜 이미지를 구별하도록 discriminator를 트레이닝
        discriminator.trainable = True
        d_loss_real = discriminator.train_on_batch(X_train[np.random.randint(0, X_train.shape[0], half_batch)], 
                                                   np.ones((half_batch, 1)))
        d_loss_fake = discriminator.train_on_batch(generator.predict(np.random.normal(0, 1, (half_batch, 100))), 
                                                   np.zeros((half_batch, 1)))
        d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
        # generator 트레이닝 단계
        #######################################################################3
        # 전부 generator가 생성한 가짜 이미지를 사용. 
        # discriminator가 구별하지 못하도록 generator를 트레이닝
        
        """
        generator를 트레이닝할 때는, 반드시 discriminator가 필요함. 
        generator가 만든 image를 평가해야 하고, 그래야 feedback이 생겨서 generator가 학습됨. 
        따라서, generator는 combined model을 통해 학습시키는데, 이때, discriminator도 함께 학습되면 안되기 때문에
        discriminator.trainable 을 False로 변경시켜 둔다. 
        """
        noise = np.random.normal(0, 1, (batch_size, 100))
        discriminator.trainable = False 
        g_loss = combined.train_on_batch(noise, np.ones((batch_size, 1)))
        # 기록
        record = (epoch, d_loss[0], 100 * d_loss[1], g_loss[0], 100 * g_loss[1])
        history.append(record)
        if epoch % print_step == 0:
            print("%5d [D loss: %.3f, acc.: %.2f%%] [G loss: %.3f, acc.: %.2f%%]" % record)
    return history
#%%time, 은
history100 = train(100, 10)
show_images(0.5 * generator.predict(noise_data) + 0.5)

댓글남기기