본문 바로가기
Personal Study/Model

[생성 신경망] DCGAN 모델 구현

by muns91 2024. 4. 18.
Deep Convolutional GAN (DCGAN) 실습

 

이번에는 지난 시간에 실습 했었던 GAN 모델의 업그레이 버전인 DCGAN 모델을 구현해보도록 하겠습니다. 이전에 GAN 모델은 DNN 모델을 적용했다면 DCGAN 모델은 이름 그대로 CNN 모델을 적용했다는 것을 알 수 있습니다. 거의 예전 모델에서 신경망 모델만 바뀌었다고 생각하시면 됩니다!. 그러면 아래를 통해 모델 구현 환경 및 코드를 살펴보고 실습을 수행하도록 하겠습니다. 대부분의 코드는 이전 GAN 실습과 거의 유사하기 때문에 설명이 겹치는 부분은 생략하도록 하겠습니다. 그래도 궁금하신 분들이 있다면 아래의 이전 GAN 실습을 참고해주세요!

 

 

Generative-Adversarial-Network/DCGAN at main · Muns91/Generative-Adversarial-Network

Contribute to Muns91/Generative-Adversarial-Network development by creating an account on GitHub.

github.com


라이브러리 & 모듈 import

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

import os
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

 

 위 코드에서는 모델 구현과 배열 그리고 이미지를 위한 라이브러리와 모듈들을 import 하였습니다. 해당 코드를 통해 라이브러리와 모듈의 기능을 사용할 수 있습니다. 

 


데이터 불러오기

# 이미지를 저장할 폴더가 없으면 생성
if not os.path.exists('gan_images'):
    os.makedirs('gan_images')

# MNIST 데이터 불러오기
(X_train, _), (_, _) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32')
X_train = (X_train - 127.5) / 127.5  # 127.5를 뺀 후 127.5로 나누어서 -1~1 사이의 값으로 바꿉니다.

true = np.ones((batch_size, 1))
fake = np.zeros((batch_size, 1))

 

 데이터는 GAN과 마찬가지로 MNIST 데이터를 사용했고 1) GAN을 통해 생성할 이미지를 저장할 폴더를 확인하고 생성하는 코드, 2) 데이터를 로드하고 저장할 변수 지정, reshape 및 정규화 그리고 3) 진짜 이미지와 가짜 이미지에 대한 레이블을 생성하는 코드로 이루어져있습니다. 아래를 통해 추가적인 설명을 진행하도록 하겠습니다.

 

 

1) 폴더 확인 및 생성

if not os.path.exists('gan_images'):
    os.makedirs('gan_images')

 

 이 코드에는 'gan_images' 라는 이름의 디렉토리가 현재 작업 디렉토리에 존재하는지를 확인합니다. 만약 디렉토리가 존재하지 않는다면 'os.makedirs('gan_images')'를 호출하여 해당 폴더를 생성하게 됩니다. 이 폴더는 훈련 중에서 생성된 이미지를 저장하는 용도입니다. 

 

 

2) 데이터 로딩 및 전처리

# MNIST 데이터 불러오기
(X_train, _), (_, _) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32')
X_train = (X_train - 127.5) / 127.5  # 127.5를 뺀 후 127.5로 나누어서 -1~1 사이의 값으로 바꿉니다.

 

 실습에서는 손글씨 데이터인 'MNIST' 데이터를 사용합니다.' 따라서 데이터를 로드하고 이를 X_train에 저장합니다. 그리고 저장받은 데이터를 사용하기 위해 추가적으로 차원을 '1' 하나 더 만들어 4차원 형태로 만들어냅니다. 여기서 추가된 1은 이미지의 채널 수를 의미합니다. 실습에서 사용된 MNIST 데이터는 흑백이기 때문에 채널 수는 1입니다.

 

3) 레이블 생성 *

true = np.ones((batch_size, 1))
fake = np.zeros((batch_size, 1))

 

 'true'와 'fake'는 각각 진짜 이미지와 가까 이미지에 대한 레이블을 생성합니다. true는 진짜 이미지에 해당하는 배치 크기만큼의 1로 채워진 배열을 생성하고, fake는 가짜 이미지에 해당하는 배치 크기만큼 0으로 채워진 배열을 생성합니다. 이 두 레이블은 훈련 과정에서 판별자가 이미지를 진짜로 판단하는지 혹은 가짜로 판단하는지에 대한 학습 목표로 사용됩니다. 


CNN 생성자 모델 *

# 생성자 모델을 만듭니다.
generator = Sequential()
generator.add(Dense(128*7*7, input_dim=100, activation=LeakyReLU(0.2)))
generator.add(BatchNormalization())
generator.add(Reshape((7, 7, 128)))
generator.add(UpSampling2D())
generator.add(Conv2D(64, kernel_size=5, padding='same'))
generator.add(BatchNormalization())
generator.add(Activation(LeakyReLU(0.2)))
generator.add(UpSampling2D())
generator.add(Conv2D(1, kernel_size=5, padding='same', activation='tanh'))

