본문 바로가기
Personal Study/Model

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

by muns91 2024. 4. 20.
Wasserstein GAN (WGAN) 실습

 

 지난 글에서는 WGAN에 대한 간단한 이론을 살펴보았습니다. 그럼 이론을 살펴보았으니 이제는 코드를 보면서 구현 실습을 하도록 하겠습니다. 코드는 글 맨 하단에서 확인하실 수 있는 책과 블로그 그리고 구현된 코드를 참고하였습니다. 실습을 원하시는 분들은 해당 사이트 및 제가 실습한 자료가 있는 깃허브를 참고하시길 바랍니다. 

 

 

만들면서 배우는 생성 AI

딥러닝 기초부터 최신 생성 AI 모델까지 설명합니다. 텐서플로와 케라스를 사용해 변이형 오토인코더(VAE), 생성적 적대 신경망(GAN), 트랜스포머, 노멀라이징 플로 모델, 에너지 기반 모델, 잡음

www.aladin.co.kr

 


 

라이브러리 & 모듈 import

import numpy as np
import tensorflow as tf
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import os
from tensorflow.keras import (
    layers,
    models,
    callbacks,
    metrics,
    optimizers,
    utils,
)

 

 해당 코드에서는 실습에 필요한 라이브러리와 모듈을 import 합니다. 특이 사항으로는 utils라는 모듈이 있는 데 이 모듈의 역할은 다음과 같습니다. 

 

  • sample_batch 함수
    • 샘플링 된 배치 데이터를 넘파이 배열로 변환하여 반환
  • display 함수
    • 이미지 배열을 시각화하고 필요에 따라 이미지를 저장하는 함수
def sample_batch(dataset):
    batch = dataset.take(1).get_single_element()
    if isinstance(batch, tuple):
        batch = batch[0]
    return batch.numpy()


def display(images, save_to=None, cmap='gray'):
    """
    Displays and saves images using matplotlib. Assumes images are in [0, 1] range.
    """
    n = images.shape[0]
    plt.figure(figsize=(20, 3))
    for i in range(n):
        plt.subplot(1, n, i + 1)
        plt.imshow(images[i, :, :, 0], cmap=cmap)  # Select the first channel for grayscale display
        plt.axis("off")
    if save_to:
        plt.savefig(save_to)
        print(f"Saved to {save_to}")
    plt.close()  # Close the figure to avoid display

 


파라미터

# 환경 설정
IMAGE_SIZE = 28  # MNIST 기본 이미지 크기
CHANNELS = 1  # 흑백 이미지
BATCH_SIZE = 128
Z_DIM = 256
LEARNING_RATE = 0.0002
ADAM_BETA_1 = 0.5
ADAM_BETA_2 = 0.999
EPOCHS = 200
CRITIC_STEPS = 3
GP_WEIGHT = 10.0
LOAD_MODEL = False

 

데이터

from tensorflow.keras.datasets import mnist

def load_mnist_data():
    (x_train, _), (_, _) = mnist.load_data()
    # 이미지를 [-1, 1] 범위로 정규화
    x_train = (x_train.astype(np.float32) - 127.5) / 127.5
    # 채널 차원 추가
    x_train = np.expand_dims(x_train, axis=-1)
    # 데이터셋 구성
    train_data = tf.data.Dataset.from_tensor_slices(x_train)
    train_data = train_data.shuffle(buffer_size=1024).batch(BATCH_SIZE)
    return train_data

train = load_mnist_data()

 

 데이터는 이전 DGCAN과 마찬가지로 MNIST 데이터를 사용하였습니다. 이전에 GAN과 DCGAN에서는 파라미터 설정 또는 모델 구조를 조금만 잘못 짜도 모드 붕괴 혹은 학습을 아예 못해버리는 문제점이 발견되었습니다. 하지만 이번 WGAN 모델에서는 조금 달라지는 것을 확인하기 위해서 같은 데이터 세트로 실습을 수행해보았습니다. 

 

비평자 (GAN, DCGAN = 판별자) *

