8-6. 이미지 생성 실습: GAN 모델 구현 및 학습
1. 이미지 생성의 새로운 지평: 생성적 적대 신경망 (GAN)
이미지 생성은 컴퓨터 비전 분야에서 오랫동안 탐구되어 온 흥미로운 주제입니다. 과거에는 픽셀 단위로 이미지를 생성하는 방식이 주를 이루었지만, 생성 모델의 발전은 이미지 생성의 패러다임을 완전히 바꾸어 놓았습니다. 오토인코더(Autoencoder)와 변이형 오토인코더(Variational Autoencoder, VAE)가 등장하며 생성 모델의 가능성을 열었지만, GAN은 더욱 혁신적인 접근 방식을 제시하며 실제와 구별하기 어려운 이미지를 생성하는 수준에 도달했습니다.
생성적 적대 신경망(Generative Adversarial Network, GAN)은 "생성자(Generator)"와 "판별자(Discriminator)"라는 두 개의 신경망을 경쟁적으로 학습시키는 방식으로 작동합니다. 생성자는 실제와 유사한 이미지를 생성하려고 노력하고, 판별자는 생성된 이미지와 실제 이미지를 구별하는 능력을 키웁니다. 이러한 경쟁적 학습 과정을 통해 생성자는 점차 실제 이미지와 가까운 이미지를 생성하게 됩니다. 마치 위조지폐범과 이를 감별하는 수사관의 관계와 유사합니다.
2. GAN의 핵심 원리: 생성자와 판별자의 적대적 경쟁
GAN의 핵심은 생성자와 판별자 사이의 적대적인 경쟁 관계입니다.
1) 생성자 (Generator)
생성자는 입력으로 무작위 노이즈(random noise)를 받아 실제 이미지와 유사한 이미지를 생성합니다. 이 노이즈는 잠재 공간(latent space)에서 샘플링된 것으로, 생성되는 이미지의 특징을 결정하는 일종의 시드(seed) 역할을 합니다. 생성자는 입력된 노이즈를 점차적으로 처리하여 최종적으로 이미지를 생성합니다.
2) 판별자 (Discriminator)
판별자는 입력된 이미지가 실제 이미지인지, 아니면 생성자가 생성한 이미지인지 판별합니다. 판별자는 이진 분류기(binary classifier)와 유사하게 작동하며, 입력된 이미지가 실제 이미지일 확률을 출력합니다.
3) 적대적 학습 (Adversarial Training)
GAN은 생성자와 판별자를 번갈아 학습시키는 방식으로 훈련됩니다.
- 판별자 학습: 먼저, 실제 이미지와 생성된 이미지를 판별자에게 입력하여 판별자를 학습시킵니다. 판별자는 실제 이미지는 '1'로, 생성된 이미지는 '0'으로 분류하도록 학습됩니다.
- 생성자 학습: 다음으로, 생성자를 학습시킵니다. 이 과정에서 생성자는 판별자를 속이도록 훈련됩니다. 즉, 생성된 이미지를 판별자가 실제 이미지로 인식하도록 생성하는 방향으로 학습됩니다.
이러한 과정을 반복하면서 생성자는 점점 더 실제와 유사한 이미지를 생성하게 되고, 판별자는 더욱 정교하게 가짜 이미지를 구분하게 됩니다.

