Ssul's Blog
[Generative-AI] GAN(Generative Adversarial Networks-생성적 적대 신경망)이해 및 구현 본문
[Generative-AI] GAN(Generative Adversarial Networks-생성적 적대 신경망)이해 및 구현
Ssul 2024. 1. 5. 10:220. GAN 아이디어
- 이미지 생성자모델과 이미지 판별자 모델 두개를 만들어서, 서로 경쟁하듯 학습
- 생성자(Generator): 임의의 노이즈를 입력 받아 그럴듯한 이미지를 생성하는 기능 학습
- 판별자(Discriminator): 입력된 이미지가 실제 이미지인지, 생성자가 생성한 이미지인지 구분하는 기능 학습
- 생성자는 더 실제같은 이미지를 만드는 신경망을 학습하고,
- 판별자는 입력된 이미지가 진짜 이미지인지, 생성된 이미지인지 판별하는 신경망을 학습한다.
- 이렇게 둘이서 경쟁하듯 학습하면(생성기는 판별기를 속이려하고, 판별기는 생성기를 구분하려 함) 생성자는 정말 실사 같은 이미지를 만들어내게 될수 있다
- 물론 현실에서 판별자가 일찍 학습이 되어, 생성자가 그럴듯한 이미지 자체를 못만들어내는 경우가 많다. 그만큼 GAN학습이 어렵다고 한다.
1. GAN 코드로 구현하기
1-1. 데이터 셋팅
IMAGE_SIZE = 64
CHANNELS = 1
BATCH_SIZE = 128
Z_DIM = 100
EPOCHS = 100 # 훈련이 오래 걸려 에포크 횟수를 300에서 100으로 줄입니다.
LOAD_MODEL = False
ADAM_BETA_1 = 0.5
ADAM_BETA_2 = 0.999
LEARNING_RATE = 0.0002
NOISE_PARAM = 0.1
import gdown
gdown.download(id='1qd50QDZtr_NYFiFVdp0sIvGDwTT3mMEQ')
!unzip -q lego-brick-images.zip
# output 디렉토리를 만듭니다.
!mkdir output
train_data = utils.image_dataset_from_directory(
"/content/dataset/",
labels=None,
color_mode="grayscale",
image_size=(IMAGE_SIZE, IMAGE_SIZE),
batch_size=BATCH_SIZE,
shuffle=True,
seed=42,
# 이미지를 지정된 image_size로 조정할 때, bilinear알고리즘 사용
# 4개의 픽셀을 기반으로 새로운 픽셀 값을 계산
interpolation="bilinear",
)
def preprocess(img):
"""
이미지 정규화 및 크기 변경
"""
# -1과 1 사이의 범위로 변환
# tf.cast(img, "float32") 부분은 이미지의 데이터 타입을 float32로 변환
img = (tf.cast(img, "float32") - 127.5) / 127.5
return img
train = train_data.map(lambda x: preprocess(x))
train_sample = sample_batch(train)
display(train_sample)
- 레고블럭 이미지 데이터를 가져옵니다.
- 아래와 같은 이미지 입니다.
1-2. GAN모델 구성(Discriminator)
discriminator_input = layers.Input(shape=(IMAGE_SIZE, IMAGE_SIZE, CHANNELS))
x = layers.Conv2D(64, kernel_size=4, strides=2, padding="same", use_bias=False)(
discriminator_input
)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(
128, kernel_size=4, strides=2, padding="same", use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(
256, kernel_size=4, strides=2, padding="same", use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
x = layers.Conv2D(
512, kernel_size=4, strides=2, padding="same", use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Dropout(0.3)(x)
# (4,4,512) > (1,1,1)개로해서 sigmoid > 0 or 1
x = layers.Conv2D(
1,
kernel_size=4,
strides=1,
padding="valid",
use_bias=False,
activation="sigmoid",
)(x)
discriminator_output = layers.Flatten()(x)
discriminator = models.Model(discriminator_input, discriminator_output)
discriminator.summary()
- 이미지를 입력 받는다.
- 이미지를 CNN신경망을 통과시켜, 최종적으로 1개의 노드로 모음. 이를 sigmoid를 통과시켜 0 또는 1값을 가지게 함
- 0이면 생성자가 생성한 가짜 이미지, 1이면 진짜 이미지
1-3. GAN모델 구성(Generator)
# 잠재공간의 차원 Z_DIM = 100차원
generator_input = layers.Input(shape=(Z_DIM,))
# (1,1,100)
x = layers.Reshape((1, 1, Z_DIM))(generator_input)
# (4,4,512)
x = layers.Conv2DTranspose(
512, kernel_size=4, strides=1, padding="valid", use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Conv2DTranspose(
256, kernel_size=4, strides=2, padding="same", use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Conv2DTranspose(
128, kernel_size=4, strides=2, padding="same", use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
x = layers.Conv2DTranspose(
64, kernel_size=4, strides=2, padding="same", use_bias=False
)(x)
x = layers.BatchNormalization(momentum=0.9)(x)
x = layers.LeakyReLU(0.2)(x)
generator_output = layers.Conv2DTranspose(
CHANNELS,
kernel_size=4,
strides=2,
padding="same",
use_bias=False,
activation="tanh",
)(x)
generator = models.Model(generator_input, generator_output)
generator.summary()
- 100차원의 임의의 벡터가 입력
- 임의의 이미지 벡터가 생성(output)하는 신경망 모델
1-4. GAN모델 구성(Generator + Disciriminator)
class DCGAN(models.Model):
def __init__(self, discriminator, generator, latent_dim):
super(DCGAN, self).__init__()
self.discriminator = discriminator
self.generator = generator
self.latent_dim = latent_dim
def compile(self, d_optimizer, g_optimizer):
super(DCGAN, self).compile()
self.loss_fn = losses.BinaryCrossentropy()
self.d_optimizer = d_optimizer
self.g_optimizer = g_optimizer
self.d_loss_metric = metrics.Mean(name="d_loss")
self.d_real_acc_metric = metrics.BinaryAccuracy(name="d_real_acc")
self.d_fake_acc_metric = metrics.BinaryAccuracy(name="d_fake_acc")
self.d_acc_metric = metrics.BinaryAccuracy(name="d_acc")
self.g_loss_metric = metrics.Mean(name="g_loss")
self.g_acc_metric = metrics.BinaryAccuracy(name="g_acc")
@property
def metrics(self):
return [
self.d_loss_metric,
self.d_real_acc_metric,
self.d_fake_acc_metric,
self.d_acc_metric,
self.g_loss_metric,
self.g_acc_metric,
]
def train_step(self, real_images):
# 잠재 공간에서 랜덤 포인트 샘플링
batch_size = tf.shape(real_images)[0]
# 생성자에 입력할 랜덤 latent 벡터 생성
random_latent_vectors = tf.random.normal(
shape=(batch_size, self.latent_dim)
)
# 가짜 이미지로 판별자 훈련하기
with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
# 가짜 이미지 생성
generated_images = self.generator(
random_latent_vectors, training=True
)
# 진짜 이미지에 대한 예측값
real_predictions = self.discriminator(real_images, training=True)
# 가짜 이미지에 대한 예측값
fake_predictions = self.discriminator(
generated_images, training=True
)
# 진짜 이미지에 대한 y값은 1로
real_labels = tf.ones_like(real_predictions)
real_noisy_labels = real_labels + NOISE_PARAM * tf.random.uniform(
tf.shape(real_predictions)
)
# 가짜 이미지에 대한 y값은 0으로
fake_labels = tf.zeros_like(fake_predictions)
fake_noisy_labels = fake_labels - NOISE_PARAM * tf.random.uniform(
tf.shape(fake_predictions)
)
# 구분자는 진짜 이미지도 잘 구분, 가짜 이미지도 잘 구분
d_real_loss = self.loss_fn(real_noisy_labels, real_predictions)
d_fake_loss = self.loss_fn(fake_noisy_labels, fake_predictions)
d_loss = (d_real_loss + d_fake_loss) / 2.0
# 생성자의 로스함수: 생성기가 생산한 이미지의 예측이 1에 가까워지게
# 최대한 진짜같이 만들기
g_loss = self.loss_fn(real_labels, fake_predictions)
# loss_fn, 파라미터 주고, 업데이트
gradients_of_discriminator = disc_tape.gradient(
d_loss, self.discriminator.trainable_variables
)
gradients_of_generator = gen_tape.gradient(
g_loss, self.generator.trainable_variables
)
self.d_optimizer.apply_gradients(
zip(gradients_of_discriminator, discriminator.trainable_variables)
)
self.g_optimizer.apply_gradients(
zip(gradients_of_generator, generator.trainable_variables)
)
# 메트릭 업데이트
self.d_loss_metric.update_state(d_loss)
self.d_real_acc_metric.update_state(real_labels, real_predictions)
self.d_fake_acc_metric.update_state(fake_labels, fake_predictions)
self.d_acc_metric.update_state(
[real_labels, fake_labels], [real_predictions, fake_predictions]
)
self.g_loss_metric.update_state(g_loss)
self.g_acc_metric.update_state(real_labels, fake_predictions)
return {m.name: m.result() for m in self.metrics}
# DCGAN 생성
dcgan = DCGAN(
discriminator=discriminator, generator=generator, latent_dim=Z_DIM
)
- random_latent_vectors = tf.random.normal(shape=(batch_size, self.latent_dim)) : 생성된 임의의 벡터. 해당 벡터를 generator에 넣어서 가짜 이미지를 생성하고, 해당 벡터의 label은 0으로.
- 진짜 이미지 넣었을때 discriminator의 예측값과 1로 loss_fun구성
- 가짜 이미지 넣었을때 discriminator의 예측값과 0으로 loss_fun구성
- 판별자의 두개의 loss함수의 평균 > 이 loss함수를 줄이는 방향이, 가짜/진짜를 잘 찾아내는 것
- 반대로 생성자는 진짜 이미지라벨과 가짜이미지의 예측값을 loss_fun로 하여, 이를 최소화 하는 방향으로 학습
1-5. GAN훈련
dcgan.compile(
d_optimizer=optimizers.Adam(
learning_rate=LEARNING_RATE, beta_1=ADAM_BETA_1, beta_2=ADAM_BETA_2
),
g_optimizer=optimizers.Adam(
learning_rate=LEARNING_RATE, beta_1=ADAM_BETA_1, beta_2=ADAM_BETA_2
),
)
# 모델 저장 체크포인트 만들기
model_checkpoint_callback = callbacks.ModelCheckpoint(
filepath="/content/checkpoint/checkpoint.ckpt",
save_weights_only=True,
save_freq="epoch",
verbose=0,
)
tensorboard_callback = callbacks.TensorBoard(log_dir="./logs")
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: # 출력 횟수를 줄이기 위해
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 * 127.5 + 127.5
generated_images = generated_images.numpy()
display(
generated_images,
save_to="/content/output/generated_img_%03d.png" % (epoch),
)
dcgan.fit(
train,
epochs=EPOCHS,
callbacks=[
model_checkpoint_callback,
tensorboard_callback,
ImageGenerator(num_img=10, latent_dim=Z_DIM),
],
)
- 학습 중간 중간에 generator가 생성한 이미지를 출력합니다.
- 완벽하지는 않아도, 학습이 진행됨에 따라, 약간은 형태를 갖춘 이미지를 생성하는 것을 볼수 있음
2. GAN정리
- GAN은 재미있는 원리이지만, 훈련이 어렵기로 유명함
- 판별자가 생성자보다 크게 뛰어나거나, 생성자가 판별자보다 크게 뛰어난 경우 학습이 안됨(손실이 유용하지 않음)
- 이런 학습의 안정성과 품질을 개선한 WGAN-GP, CGAN 등이 있다.
- 또한, 더 발전된 고급 GAN(ProGAN, SAGAN, ViT VQ-GAN 등)도 있다고 한다
'AI & ML > 개념잡기' 카테고리의 다른 글
Langchain Prompt template 정리 (0) | 2024.05.14 |
---|---|
LLM 모델 사이즈, 경량화/양자화(quantization) (1) | 2024.03.14 |
[Generative-AI] 오토인코더(AE)에서 VAE까지 (1) | 2024.01.04 |
[Generative-AI]GPT-Transformer 개념잡기 (0) | 2023.12.27 |
[Generative-AI] 디퓨전(확산) 모델 개념잡기 (0) | 2023.12.27 |