critic_input = layers.Input(shape=(IMAGE_SIZE, IMAGE_SIZE, CHANNELS))
x = layers.Conv2D(64, kernel_size=4, strides=2, padding="same")(critic_input)
x = layers.LeakyReLU(0.2)(x)
x = layers.Conv2D(128, kernel_size=4, strides=2, padding="same")(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(1, kernel_size=7, strides=1, padding="valid")(x)
critic_output = layers.Flatten()(x)
critic = models.Model(critic_input, critic_output)
critic.summary()

 

 이전에 GAN과 DCGAN에서는 판별자의 마지막 출력으로 [0, 1]의 값을 출력하기 위해 이진 크로스 엔트로피와 시그모이드를 사용하였습니다. 하지만 WGAN에서는 이와 달리 Wasserstein Loss를 사용하여 시그모이드 함수를 제거함으로써 0과 1 대신에 [-∞, ∞ ] 범위의 어떤 숫자도 될 수 있도록합니다. 이러한 이유로 WGAN의 판별자는 비평자(Critic)라 부르고 확률 대신 점수를 반환하게 됩니다. 즉, 이미지가 얼마나 진짜인지를 수치적으로 평가하는 값을 계산하기 때문에 이 값은 범위에 제한이 없으며 비평가의 목적은 생성된 샘플과 진짜 샘플 사이의 Wasserstein 거리를 최소화하는 것입니다. 따라서 활성화 함수 대신에 직접적인 스코어를 출력합니다. 

 

생성자 *

from tensorflow.keras import layers, models


generator_input = layers.Input(shape=(Z_DIM,))
# Dense layer to upscale the input
x = layers.Dense(7 * 7 * 128, use_bias=False)(generator_input)  # 초기 특성 맵 설정 (7x7x256)
x = layers.BatchNormalization()(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Reshape((7, 7, 128))(x)  # 7x7 feature map

# Second Transposed Conv layer to increase dimensions to 28x28
x = layers.Conv2DTranspose(
    64, kernel_size=4, strides=2, padding="same", use_bias=False  # Adjusted kernel size
)(x)
x = layers.BatchNormalization()(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.UpSampling2D()(x)

# Final Transposed Conv layer to get to the desired channel depth
generator_output = layers.Conv2DTranspose(
    CHANNELS, kernel_size=4, strides=1, padding="same", activation="tanh"  # Adjusted to match final output size exactly
)(x)

generator = models.Model(generator_input, generator_output)
generator.summary()

 

 WGAN의 생성자 역시 다른 GAN과 마찬가지로 임의의 랜덤한 값을 입력으로 받습니다. 생성자의 구조는 이전 DCGAN과 거의 차이가 없습니다. 

 


WGAM-GP

class WGANGP(models.Model):
    def __init__(self, critic, generator, latent_dim, critic_steps, gp_weight):
        super(WGANGP, self).__init__()
        self.critic = critic
        self.generator = generator
        self.latent_dim = latent_dim
        self.critic_steps = critic_steps
        self.gp_weight = gp_weight

    def compile(self, c_optimizer, g_optimizer):
        super(WGANGP, self).compile()
        self.c_optimizer = c_optimizer
        self.g_optimizer = g_optimizer
        self.c_wass_loss_metric = metrics.Mean(name="c_wass_loss")
        self.c_gp_metric = metrics.Mean(name="c_gp")
        self.c_loss_metric = metrics.Mean(name="c_loss")
        self.g_loss_metric = metrics.Mean(name="g_loss")

    @property
    def metrics(self):
        return [
            self.c_loss_metric,
            self.c_wass_loss_metric,
            self.c_gp_metric,
            self.g_loss_metric,
        ]

    def gradient_penalty(self, batch_size, real_images, fake_images):
        alpha = tf.random.normal([batch_size, 1, 1, 1], 0.0, 1.0)
        diff = fake_images - real_images
        interpolated = real_images + alpha * diff

        with tf.GradientTape() as gp_tape:
            gp_tape.watch(interpolated)
            pred = self.critic(interpolated, training=True)

        grads = gp_tape.gradient(pred, [interpolated])[0]
        norm = tf.sqrt(tf.reduce_sum(tf.square(grads), axis=[1, 2, 3]))
        gp = tf.reduce_mean((norm - 1.0) ** 2)
        return gp

    def train_step(self, real_images):
        batch_size = tf.shape(real_images)[0]

        for i in range(self.critic_steps):
            random_latent_vectors = tf.random.normal(
                shape=(batch_size, self.latent_dim)
            )

            with tf.GradientTape() as tape:
                fake_images = self.generator(
                    random_latent_vectors, training=True
                )
                fake_predictions = self.critic(fake_images, training=True)
                real_predictions = self.critic(real_images, training=True)

                c_wass_loss = tf.reduce_mean(fake_predictions) - tf.reduce_mean(
                    real_predictions
                )
                c_gp = self.gradient_penalty(
                    batch_size, real_images, fake_images
                )
                c_loss = c_wass_loss + c_gp * self.gp_weight

            c_gradient = tape.gradient(c_loss, self.critic.trainable_variables)
            self.c_optimizer.apply_gradients(
                zip(c_gradient, self.critic.trainable_variables)
            )

        random_latent_vectors = tf.random.normal(
            shape=(batch_size, self.latent_dim)
        )
        with tf.GradientTape() as tape:
            fake_images = self.generator(random_latent_vectors, training=True)
            fake_predictions = self.critic(fake_images, training=True)
            g_loss = -tf.reduce_mean(fake_predictions)

        gen_gradient = tape.gradient(g_loss, self.generator.trainable_variables)
        self.g_optimizer.apply_gradients(
            zip(gen_gradient, self.generator.trainable_variables)
        )

        self.c_loss_metric.update_state(c_loss)
        self.c_wass_loss_metric.update_state(c_wass_loss)
        self.c_gp_metric.update_state(c_gp)
        self.g_loss_metric.update_state(g_loss)

        return {m.name: m.result() for m in self.metrics}

 

 WGAN에서는 Wasserstein Loss외에도 그레디언트 패널티(Gradient Penalty)를 통한 립시츠(Lipschitz) 제약을 사용하게 됩니다. 립시츠 제약은 WGAN-GP(Wasserstein GAN with Gradient Penalty) 모델에서 비평가의 함수가 립시츠 연속성을 충족하도록 보장하는 중요한 요소입니다. 이 제약은 비평가의 그레이언트가 제어 가능한 범위 내에 있어야 함을 의미하며, 이는 비평가 함수가 매우 급격하게 변하지 않도록 제한하는 역할을 합니다.  그럼 위 코드를 나누어서 각각의 역할을 살펴보도록 하겠습니다. 

 

1) 클래스 초기화

def __init__(self, critic, generator, latent_dim, critic_steps, gp_weight):
    super(WGANGP, self).__init__()
    self.critic = critic
    self.generator = generator
    self.latent_dim = latent_dim
    self.critic_steps = critic_steps
    self.gp_weight = gp_weight

 

 클래스에서 각각의 역할은 아래와 같습니다. 

  • critic : 비평가 모델로 생성된 이미지와 실제 이미지를 평가합니다.
  • generator : 생성자 모델로 노이즈 벡터를 받아 이미지를 생성합니다.
  • latent_dim : 노이즈 벡터의 차원입니다.
  • critic_steps : 각 훈련 단계에서 비평가를 몇 번 업데이트할 지 결정합니다.
  • gp_weight : 그레디언트 패널티의 가중치로 비평가의 그레디언트를 얼마나 강하게 제한할지 결정합니다. 

 

2) 컴파일 메소드

def compile(self, c_optimizer, g_optimizer):
    super(WGANGP, self).compile()
    self.c_optimizer = c_optimizer
    self.g_optimizer = g_optimizer
    self.c_wass_loss_metric = metrics.Mean(name="c_wass_loss")
    self.c_gp_metric = metrics.Mean(name="c_gp")
    self.c_loss_metric = metrics.Mean(name="c_loss")
    self.g_loss_metric = metrics.Mean(name="g_loss")

 

  • c_optimizer, g_optimizer : 각각 비평가와 생성자의 최적화를 담당하는 옵티마이저입니다.
  • 여러 손실 값들을 추적하는 메트릭스를 설정합니다.

 

3) 그레디언트 패널티를 통한 립시츠 제약의 적용

    def gradient_penalty(self, batch_size, real_images, fake_images):
        alpha = tf.random.normal([batch_size, 1, 1, 1], 0.0, 1.0)
        diff = fake_images - real_images
        interpolated = real_images + alpha * diff

        with tf.GradientTape() as gp_tape:
            gp_tape.watch(interpolated)
            pred = self.critic(interpolated, training=True)

        grads = gp_tape.gradient(pred, [interpolated])[0]
        norm = tf.sqrt(tf.reduce_sum(tf.square(grads), axis=[1, 2, 3]))
        gp = tf.reduce_mean((norm - 1.0) ** 2)
        return gp

 

 립시츠 제약은 특히 'gradient_penalty' 메서드에서 직접적으로 적용됩니다. 이 메서드는 다음과 같은 과정을 통해 그레디언트 패널티를 계산합니다. 

  1. interpolated 이미지 생성 : 실제 이미지와 가짜 이미지 사이의 무작위 위치에서 이미지를 생성합니다. 이는 실제 데이터와 생성된 데이터의 연속적인 공간을 탐색하기 위해서 입니다. 
  2. 그레디언트 계산 : tf_GradientTape()를 사용하여 interpolated 이미지에 대한 비평가의 출력의 그래디언트를 계산합니다. 이 그레디언트는 비평가 함수가 얼마나 급격하게 변화하는지를 나타냅니다. 
  3. 그레디언트의 노름 계산 : 계산된 그레디언트의 유클리드 노름(Norm)을 구합니다. 이는 그레디언트의 크기를 측정하는 것입니다.
    • 여기서 유클리드 노름은 벡터의 길이나 크기를 측정하는 방법 중 하나입니다. 다차원 공간에서 어떤 점의 원점으로부터의 거리를 계산할 때, 주로 사용됩니다. 벡터의 각 요소를 제곱하여 모두 더한 뒤, 그 합의 제곱근을 구하는 방식으로 나타냅니다. 
    • WGAN-GP 모델에서 이를 계산하는 이유는 비평가의 함수가 립시츠 연속 조건을 충족하도록 하기 위함입니다. 립시츠 연속 조건이란 함수의 그레디언트가 특정 상수 이하로 유지되어야 한다는 규칙을 말합니다. 즉 비평가의 출력이 입력에 대해 너무 급격하게 변하지 않도록 제한하는 것입니다. 
  4. 그레디언트 패널티 적용 : 노름이 1에서 벗어난 정도에 대해 제곱을 하여 평균을 내고 패널티를 적용합니다. 이 패널티는 비평가의 학습 과정에서 그래디언트가 1을 초과하지 않도록 강제하여 립시츠 연속성을 유지합니다. 

 


 

