[VISION] 비전 시스템을 위한 딥러닝(9) - 프로젝트: GAN 모델 직접 구현해보기

Study/Vision & Deep Learning · 2023. 5. 31. 16:02

이번 프로젝트는 합성곱층으로 생성자 모델과 판별자 모델을 구현해서 GAN 모델을 구성해보는 것을 목표로 한다.

이러한 GAN 모델을 심층 합성곱 GAN (DCGAN)이라고 한다.

학습 데이터는 Fashion-MNIST 데이터셋을 학습 데이터로 사용한다. 

Fasion-MNIST는 6만 장의 회색조 이미지로 구성되어 있으며, 그 중 테스트 데이터가 1만장이다.

각 이미지는 28x28 픽셀 크기의 회색조 이미지이며 10개 클래스의 레이블과 연관되어 있다.

 

1단계: 라이브러리 임포트하기

from __future__ import print_function, division

from keras.datasets import mnist
from keras.layers import Input, Dense, Reshape, Flatten, Dropout
from keras.layers import BatchNormalization, Activation, ZeroPadding2D
from keras.layers import LeakyReLU
from keras.layers.convolutional import UpSampling2D, Conv2D
from keras.models import Sequential, Model
from keras.optimizers import Adam

import matplotlib.pyplot as plt
%matplotlib inline

from keras.datasets import fashion_mnist

from tqdm import tqdm

import numpy as np

 

2단계: 데이터셋 내려받기 및 이미지 확인하기

fashion_mnist.load.data() 명령 한 줄만으로 데이터셋을 내려받을 수 있다.

데이터셋을 내려받은 후 학습 속도 향상을 위해 픽셀값의 범위를 -1부터 1이 되도록 조정한다.

# Load the dataset
(training_data, _), (_, _) = fashion_mnist.load_data()

# Rescale the training data to scale -1 to 1
X_train = training_data / 127.5 - 1.
X_train = np.expand_dims(X_train, axis=3)

def visualize_input(img, ax):
    ax.imshow(img, cmap='gray')
    width, height = img.shape
    thresh = img.max()/2.5
    for x in range(width):
        for y in range(height):
            ax.annotate(str(round(img[x][y],2)), xy=(y,x),
                        horizontalalignment='center',
                        verticalalignment='center',
                        color='white' if img[x][y]<thresh else 'black')

fig = plt.figure(figsize = (12,12)) 
ax = fig.add_subplot(111)
visualize_input(training_data[3343], ax)

 

3단계: 생성자 모델 구성하기

생성자 모델은 노이즈 벡터(z)를 입력으로 받는다.

생성자 모델의 구조의 첫 번째 층은 전결합층이며, 그다음 층에서 1차원 벡터를 가로세로는 좁으나 깊이가 깊은 모양으로 변형한다.

그리고 이어지는 업샘플링 층에서 특징 맵의 크기를 두 배씩 늘린다.

아래 구현에서는 3개의 합성곱층을 배치했고, 각 층은 합성곱층 -> 배치 정규화층 -> ReLU 순으로 구성된다.

이를 28x28이 될 때까지 반복한다.

def build_generator():

    model = Sequential()

	# 전결합층 추가
    model.add(Dense(128 * 7 * 7, activation="relu", input_dim= latent_dim))
    # 이미지 행렬의 모양을 변환
    model.add(Reshape((7, 7, 128)))
    # 이미지의 크기를 가로세로 두 배씩 늘리는 업샘플링층 (7x7 -> 14x14)
    model.add(UpSampling2D())

	# 합성곱 연산과 배치 정규화를 수행하는 합성곱층을 추가한다.
    model.add(Conv2D(128, kernel_size=3, padding="same"))
    model.add(BatchNormalization(momentum=0.8))
    model.add(Activation("relu"))
    
    model.add(UpSampling2D())

    model.add(Conv2D(64, kernel_size=3, padding="same"))
    model.add(BatchNormalization(momentum=0.8))
    model.add(Activation("relu"))

    model.add(Conv2D(channels, kernel_size=3, padding="same"))
    model.add(Activation("tanh"))

    model.summary()

    noise = Input(shape=(latent_dim,))
    img = model(noise)

    return Model(noise, img)

 

4단계: 판별자 모델 구성하기

판별자 모델은 합성곱층으로 구성된 일반적인 분류 모델이다.

판별자 모델의 입력은 28x1 크기의 이미지다.

합성곱층의 깊이(필터 수)는 첫 번째 층의 필터 수를 32 또는 64로 설정하고, 층마다 두 배씩 증가시킨다.

다운샘플링은 풀링층 대신 스트라이드 값을 조정한 합성곱층을 사용한다.