generator.summary()

 

 이 코드는 딥러닝에서 DCGAN의 생성자 부분을 구성하고 있는 모델입니다. 이전 GAN과 다르게 CNN 모델로 구축이 되어 있습니다. 특이 사항으로 generator.add(BatchNormalization())과 generator.add(UpSampling2D()) 이 사용되었는 데, 배치 정규화의 경우 배치 별로 이전 레이어의 출력을 정규화함으로써 학습 과정을 안정시키고 학습의 속도를 향상 시키는 역할을 합니다. 업샘플링의 경우 이미지의 크기를 두배로 늘리는 작업을 수행합니다. 업샘플링을 사용하는 이유는 이미지의 해상도를 크게 하여 큰 해상도의 이미지를 생성하게 해주고 생성자의 네트워크가 초기에 작은 차원에서 시작해서 점차적으로 목표 이미지의 해상도로 확대해 나가는 과정을 수행하기 위해서 입니다. 또한 확대된 픽셀 주변에 세부적인 패턴과 텍스처를 재현할 수 있게 도와줍니다. 

생성자 모델

 

CNN 판별자 모델 *

# 판별자 모델을 만듭니다.
discriminator = Sequential()
discriminator.add(Conv2D(64, kernel_size=5, strides=2, input_shape=(28,28,1), padding="same"))
discriminator.add(Activation(LeakyReLU(0.2)))
discriminator.add(Dropout(0.3))
discriminator.add(Conv2D(128, kernel_size=5, strides=2, padding="same"))
discriminator.add(Activation(LeakyReLU(0.2)))
discriminator.add(Dropout(0.3))
discriminator.add(Flatten())
discriminator.add(Dense(1, activation='sigmoid'))
discriminator.compile(loss='binary_crossentropy', optimizer='adam')
discriminator.trainable = False

discriminator.summary()

 

  위 코드는 판별자 모델을 설정하는 부분으로서 판별자는 생성자가 만든 이미지가 실제 이미지인지 아닌지를 판별합니다. 이 코드는 위에서 언급한 생성자와는 판별을 하는 모델이기 때문에 업샘플링이 사용되지 않습니다. 이 모델은 생성된 가짜 이미지가 실제 이미지와 얼마나 유사한지를 판별하는 기능을 수행합니다. 판별자의 목표는 진짜 이미지를 진짜(1)로 가짜 이미지를 가짜(0)으로 정확하게 구분하는 것입니다. 따라서 위에서 언급한 true와 fake 레이블이 1과 0으로 되어 있기 때문에 이를 최종적으로 답을 내놓기 위해 아웃풋의 출력은 1이고 이를 예측하기 위해 binary_crossentropy와 sigmoid를 사용하게 됩니다.

 

discriminator.trainable = False

 

 마지막에 사용된 이 설정은 생성자와 판별자가 연결된 전체 GAN에서 훈련을 할때 판별자의 가중치가 업데이트되지 않도록 합니다. 이는 판별자를 고정시켜서 생성자만을 훈련하게 함으로써 전체적인 GAN 과정에서 꼭 필요합니다. 

 

판별자 모델

 

DCGAN 모델 만들기 *

# 생성자와 판별자 모델을 연결시키는 gan 모델을 만듭니다.
ginput = Input(shape=(100,))
dis_output = discriminator(generator(ginput))
gan = Model(ginput, dis_output)
gan.compile(loss='binary_crossentropy', optimizer='adam')
gan.summary()

 

 우선적으로 생성자의 입력 함수(ginput)는 100차원의 랜덤 노이즈가 '생성자'의 입력으로 사용됩니다. 차원이 100인 이유는 생성자가 다양한 패턴과 특징을 학습하고 이를 기반으로 복잡한 이미지를 생성할 수 있을 정도로 충분히 많은 정보를 포함할 수 있기 때문입니다. 따라서 차원이 높아질 수록 생성자가 더 세밀하고 다양한 출력을 생성할 수 있게 됩니다. 하지만 차원 수를 너무 높게 설정하면 모델 학습이 더 어려워지고 과적합의 위험이 증가할 수 있습니다. 

 

 이후,  ginput은  바로 판별자의 입력으로 사용되게 되며 이를 통해 판별자는 가짜와 진짜에 대한 출력을 dis_output을 통해 내보냅니다. 그래서 ginput과 dis_output이 Model로 들어가 생성자와 판별자가 연결된 하나의 큰 모델로 만들어지게 됩니다. 

 

DCGAN 모델

 


학 습