4) 훈련 단계

 def train_step(self, real_images):
        batch_size = tf.shape(real_images)[0]

        for i in range(self.critic_steps):
            random_latent_vectors = tf.random.normal(
                shape=(batch_size, self.latent_dim)
            )

            with tf.GradientTape() as tape:
                fake_images = self.generator(
                    random_latent_vectors, training=True
                )
                fake_predictions = self.critic(fake_images, training=True)
                real_predictions = self.critic(real_images, training=True)

                c_wass_loss = tf.reduce_mean(fake_predictions) - tf.reduce_mean(
                    real_predictions
                )
                c_gp = self.gradient_penalty(
                    batch_size, real_images, fake_images
                )
                c_loss = c_wass_loss + c_gp * self.gp_weight

            c_gradient = tape.gradient(c_loss, self.critic.trainable_variables)
            self.c_optimizer.apply_gradients(
                zip(c_gradient, self.critic.trainable_variables)
            )

        random_latent_vectors = tf.random.normal(
            shape=(batch_size, self.latent_dim)
        )
        with tf.GradientTape() as tape:
            fake_images = self.generator(random_latent_vectors, training=True)
            fake_predictions = self.critic(fake_images, training=True)
            g_loss = -tf.reduce_mean(fake_predictions)

        gen_gradient = tape.gradient(g_loss, self.generator.trainable_variables)
        self.g_optimizer.apply_gradients(
            zip(gen_gradient, self.generator.trainable_variables)
        )

        self.c_loss_metric.update_state(c_loss)
        self.c_wass_loss_metric.update_state(c_wass_loss)
        self.c_gp_metric.update_state(c_gp)
        self.g_loss_metric.update_state(g_loss)

        return {m.name: m.result() for m in self.metrics}

 

 훈련 단계도 각각의 코드를 나누어서 살펴보도록 하겠습니다. 

 

 

