본문 바로가기

Book/GAN In Action

4장. DCGAN

반응형

  3장에서 배웠던 내용은 하나의 은닉층을 가진 피드포워드 신경망을 활용하여 GAN의 생성자와 판별자를 구성하여 훈련하였다. 이번 장에서는 간단한 피드포워드 신경망 대신 합성곱 신경망을 활용하여 생성자와 판별자를 구현하는 DCGAN에 대해 학습하였다.

 

  DCGAN에 대해 학습하기 전에 합성곱 신경망과 배치 정규화에 대해 학습하였다.

 

합성곱 신경망(ConvNet or CNN)

  합성곱 신경망의 층은 피드포워드 신경망과 달리 3차원으로 구성된다.(너비 x 높이 x 깊이) 하나 이상의 필터(가중치 커널)이 입력층 위를 슬라이딩하면서 합성곱을 수행한다. 각 필터는 비교적 작은 수용장(너비 x 높이)를 가진다. 하지만 항상 입력 배열의 전체 깊이에 적용된다.

  입력 위를 슬라이딩할 때 각 필터는 하나의 활성화 값을 출력한다. 이 값은 입력과 필터 사이의 점곱으로 계산한다. 이 과정에서 필터마다 2차원 활성화 맵이 만들어지게 된다. 각 필터가 만든 활성화 맵을 차례로 쌓아 3차원 출력층을 만든다. 결국 출력의 깊이는 사용한 필터의 수와 같게 된다.

 

  특정 필터의 파라미터가 모든 입력 값에 공유된다는 점이 중요하다.  파라미터 공유를 통해 시각적인 특징과 모양이 입력 이미지의 어느 부분에 있는지에 상관없이 효율적으로 학습할 수 있다. 실용적인 측면을 보면 파라미터 공유는 훈련할 파라미터의 개수를 크게 감소시킨다. 이렇게 하면 과대적합의 위험이 줄어들고 완전 연결 신경망의 경우처럼 훈련 파라미터의 개수를 기하급수적으로 늘리지 않고 스케일을 키워 고해상도 이미지를 만들 수 있다.

 

DCGAN의 역사

  DCGAN은 2016년에 알렉 래드퍼드, 루케 메츠, 수미스 친탈라가 소개하였다. GAN에 합성곱 신경망을 사용한 것은 LAPGAN이 처음이었다. LAPGAN은 라플라스 피라미드 안에 합성곱 신경망을 폭포수처럼 쌓은 후 각 단계에 있는 합성곱은 GAN 프레임워크를 사용하여 독립적으로 훈련하는 방식이다. 하지만 이 방식은 구조가 우아하지 않고 복잡하며 계산 비용이 많이 들었다. 그럼에도 기존의 GAN보다 4배나 성능이 더 좋았다.

  DCGAN을 활용하여 완전한 GAN프레임워크의 스케일을 키울 수 있는 기법과 최적화 방법을 소개하여 더이상 LAPGAN을 사용할 필요가 없어졌다. DCGAN을 활용한 방법 중 핵심 기법은 배치 정규화이다. 배치 정규화는 각 층의 입력을 정규화하여 안정적으로 훈련하는 것을 도와준다.

 

배치 정규화

  정규화란 평균이 0이고 단위 분산을 가지도록 데이터의 스케일을 조정하는 것이다. 입력 데이터 포인트 x에서 평균 μ를 뺀 값을 표준편차 σ로 나누어 계산한다.

  정규화는 스케일이 다른 특성을 쉽게 비교하고 더 나아가 특성의 스케일에 훈련 과정이 민감하지 않게 만든다.

 

  배치 정규화의 아이디어는 많은 층을 가진 심층 신경망을 다룰 때 입력을 정규화하는 것이 충분하지 않다는 것에서 출발한다. 입력 값이 네트워크를 통과하여 한 층에서 다음 층으로 전달될 때 각 층에 있는 훈련된 파라미터에 의해 스케일이 바뀐다. 이 파라미터가 역전파에 의해 조정되기 때문에 연속적으로 훈련이 반복되는 동안 각 층의 입력의 분포가 바뀌기 쉬워 학습 과정을 불안하게 만든다. 이 문제를 공변량 변화라고 부른다. 배치 정규화는 각 미니 배치의 평균과 분산으로 미니 배치 데이터의 스케일을 조정하여 이 문제를 해결한다. 

  정규화된 x 값에서 0으로 나누는 걸 피하기 위해 0.001같이 작은 양의 상수 값으로 입실론 항을 추가한다. 배치 정규화에서는 정규화된 값을 바로 사용하지 않고 이 값을 다음 층의 입력으로 전달하기 전에 감마를 곱하고 베타를 더한다.

  감마와 베타가 가중치나 편향처럼 신경망이 훈련되는 동안 훈련되는 파라미터라는 점이 중요하다. 이렇게 하는 이유는 0이 아닌 평균과 1이 아닌 분산으로 표준화된 중간층의 입력 값에 도움이 되기 때문이다. 감마와 베타가 훈련되기 때문에 신경망이 어떤 값이 최선인지 학습할 수 있다.

  배치 정규화는 이전 층의 파라미터 업데이트가 현재 층의 입력 분포에 미치는 영향을 제한한다. 층 간의 원치 않는 파라미터 상호 의존성을 줄여서 신경망의 훈련 속도를 높이고 안정적으로 만든다. 특히 신경망 파라미터 초기화에 대해 안정적이다.

 

