인공지능/공부

TensorFlow - 생성형 인공지능 GAN

이게될까 2023. 12. 7. 11:08
728x90
728x90

여기에 작성하진 않았지만 인코딩 엔코딩으로 모델을 생성하는 것에서 발전된 모델이다.
생성하는 모델이 있고, 그 것을 판단하는 모델이 있다. 생성한 모델에서 나온 것은 판단하는 모델에서 진짜라고 만들어야 하고, 판단하는 모델은 생성된 것은 가짜(0)으로, 진짜 데이터는 진짜(1)로 판단해야한다. 이 과정을 토대로 학습한다.

이번엔 mnist를 활용하여 바로 colab, kaggle 등 사용하기 편한 곳에서 하면 된다.

import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, BatchNormalization, Activation, Reshape, Conv2DTranspose, Input, LeakyReLU, Conv2D, Flatten, Dropout
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
import numpy as np
import matplotlib.pyplot as plt
from IPython import display
(train_images, _), (_, _) = 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
BATCH_SIZE = 256
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(60000).batch(BATCH_SIZE)

데이터를 받아서 적당히 정리하는 과정이다. 저 데이터는 나중에 판단하는 모델에서 사용된다.

def make_generator_model():
  input = Input(shape = 100)
  x = Dense(7*7*256, use_bias=False)(input)
  x = BatchNormalization()(x)
  x = LeakyReLU()(x)
  x = Reshape((7, 7, 256))(x)
  x = Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False)(x)
  x = BatchNormalization()(x)
  x = LeakyReLU()(x)
  x = Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False)(x)
  x = BatchNormalization()(x)
  x = LeakyReLU()(x)
  output = Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh')(x)
  model = Model(inputs = input, outputs = output)
  return model

생성형 모델을 만들어 봤다. Conv2D는 크기를 줄였다면 Conv2DTranspose는 크기를 늘릴 때 사용한다.

위의 모델은 아래와 같이 진행한다.

noise = tf.random.normal([1,100])
generator_test = generator_model(noise, training = False)
plt.imshow(generator_test[0,:,:,0], cmap='gray')

위의 생성형 모델을 통해 랜덤한 값을 넣어보면 밑에와 같은 이미지가 나온다.

아직 학습되지 않아 아무렇게 나오지만 생성이 되는 것을 확인했다.

728x90
def make_discriminator_model():
  input = Input(shape = (28,28,1))
  x = Conv2D(64, (5, 5), strides=(2, 2), padding='same')(input)
  x = LeakyReLU()(x)
  x = Dropout(0.3)(x)
  x = Conv2D(128, (5, 5), strides=(2, 2), padding='same')(x)
  x = LeakyReLU()(x)
  x = Dropout(0.3)(x)
  x = Flatten()(x)
  output = Dense(1, activation = 'sigmoid')(x)
  model = Model(inputs = input, outputs = output)
  return model

위의 모델은 판단하는 네트워크이다. 마지막에 0(생성 데이터)과 1(실제 데이터)로 판단하게 된다.

네트워크의 진행은 아래와 같이 진행된다. 여기서 사용되는 Conv2D는 앞서 CNN에서 사용했던 것과 같다.

dis_value = discriminator_model(generator_test, training = False)
print(dis_value)

아까 생성했던 모델을 위의 모델로 판단하면 애매한 값이 나올 것이다. 이제 학습을 위해 각종 함수들을 만들어 본다.

cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=False)
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)
generator_optimizer = Adam(1e-4)
discriminator_optimizer = Adam(1e-4)

판별자(discriminator)는 실제 데이터를 1로 판별할 수록 loss는 0이고, 생성 데이터를 0으로 판별할 수록 loss는 0이다. 반대는 극대로 간다고 생각하면 된다. 
생성자(generator) 판별자가 생성데이터를 1으로 판별할 수록 loss는 0이다. 즉 실제라고 속이는 것이 생성자의 일이다.
옵티마이저도 정의해줬다.

EPOCHS = 50
noise_dim = 100
num_examples_to_generate = 16
seed = tf.random.normal([num_examples_to_generate, noise_dim])

@tf.function
def train_step(images):
  noise = tf.random.normal([BATCH_SIZE, noise_dim]) # 생성을 위한 랜덤 값
  with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape: #자동미분
    generated_images = generator_model(noise, training=True) # 모델 생성
    real_output = discriminator_model(images, training=True) # 실제 이미지에 대한 모델 판별
    fake_output = discriminator_model(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_model.trainable_variables) # 손실 값에 대한 모델 파라미터 자동 미분
  gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator_model.trainable_variables) # 손실 값에 대한 모델 파라미터 자동 미분
  generator_optimizer.apply_gradients(zip(gradients_of_generator, generator_model.trainable_variables)) #옵티마이저 적용
  discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator_model.trainable_variables))#옵티마이저 적용
  return gen_loss, disc_loss, real_output, fake_output

생성형 모델은 loss함수도 2개이기 때문에 fit만으로는 할 수 없다. 그래서 train을 정의해줬고, 앞에 @tf를 붙여줬다. 설명은 주석 형태로 달아놓겠다.

def generate_and_save_images(model, epoch, test_input):
  predictions = model(test_input, training=False)
  fig = plt.figure(figsize=(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.show()

매 학습마다 test를 할 수 있게 해주는 함수를 정의해준다.

def train(dataset, epochs):
  for epoch in range(epochs):
    for image_batch in dataset:
      gen_loss, disc_loss, real_output, fake_output = train_step(image_batch)
    display.clear_output(wait=True)
    generate_and_save_images(generator_model,epoch + 1,seed)
    print('epoch : ', epoch+1, ', generate loss : ', gen_loss.numpy(), ', discriminator loss : ', disc_loss.numpy())
    print('D(x):',np.mean(real_output.numpy()), ', D(g(x)):',np.mean(fake_output.numpy()))

마지막으로 train함수도 정의해준다. 이 것을 사용하여 우린 train을 통해 바로 학습이 가능하다.

train(train_dataset, EPOCHS)

이렇게 한줄만 쳐주면 바로 학습이 가능하고, epochs를 크게 주지 않아서 제대로 이미지가 나오진 않았으나 그래도 숫자의 틀은 따라갔다.

위와 같은 방식으로 사람얼굴에 한 데이터를 집어 넣으면 사람얼굴도 새롭게 생성 가능하다. 이것에 대한 활용은 나중에 다루어 보겟다.

728x90