4-1) 비평가 업데이트

for i in range(self.critic_steps):
            random_latent_vectors = tf.random.normal(
                shape=(batch_size, self.latent_dim)
            )

            with tf.GradientTape() as tape:
                fake_images = self.generator(
                    random_latent_vectors, training=True
                )
                fake_predictions = self.critic(fake_images, training=True)
                real_predictions = self.critic(real_images, training=True)

                c_wass_loss = tf.reduce_mean(fake_predictions) - tf.reduce_mean(
                    real_predictions
                )
                c_gp = self.gradient_penalty(
                    batch_size, real_images, fake_images
                )
                c_loss = c_wass_loss + c_gp * self.gp_weight

            c_gradient = tape.gradient(c_loss, self.critic.trainable_variables)
            self.c_optimizer.apply_gradients(
                zip(c_gradient, self.critic.trainable_variables)
            )

        random_latent_vectors = tf.random.normal(
            shape=(batch_size, self.latent_dim)
        )

 

  • self.critic_steps 동안 반복하여 비평가를 업데이트 합니다. 이는 비평가가 더 자주 업데이트 되어 더 정확한 평가를 할 수 있도록 합니다. 
  • random_latent_vectors은 정규 분포에서 뽑은 임의의 노이즈 벡터로 생성자에 입력됩니다.

 