DCGAN을 활용하여 숫자 생성하기

  MNIST 숫자 데이터셋을 활용하여 DCGAN을 사용해보자. 

 

  먼저 필요한 패키지와 모듈, 라이브러리를 가져온다.

%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np

from tensorflow.keras.datasets import mnist
from tensorflow.keras.layers import Activation, BatchNormalization, Dense, Dropout, Flatten, Reshape
from tensorflow.keras.layers import LeakyReLU
from tensorflow.keras.layers import Conv2D, Conv2DTranspose
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import Adam

 

  모델의 입력 차원을 지정한다.

 

img_rows = 28
img_cols = 28
channels = 1

# 입력 이미지 차원
img_shape = (img_rows, img_cols, channels)

# 생성자 입력으로 사용될 잡음 벡터의 크기
z_dim = 100

  

  ConvNet은 전통적으로 이미지 분류 작업에 사용한다. 이 네트워크는 높이 x 너비 x 컬러 채널 수 차원을 가진 이미지를 입력으로 받아 일련의 합성곱 층을 통과시킨다. 그다음 클래스 점수를 담은 1 x n 차원의 벡터 하나를 출력한다.(n은 클래스 레이블의 수)

  ConvNet 구조를 사용해 이미지를 생성하려면 이 과정을 거꾸로 하면 된다. 이미지를 받아 처리하여 벡터로 만드는 것이 아니라 벡터를 받아 크기를 늘려 이미지로 생성한다.

 

  이 과정의 핵심 요소는 전치 합성곱이다. 일반적인 합성곱은 전형적으로 깊이를 늘리면서 입력 너비와 높이를 줄이기 위해 사용한다. 전치 합성곱은 반대로 동작한다. 깊이를 줄이는 동안 너비와 높이를 증가시킨다. 진행 과정을 좀더 자세히 생각해보자.

 

  생성자는 잡음 벡터 z에서 출발한다. 완전 연결 층을 사용해 이 벡터를 작은 면적(높이 x 너비)과 큰 깊이를 가진 3차원 출력으로 바꾼다. 전치 합성곱을 사용해 이 출력의 깊이는 줄이고 면적은 늘리도록 점진적으로 바꾸어 간다. 최종 층에 도달할 때 이미지의 크기는 우리가 사용할 데이터셋과 동일한 28 x 28 x 1의 크기가 된다. 각 전치 합성곱층 다음에는 배치 정규화 층과 LeakyReLU 활성화 함수 층을 적용한다. 마지막 층에서는 배치 정규화를 적용하지 않고 LeakyReLU 활성화 함수 대신 tanh 활성화 함수를 사용한다.

# 생성자
def build_generator(z_dim):

    model = Sequential()

    # 완전 연결층을 통해 입력을 7x7x256 크기 텐서로 변환
    model.add(Dense(256 * 7 * 7, input_dim=z_dim))
    model.add(Reshape((7, 7, 256)))

    # 7x7x256에서 14x14x128 텐서로 바꾸는 전치 합성곱 층
    model.add(Conv2DTranspose(128, kernel_size=3, strides=2, padding='same'))

    # 배치 정규화
    model.add(BatchNormalization())

    # LeakyReLU 활성화 함수
    model.add(LeakyReLU(alpha=0.01))

    # 14x14x128에서 14x14x64 텐서로 바꾸는 전치 합성곱 층
    model.add(Conv2DTranspose(64, kernel_size=3, strides=1, padding='same'))

    # 배치 정규화
    model.add(BatchNormalization())

    # LeakyReLU 활성화 함수
    model.add(LeakyReLU(alpha=0.01))

    # 14x14x64에서 28x28x1 텐서로 바꾸는 전치 합성곱 층
    model.add(Conv2DTranspose(1, kernel_size=3, strides=2, padding='same'))

    # tanh 활성화 함수를 사용한 출력층
    model.add(Activation('tanh'))

    return model

 

  판별자는 이미지를 받아 예측 벡터를 출력하는 합성곱 신경망과 비슷한 ConvNet을 사용한다. 이 경우, 이진 분류기는 입력 이미지가 진짜인지 가짜인지를 나타낸다.

 

  합성곱 층과 LeakyReLU 활성화 함수 층을 번갈아 가며 적용시키고 마지막에 시그모이드 활성화 함수를 사용하여 확률값을 나타내도록 한다.