위 그림은 GAN의 기본적인 구조를 나타냅니다. 생성자는 노이즈를 입력받아 이미지를 생성하고, 판별자는 실제 이미지와 생성된 이미지를 구별합니다. 두 네트워크는 서로 경쟁하며 학습을 진행합니다.
3. GAN의 수학적 표현: 손실 함수
GAN의 학습 과정은 손실 함수(loss function)를 최소화하는 것을 목표로 합니다.
1) 판별자의 손실 함수
판별자의 목표는 실제 이미지와 생성된 이미지를 정확하게 구별하는 것입니다. 따라서 판별자의 손실 함수는 다음과 같이 정의됩니다.
$$ L_D = \mathbb{E}_{x \sim p_{data}(x)}[\log D(x)] + \mathbb{E}_{z \sim p_{z}(z)}[\log (1 - D(G(z)))] $$
여기서,
- $x$는 실제 이미지,
- $z$는 입력 노이즈,
- $D(x)$는 실제 이미지를 판별자가 실제 이미지라고 판단할 확률,
- $G(z)$는 생성자가 생성한 이미지,
- $D(G(z))$는 생성된 이미지를 판별자가 실제 이미지라고 판단할 확률입니다.
- $p_{data}(x)$는 실제 이미지의 분포,
- $p_{z}(z)$는 입력 노이즈의 분포,
- $\mathbb{E}$는 기댓값을 나타냅니다.
판별자는 $L_D$를 최소화하도록 학습됩니다. 즉, 실제 이미지를 실제 이미지로, 생성된 이미지를 가짜 이미지로 정확하게 판별하도록 학습됩니다.
2) 생성자의 손실 함수
생성자의 목표는 판별자를 속여 실제 이미지처럼 보이도록 이미지를 생성하는 것입니다. 따라서 생성자의 손실 함수는 다음과 같이 정의됩니다.
$$ L_G = \mathbb{E}_{z \sim p_{z}(z)}[\log (1 - D(G(z)))] $$
생성자는 $L_G$를 최소화하도록 학습됩니다. 즉, 생성된 이미지를 판별자가 실제 이미지로 판단하도록 학습됩니다.
또는, 판별자를 속이는 대신 판별기가 생성된 이미지를 실제 이미지로 판단하도록 유도하는 방식으로 손실 함수를 정의할 수 있습니다. 이 경우, 생성자의 손실 함수는 다음과 같이 정의됩니다.
$$ L_G = \mathbb{E}_{z \sim p_{z}(z)}[-\log D(G(z))] $$
이 두 가지 형태의 손실 함수는 학습의 안정성과 성능에 영향을 미칠 수 있습니다.
3) 전체 손실 함수
GAN은 두 개의 네트워크를 번갈아 학습시키므로, 전체 손실 함수는 다음과 같이 표현됩니다.
$$ \min_G \max_D L(D, G) = \mathbb{E}_{x \sim p_{data}(x)}[\log D(x)] + \mathbb{E}_{z \sim p_{z}(z)}[\log (1 - D(G(z)))] $$
4. GAN 구현 실습: MNIST 데이터셋을 이용한 이미지 생성
이제 실제로 GAN을 구현하고 MNIST 데이터셋을 사용하여 이미지를 생성하는 실습을 진행해 보겠습니다.
1) 필요한 라이브러리 설치
pip install tensorflow
pip install matplotlib
TensorFlow와 Matplotlib 라이브러리를 설치합니다.
2) 데이터 로드 및 전처리
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
# MNIST 데이터셋 로드
(train_images, _), (_, _) = tf.keras.datasets.mnist.load_data()
# 이미지 정규화 및 차원 변경
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # -1 ~ 1 범위로 정규화
MNIST 데이터셋을 로드하고, 이미지 픽셀 값을 -1과 1 사이로 정규화합니다. 이는 활성 함수로 tanh를 사용할 때 더 나은 결과를 얻기 위함입니다.
3) 생성자 모델 구현
def build_generator():
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Reshape((7, 7, 256)))
assert model.output_shape == (None, 7, 7, 256) # Note: None is the batch size
model.add(tf.keras.layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))
assert model.output_shape == (None, 7, 7, 128)
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
assert model.output_shape == (None, 14, 14, 64)
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
assert model.output_shape == (None, 28, 28, 1)
return model
생성자는 입력 노이즈(100차원)를 받아 28x28 흑백 이미지를 생성합니다. Conv2DTranspose 레이어를 사용하여 이미지의 크기를 키워나갑니다. BatchNormalization과 LeakyReLU를 사용하여 학습을 안정화시키고, 마지막 Conv2DTranspose 레이어에서 tanh 활성화 함수를 사용하여 -1과 1 사이의 픽셀 값을 출력합니다.
4) 판별자 모델 구현
def build_discriminator():
model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same', input_shape=[28, 28, 1]))
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(1))
return model
판별자는 28x28 흑백 이미지를 입력받아 해당 이미지가 실제 이미지인지 가짜 이미지인지 판별합니다. Conv2D 레이어를 사용하여 이미지의 특징을 추출하고, Dropout을 사용하여 과적합을 방지합니다. 마지막 Dense 레이어는 1개의 유닛을 가지고, sigmoid 함수를 사용하여 0과 1 사이의 값을 출력하도록 구현할 수 있지만, 여기서는 sigmoid를 직접 사용하지 않고 모델의 출력값을 손실 함수에서 활용합니다.
5) 손실 함수 및 옵티마이저 정의
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True) # 판별자 출력에 sigmoid를 사용하지 않으므로 from_logits=True
generator_optimizer = tf.keras.optimizers.Adam(1e-4)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)
BinaryCrossentropy 손실 함수를 사용하고, Adam 옵티마이저를 정의합니다. from_logits=True는 판별자 모델의 출력에 sigmoid 함수를 적용하지 않았음을 나타냅니다.
6) 학습 루프 구현
def discriminator_loss(real_output, fake_output):
real_loss = cross_entropy(tf.ones_like(real_output), real_output)
fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
total_loss = real_loss + fake_loss
return total_loss
def generator_loss(fake_output):
return cross_entropy(tf.ones_like(fake_output), fake_output)
@tf.function
def train_step(images, noise):
with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
generated_images = generator(noise, training=True)
real_output = discriminator(images, training=True)
fake_output = discriminator(generated_images, training=True)
gen_loss = generator_loss(fake_output)
disc_loss = discriminator_loss(real_output, fake_output)
gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
train_step 함수는 각 학습 단계에서 수행되는 연산을 정의합니다. 생성자와 판별자를 학습시키기 위한 손실을 계산하고, 기울기를 계산하여 옵티마이저를 적용합니다. @tf.function 데코레이터를 사용하여 TensorFlow 그래프를 생성하여 학습 속도를 향상시킵니다.
7) 모델 학습 및 이미지 시각화
def generate_and_save_images(model, epoch, test_input):
predictions = model(test_input, training=False)
fig <mark class="highlight"><strong><u> plt.figure(figsize</u></strong></mark>(4, 4))
for i in range(predictions.shape[0]):
plt.subplot(4, 4, i+1)
plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
plt.axis('off')
plt.savefig('image_at_epoch_.png'.format(epoch))
plt.show()
epochs = 100
batch_size = 256
noise_dim = 100
num_examples_to_generate = 16
seed = tf.random.normal([num_examples_to_generate, noise_dim])
generator = build_generator()
discriminator = build_discriminator()
for epoch in range(epochs):
for image_batch in range(train_images.shape[0] // batch_size):
batch_images = train_images[image_batch*batch_size : (image_batch+1)*batch_size]
noise = tf.random.normal([batch_size, noise_dim])
train_step(batch_images, noise)
if (epoch + 1) % 10 == 0:
generate_and_save_images(generator, epoch + 1, seed)
MNIST 데이터셋을 사용하여 모델을 학습시킵니다. 매 에폭마다 생성된 이미지를 저장하고, 학습 과정을 시각화합니다.
8) 결과 확인
위 코드를 실행하면, 학습 과정을 거치면서 생성된 이미지가 image_at_epoch_xxxx.png 형식으로 저장됩니다. 학습이 진행됨에 따라 생성된 이미지의 품질이 점차적으로 향상되는 것을 확인할 수 있습니다. 처음에는 노이즈처럼 보이지만, 시간이 지남에 따라 숫자 형태를 갖추어가는 것을 관찰할 수 있습니다.
5. GAN의 응용 및 확장
GAN은 다양한 분야에서 활용될 수 있으며, 지속적으로 새로운 모델들이 개발되고 있습니다.
1) 이미지 생성
GAN은 이미지 생성 분야에서 가장 널리 사용됩니다. 다양한 이미지 데이터셋(예: CIFAR-10, ImageNet)을 사용하여 고품질의 이미지를 생성할 수 있습니다.
2) 이미지 변환
GAN은 이미지 스타일 변환, 이미지 초해상화, 이미지 채색 등 다양한 이미지 변환 작업에 활용될 수 있습니다.
3) 텍스트-이미지 생성
GAN은 텍스트 설명을 기반으로 이미지를 생성하는 데 사용될 수 있습니다. (e.g., "A red bird on a tree" -> 이미지)
4) 의료 영상 생성
GAN은 의료 영상(X-ray, MRI 등)을 생성하여 의료 진단 및 연구에 활용될 수 있습니다.
5) 다양한 GAN 모델
- Conditional GAN (CGAN): 조건부 생성 모델로, 특정 조건(예: 클래스 레이블)을 기반으로 이미지를 생성합니다.
- StyleGAN: 고품질 이미지 생성을 위한 모델로, 이미지의 스타일을 제어할 수 있습니다.
- CycleGAN: 비지도 학습 기반의 이미지 변환 모델로, 두 개의 데이터셋 간의 스타일을 변환합니다.
6. 주의사항 및 트러블슈팅
GAN을 학습시키는 것은 여러 가지 어려움이 따릅니다.
1) 모드 붕괴 (Mode Collapse)
생성자가 다양한 이미지를 생성하지 못하고, 제한된 종류의 이미지만 생성하는 현상입니다. 이를 해결하기 위해 다양한 기법(예: 미니배치 판별, 손실 함수 변경)을 사용할 수 있습니다.
2) 학습 불안정성
GAN의 학습은 종종 불안정하며, 학습이 발산하거나, 생성된 이미지의 품질이 좋지 않은 경우가 발생할 수 있습니다. 학습률, 배치 정규화, 기울기 클리핑 등 다양한 기법을 통해 학습을 안정화시킬 수 있습니다.
3) 평가의 어려움
GAN은 생성된 이미지의 품질을 객관적으로 평가하기 어렵습니다. Inception Score, Frechet Inception Distance (FID) 등과 같은 평가 지표를 사용할 수 있지만, 사람의 주관적인 평가도 중요합니다.
7. CIFAR-10 데이터셋을 이용한 GAN 실습
MNIST 데이터셋 외에도 CIFAR-10과 같은 더 복잡한 데이터셋을 사용하여 GAN을 학습할 수 있습니다. CIFAR-10은 32x32 컬러 이미지로 구성되어 있으며, 10개의 클래스(비행기, 자동차, 새, 고양이, 사슴, 개, 개구리, 말, 배, 트럭)를 포함합니다.
CIFAR-10 데이터셋을 사용하기 위해서는 생성자와 판별자 모델의 구조를 변경해야 합니다. 이미지의 크기가 32x32로 커지고, 컬러 이미지(RGB)를 사용하므로, 더 많은 파라미터를 가진 컨볼루션 레이어와 완전 연결 레이어를 사용해야 합니다.
# CIFAR-10 데이터셋 로드 및 전처리
(train_images, _), (_, _) = tf.keras.datasets.cifar10.load_data()
train_images = train_images.astype('float32')
train_images = (train_images - 127.5) / 127.5 # -1 ~ 1 범위로 정규화
def build_generator():
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(8*8*256, use_bias=False, input_shape=(100,)))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Reshape((8, 8, 256)))
assert model.output_shape == (None, 8, 8, 256)
model.add(tf.keras.layers.Conv2DTranspose(128, (5, 5), strides=(2, 2), padding='same', use_bias=False))
assert model.output_shape == (None, 16, 16, 128)
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
assert model.output_shape == (None, 32, 32, 64)
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Conv2DTranspose(3, (5, 5), strides=(1, 1), padding='same', use_bias=False, activation='tanh'))
assert model.output_shape == (None, 32, 32, 3)
return model
생성자는 CIFAR-10 이미지 크기에 맞게 조정되었습니다. 입력 노이즈(100차원)를 받아 32x32 컬러 이미지를 생성합니다.
def build_discriminator():
model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same', input_shape=[32, 32, 3]))
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(1))
return model
판별자 또한 CIFAR-10 이미지 크기에 맞게 조정되었습니다. 32x32 컬러 이미지를 입력받아 실제 이미지인지 가짜 이미지인지 판별합니다.
학습 루프는 MNIST 예제와 동일하게 구현할 수 있습니다. CIFAR-10 데이터셋을 사용하여 GAN을 학습시키면, 더욱 복잡하고 다양한 이미지를 생성할 수 있습니다. CIFAR-10 데이터셋을 사용한 GAN의 학습은 더 많은 계산 자원과 학습 시간을 필요로 합니다. GPU를 사용하여 학습 속도를 향상시킬 수 있습니다.
8. 결론
GAN은 이미지 생성 분야에서 혁신적인 발전을 이룬 모델입니다. 생성자와 판별자의 적대적 학습을 통해 실제와 구별하기 어려운 이미지를 생성할 수 있으며, 다양한 분야에서 활용될 수 있습니다. MNIST 데이터셋을 이용한 간단한 실습을 통해 GAN의 기본적인 동작 방식을 이해하고, CIFAR-10과 같은 더 복잡한 데이터셋을 사용하여 GAN의 성능을 향상시키는 방법을 익혔습니다. GAN의 학습은 어려움이 따르지만, 끊임없는 연구와 발전을 통해 이미지 생성 기술은 더욱 발전할 것입니다.

GAN은 앞으로도 이미지 생성 분야뿐만 아니라 다양한 분야에서 활용될 가능성이 무궁무진합니다. 지속적인 연구와 개발을 통해 GAN의 성능을 향상시키고, 더 다양한 문제를 해결할 수 있을 것입니다.
비슷한 글 추천
4-5. MNIST 손글씨 숫자 인식: PyTorch 실습
MNIST 손글씨 숫자 인식 문제를 PyTorch를 이용하여 해결하는 실습 과정을 상세히 설명합니다.
5-5. Image Classification 실습: CNN 모델 구현 및 학습
CNN 모델을 직접 구현하고, 이미지 분류 문제(CIFAR-10, ImageNet 일부)를 해결하는 실습 과정을 상세히 설명합니다.
14-1. Diffusion Models: 이미지 생성의 새로운 강자
Diffusion Models의 원리를 설명하고, 기존 GAN 기반 모델과 비교하여 장단점을 분석합니다.
8-5. StyleGAN: 고품질 이미지 생성 모델
StyleGAN의 특징과 구조를 설명하고, 이미지 생성 품질을 향상시키는 기술적 요소들을 분석합니다.
Comments (0)
No comments yet. Be the first to comment!
Please to write a comment.