4-2) 그레디언트 테이프 사용

 with tf.GradientTape() as tape:
            fake_images = self.generator(random_latent_vectors, training=True)
            fake_predictions = self.critic(fake_images, training=True)
            g_loss = -tf.reduce_mean(fake_predictions)

        gen_gradient = tape.gradient(g_loss, self.generator.trainable_variables)
        self.g_optimizer.apply_gradients(
            zip(gen_gradient, self.generator.trainable_variables)
        )

 

  •  tf.GradientTape는 미분을 위한 컨테스트를 제공합니다. 이를 통해 자동으로 그레디언트를 계산할 수 있습니다. 
  • fake_images는 생성자에서 생성된 가짜 이미지입니다. 
  • fake_predictions와 real_predictions는 각각 가짜이미지와 실제 이미지에 대한 비평가의 예측값입니다.

 

4-3) Wasserstein 손실과 그레디언트 패널티 계산

c_wass_loss = tf.reduce_mean(fake_predictions) - tf.reduce_mean(real_predictions)
c_gp = self.gradient_penalty(batch_size, real_images, fake_images)
c_loss = c_wass_loss + c_gp * self.gp_weight

 

  •  c_wass_loss는 Wasserstein 손실로 가짜 이미지의 평균 예측값에서 실제 이미지의 평균 예측값을 뺀 갑입니다. 이는 비평가가 가짜와 실제를 얼마나 잘 구분하는지 평가합니다. 
  • c_gp는 그레디언트 패널티로 비평가가 립시츠 연속성을 유지하도록 합니다. 
  • c_loss는 최종 비평가 손실로 Wasserstein 손실과 그레디언트 패널티의 가중 합니다. 

 

4-4) 생성자 업데이트

random_latent_vectors = tf.random.normal(shape=(batch_size, self.latent_dim))
with tf.GradientTape() as tape:
    fake_images = self.generator(random_latent_vectors, training=True)
    fake_predictions = self.critic(fake_images, training=True)
    g_loss = -tf.reduce_mean(fake_predictions)
  • 생성자는 가짜 이미지의 품질을 높이기 위해 업데이트 됩니다.
  • g_loss는 생성된 이미지를 실제로 간주하도록 비평가를 속이는 것을 목표로 합니다. 비평가가 높은 값을 할당할수록 생성자의 손실은 감소합니다. 

 

4-5) 그레디언트 적용 및 메트릭 업데이트

        self.c_loss_metric.update_state(c_loss)
        self.c_wass_loss_metric.update_state(c_wass_loss)
        self.c_gp_metric.update_state(c_gp)
        self.g_loss_metric.update_state(g_loss)

 

  • 각 네트워크의 그레디언트를 계산하고 적용하여 학습을 진행합니다.
  • 손실과 메트릭을 업데이트하여 훈련과정에서의 성능을 추적합니다. 

 


 

GAN 만들기

# 모델 인스턴스 생성
wgangp = WGANGP(
    critic=critic,
    generator=generator,
    latent_dim=Z_DIM,
    critic_steps=CRITIC_STEPS,
    gp_weight=GP_WEIGHT,
)