# 판별자
def build_discriminator(img_shape):

    model = Sequential()

    # 28x28x1에서 14x14x32 텐서로 바꾸는 합성곱 층
    model.add(
        Conv2D(32,
               kernel_size=3,
               strides=2,
               input_shape=img_shape,
               padding='same'))

    # LeakyReLU 활성화 함수
    model.add(LeakyReLU(alpha=0.01))

    # 14x14x32에서 7x7x64 텐서로 바꾸는 합성곱 층
    model.add(
        Conv2D(64,
               kernel_size=3,
               strides=2,
               padding='same'))

    # LeakyReLU 활성화 함수
    model.add(LeakyReLU(alpha=0.01))

    # 7x7x64에서 3x3x128 텐서로 바꾸는 합성곱 층
    model.add(
        Conv2D(128,
               kernel_size=3,
               strides=2,
               padding='same'))

    # LeakyReLU 활성화 함수
    model.add(LeakyReLU(alpha=0.01))
    
    # sigmoid 활성화 함수를 사용한 출력층
    model.add(Flatten())
    model.add(Dense(1, activation='sigmoid'))

    return model

 

  이제 생성자와 판별자를 위해 사용한 네트워크 구조를 제외하고 나머지 DCGAN 신경망의 설정과 구현은 3장에서 GAN을 생성할 때 만든 구조와 동일하다.

# 모델 생성하기
def build_gan(generator, discriminator):

    model = Sequential()

    # 생성자 -> 판별자로 연결된 모델
    model.add(generator)
    model.add(discriminator)

    return model
# 판별자 모델을 만들고 컴파일하기
discriminator = build_discriminator(img_shape)
discriminator.compile(loss='binary_crossentropy',
                      optimizer=Adam(),
                      metrics=['accuracy'])

# 생성자 모델 만들기
generator = build_generator(z_dim)

# 생성자를 훈련하는 동안 판별자의 파라미터를 유지
discriminator.trainable = False

# 생성자를 훈련하기 위해 동결된 판별자로 GAN 모델을 만들고 컴파일
gan = build_gan(generator, discriminator)
gan.compile(loss='binary_crossentropy', optimizer=Adam())
losses = []
accuracies = []
iteration_checkpoints = []


def train(iterations, batch_size, sample_interval):

    # MNIST 데이터셋 로드
    (X_train, _), (_, _) = mnist.load_data()

    # [0, 255] 흑백 픽셀 값을 [-1, 1] 사이로 스케일 조정
    X_train = X_train / 127.5 - 1.0
    X_train = np.expand_dims(X_train, axis=3)

    # 진짜 이미지 레이블: 모두 1
    real = np.ones((batch_size, 1))

    # 가짜 이미지 레이블: 모두 0
    fake = np.zeros((batch_size, 1))

    for iteration in range(iterations):

        # -------------------------
        #  판별자 훈련
        # -------------------------

        # 진짜 이미지에서 랜덤 배치 가져오기
        idx = np.random.randint(0, X_train.shape[0], batch_size)
        imgs = X_train[idx]

        # 가짜 이미지 배치 생성
        z = np.random.normal(0, 1, (batch_size, 100))
        gen_imgs = generator.predict(z)

        # 판별자 훈련
        d_loss_real = discriminator.train_on_batch(imgs, real)
        d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
        d_loss, accuracy = 0.5 * np.add(d_loss_real, d_loss_fake)

        # ---------------------
        #  생성자 훈련
        # ---------------------

        # 가짜 이미지 배치 생성
        z = np.random.normal(0, 1, (batch_size, 100))
        gen_imgs = generator.predict(z)

        # 생성자 훈련
        g_loss = gan.train_on_batch(z, real)

        if (iteration + 1) % sample_interval == 0:

            # 훈련이 끝난 후 그래프를 그리기 위해 손실과 정확도 저장
            losses.append((d_loss, g_loss))
            accuracies.append(100.0 * accuracy)
            iteration_checkpoints.append(iteration + 1)

            # 훈련 과정 출력
            print("%d [D 손실: %f, 정확도: %.2f%%] [G 손실: %f]" %
                  (iteration + 1, d_loss, 100.0 * accuracy, g_loss))

            # 생성된 이미지 샘플 출력
            sample_images(generator)

 

결과

  DCGAN을 사용하여 MNIST 데이터셋을 훈련한 결과, GAN을 사용하여 나타낸 결과보다 확연히 해상도가 더 좋아졌다. 진짜 데이터셋과 비교했을 때 차이가 거의 없을 정도로 거의 완벽한 글씨체를 출력하였다.

 

  GAN에 대해 학습하면서 3장까지만 해도 아직 컴퓨터와 사람을 구별할 수 있을 정도였지만 4장에서 DCGAN을 사용해보았는데 이건 사람이 한거라고 봐도 무방하다... 훈련 결과를 보면서 이런 기술들이 과연 이로운 점이 많을 지 해로운 점이 많을 지 처음으로 고민해보게 된 것 같다. 

반응형

'Book > GAN In Action' 카테고리의 다른 글

6장. ProGAN  (0) 2024.08.07
5장. GAN 평가의 어려움  (0) 2024.08.06
3장. GAN 구현하기  (0) 2024.07.30
2장. 오토인코더와 생성 학습  (0) 2024.07.29
1장. GAN과 생성 모델링  (1) 2024.07.24