4개의 합성곱층 모두 합성곱층 -> 배치 정규화 -> 누설 ReLU 활성화 함수로 구성한다.

def build_discriminator():

    model = Sequential()

    model.add(Conv2D(32, kernel_size=3, strides=2, input_shape= img_shape, padding="same"))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))

    model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))
    model.add(ZeroPadding2D(padding=((0,1),(0,1))))
    model.add(BatchNormalization(momentum=0.8))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))

    model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))
    model.add(BatchNormalization(momentum=0.8))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))

    model.add(Conv2D(256, kernel_size=3, strides=1, padding="same"))
    model.add(BatchNormalization(momentum=0.8))
    model.add(LeakyReLU(alpha=0.2))
    model.add(Dropout(0.25))

    model.add(Flatten())
    model.add(Dense(1, activation='sigmoid'))

    model.summary()

    img = Input(shape= img_shape)
    validity = model(img)

    return Model(img, validity)

 

5단계: 통합 신경망 구성하기

생성자 모델을 학습시키려면 생성자 모델과 판별자 모델을 모두 포함하는 통합 신경망을 구성해야 한다.

통합 신경망은 노이즈 데이터 z를 입력받아 판별자 모델의 예측 결과를 출력한다.

통합 신경망의 구조

앞서 말했듯이 통합 신경망으로 학습을 진행할 때는 판별자 모델의 가중치를 고정해두는 것을 잊지 말아야 한다.

따라서, 아래 코드에서도 해당 부분이 고정되어 있다.

optimizer = Adam(0.0002, 0.5)

# Build and compile the discriminator
discriminator = build_discriminator()
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])

# Build the generator
generator = build_generator()

# The generator takes noise as input and generates imgs
z = Input(shape=(latent_dim,))
img = generator(z)

# 판별자 모델의 가중치를 고정한다.
# For the combined model we will only train the generator
discriminator.trainable = False

# The discriminator takes generated images as input and determines validity
valid = discriminator(img)

# The combined model  (stacked generator and discriminator)
# Trains the generator to fool the discriminator
# 통합 신경망을 이용해서 판별자 모델을 속일 수 있도록 생성자 모델을 합성시킨다.
combined = Model(z, valid)
combined.compile(loss='binary_crossentropy', optimizer=optimizer)

 

6단계: 합습 과정을 담당하는 함수 구현

다음과 같은 인수를 갖는 train 함수를 구현한다.

  • 에포크 수
  • 배치 크기
  • 모델의 상태를 저장하는 간격(save_interval)
def train(epochs, batch_size=128, save_interval=50):

    # Adversarial ground truths
    # 정답 데이터
    valid = np.ones((batch_size, 1)) 
    fake = np.zeros((batch_size, 1))

    for epoch in range(epochs):

        # ---------------------
        #  Train Discriminator
        # ---------------------

        # Select a random half of images
        idx = np.random.randint(0, X_train.shape[0], batch_size)
        imgs = X_train[idx]

        # Sample noise and generate a batch of new images
        noise = np.random.normal(0, 1, (batch_size, latent_dim))
        gen_imgs = generator.predict(noise)

        # Train the discriminator (real classified as ones and generated as zeros)
        d_loss_real = discriminator.train_on_batch(imgs, valid)
        d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
        d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

        # ---------------------
        #  Train Generator
        # ---------------------

        # Train the generator (wants discriminator to mistake images as real)
        g_loss = combined.train_on_batch(noise, valid)

        # Plot the progress
        print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss))

        # If at save interval => save generated image samples
        if epoch % save_interval == 0:
            plot_generated_images(epoch, generator)

마지막 부분의 plot_generated_images() 함수는 다음과 같다.

def plot_generated_images(epoch, generator, examples=100, dim=(10, 10), figsize=(10, 10)):
    noise = np.random.normal(0, 1, size=[examples, latent_dim])
    generated_images = generator.predict(noise)
    generated_images = generated_images.reshape(examples, 28, 28)

    plt.figure(figsize=figsize)
    for i in range(generated_images.shape[0]):
        plt.subplot(dim[0], dim[1], i+1)
        plt.imshow(generated_images[i], interpolation='nearest', cmap='gray_r')
        plt.axis('off')
    plt.tight_layout()
    plt.savefig('gan_generated_image_epoch_%d.png' % epoch)

 

7단계: 학습 및 학습 결과 관찰

train(epochs=10, batch_size=32, save_interval=1)

모델이 생성한 이미지 예시

에포크가 진행될수록 훈련 데이터와 비슷한 데이터가 생성된 것을 확인할 수 있다.

 

 

 

반응형