# 출력 디렉터리 생성
if not os.path.exists('output'):
    os.makedirs('output')

# 콜백 정의
class ImageGenerator(callbacks.Callback):
    def __init__(self, num_img, latent_dim):
        self.num_img = num_img
        self.latent_dim = latent_dim

    def on_epoch_end(self, epoch, logs=None):
        if epoch % 10 != 0:  # 출력 횟수를 줄이기 위해
            return
        random_latent_vectors = tf.random.normal(shape=(self.num_img, self.latent_dim))
        generated_images = self.model.generator(random_latent_vectors)
        generated_images = generated_images * 0.5 + 0.5  # Normalize images to [0, 1]
        generated_images = generated_images.numpy()
        display(
            generated_images,
            save_to=f"./output/generated_img_{epoch:03d}.png",
            cmap='gray'
        )

# 모델 저장 체크포인트 및 텐서보드 콜백 설정
model_checkpoint_callback = callbacks.ModelCheckpoint(
    filepath="./checkpoint/checkpoint.ckpt",
    save_weights_only=True,
    save_freq="epoch",
    verbose=0
)

tensorboard_callback = callbacks.TensorBoard(log_dir="./logs")

 

  1.  모델 인스턴스 생성
  2. 출력 디렉터리 생성
  3. 이미지 생성 콜백 정의
  4. 체크 포인트 및 텐서보드 콜백 설정

 

학 습

# 훈련 시작
wgangp.fit(
    train,
    epochs=EPOCHS,  # MNIST 데이터셋 크기에 맞추어 조정
    callbacks=[model_checkpoint_callback, tensorboard_callback, ImageGenerator(num_img=10, latent_dim=Z_DIM)],
)

 

 

데이터 확인

# 이미지 비교 함수
def load_and_compare_images(epoch, example_index=0):
    (X_train, _), (_, _) = mnist.load_data()
    X_train = (X_train.astype('float32') - 127.5) / 127.5  # Normalize the images

    image_path = f"./output/generated_img_{epoch}.png"
    if not os.path.exists(image_path):
        print("해당 경로에 이미지 파일이 없습니다:", image_path)
        return

    generated_image = mpimg.imread(image_path)
    original_image = X_train[epoch].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, cmap='gray')
    axs[1].set_title('Generated Image')
    axs[1].axis('off')

    plt.show()

# 예시로 40번째 에포크의 이미지 비교
load_and_compare_images(10, example_index=1)

 

 여기까지 라이브러리 import부터 데이터 확인까지 살펴보았습니다. 데이터의 결과를 살펴본 결과 Epoch가 증가할 수록 생성된 글씨 이미지가 또렷해지는 것을 확인하실 수 있습니다.

 

Epoch = 10

 

Epoch = 50
Epoch = 100

 

Epoch = 150


마무리 

 여기까지 WGAN에 대한 실습 코드를 살펴보았습니다. GAN에서 DCGAN 그리고 WGAN으로 변화되면서 보다 안정적인 학습과 더 나은 수렴 성질을 제공할 수 있었습니다. 하지만 여전히 WGAN에서는 계산의 복잡성으로 인한 학습 시간이 길어진다는 점, 복잡한 하이퍼 파라미터에 대한 조정 등의 문제는 여전히 존재합니다. 그래서 WGAN 이후에도 더 많은 모델들이 만들어지고 발전해오고 있습니다. 다음 글에서는 GAN의 또 다른 발전 모델인 조건부 GAN (Conditional GAN)에 대해 이론을 살펴보고 그 이후 실습을 진행하도록 하겠습니다. 그럼 여기까지 WGAN에 대한 실습 글이었습니다. 

 

 

참고

1. 논문 : https://arxiv.org/pdf/1701.07875.pdf

2. 만들면서 배우는 생성 AI : https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=324278784

 

만들면서 배우는 생성 AI

딥러닝 기초부터 최신 생성 AI 모델까지 설명합니다. 텐서플로와 케라스를 사용해 변이형 오토인코더(VAE), 생성적 적대 신경망(GAN), 트랜스포머, 노멀라이징 플로 모델, 에너지 기반 모델, 잡음

www.aladin.co.kr