for i in range(epoch):
    # 실제 데이터를 판별자에 입력하는 부분입니다.
    idx = np.random.randint(0, X_train.shape[0], batch_size)
    imgs = X_train[idx]
    d_loss_real = discriminator.train_on_batch(imgs, true)

    # 가상 이미지를 판별자에 입력하는 부분입니다.
    noise = np.random.normal(0, 1, (batch_size, 100))
    gen_imgs = generator.predict(noise)
    d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)

    # 판별자와 생성자의 오차를 계산합니다.
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
    g_loss = gan.train_on_batch(noise, true)

    print('epoch:%d' % i, ' d_loss:%.4f' % d_loss, ' g_loss:%.4f' % g_loss)

    # 이미지 저장 부분
    if i % saving_interval == 0:
        noise = np.random.normal(0, 1, (25, 100))
        gen_imgs = generator.predict(noise)
        gen_imgs = 0.5 * gen_imgs + 0.5  # Rescale images 0 - 1

        fig, axs = plt.subplots(5, 5)
        count = 0
        for j in range(5):
            for k in range(5):
                axs[j, k].imshow(gen_imgs[count, :, :, 0], cmap='gray')
                axs[j, k].axis('off')
                count += 1
        fig.savefig("gan_images/gan_mnist_%d.png" % i)
        plt.close(fig)  # 메모리 누수 방지를 위해 plt 객체를 닫습니다.

 

 여기서부터는 학습 과정이 수행됩니다. 불러온 모델을 통해서 epoch 만큼 학습이 수행되고 saving_interval에 저장된 숫자만큼 epoch 당 생성한 이미지를 저장합니다. 나머지 자세한 사항은 아래를 통해 설명하도록 하겠습니다. 

 

1) 훈련 루프

for i in range(epoch):
    # 실제 데이터를 판별자에 입력하는 부분입니다.
    idx = np.random.randint(0, X_train.shape[0], batch_size)
    imgs = X_train[idx]
    d_loss_real = discriminator.train_on_batch(imgs, true)

    # 가상 이미지를 판별자에 입력하는 부분입니다.
    noise = np.random.normal(0, 1, (batch_size, 100))
    gen_imgs = generator.predict(noise)
    d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)

 

2) 손실 계산 및 출력

    # 판별자와 생성자의 오차를 계산합니다.
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)
    g_loss = gan.train_on_batch(noise, true)

 

3) 이미지 저장

    if i % saving_interval == 0:
        noise = np.random.normal(0, 1, (25, 100))
        gen_imgs = generator.predict(noise)
        gen_imgs = 0.5 * gen_imgs + 0.5  # Rescale images 0 - 1

        fig, axs = plt.subplots(5, 5)
        count = 0
        for j in range(5):
            for k in range(5):
                axs[j, k].imshow(gen_imgs[count, :, :, 0], cmap='gray')
                axs[j, k].axis('off')
                count += 1
        fig.savefig("gan_images/gan_mnist_%d.png" % i)
        plt.close(fig)

 


원본과 생성 이미지 비교

def load_and_compare_images(epoch, example_index=0):
    # MNIST 데이터 불러오기
    (X_train, _), (_, _) = mnist.load_data()
    X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32')
    X_train = (X_train - 127.5) / 127.5  # Normalize

    # 저장된 이미지 불러오기
    image_path = f"gan_images/gan_mnist_{epoch}.png"
    if not os.path.exists(image_path):
        print("해당 경로에 이미지 파일이 없습니다:", image_path)
        return

    generated_image = mpimg.imread(image_path)

    # 원본 이미지 선택
    original_image = X_train[example_index].reshape(28, 28)

    # 이미지 비교
    fig, axs = plt.subplots(1, 2, figsize=(10, 5))
    axs[0].imshow(original_image, cmap='gray')
    axs[0].set_title('Original Image')
    axs[0].axis('off')

    axs[1].imshow(generated_image)
    axs[1].set_title('Generated Image')
    axs[1].axis('off')

    plt.show()

# 예를 들어, 200번째 에포크의 생성된 이미지와 원본 이미지 비교
load_and_compare_images(1600, example_index=1)

 

 이 코드에서는 특정 epoch에서 생성된 GAN 이미지와 실제 데이터 셋의 이미지를 비교합니다. 목적은 생성자가 시간에 따라 얼마나 진짜 같은 이미지를 생성하는 지 시각적으로 확인하기 위해서 입니다. 아래 사진을 통해서 1000 Epoch 마다의 이미지를 비교보았습니다. 확실히 학습이 덜 되었을 때는 상대적으로 노이즈가 많이보이고 일정 구간이 지나면 글자의 모양이 나타나기 시작하는 것을 확인할 수 있습니다. 

 

epoch = 1000

 

Epoch = 2000

 

Epoch = 4000


 

마무리

 여기까지 기본적인 DCGAN에 대한 실습을 해보았습니다. DNN에서 CNN으로 모델이 강화된 이후에도 GAN은 여기서 멈추지 않고 더 발전을 했습니다. 다음에는 DCGAN 외에도 다른 방식이 적용된 또 다른 GAN 활용한 실습을 진행하도록 하겠습니다. 그럼 이번 글을 여기서 마치도록 하겠습니다. 

 

참고 

모두의 딥러닝 : https://thebook.io/080324/

 

모두의 딥러닝 개정 3판

더북(TheBook): (주)도서출판 길벗에서 제공하는 IT 도서 열람 서비스입니다.

thebook.io