[SSUDA] GAN에 대해 가볍게 알아보기
GAN을 간단히 설명하면 저번에 공부했던 오토인코더의 뒷부분만 뗀것과 유사합니다.
임의에 특성들로부터 이미지 등을 복구한 디코더를 얘기합니다. 다만 이 구조로는 원래 데이터를 모르기 때문에 학습이 불가능한데요.
Discriminator(구별자)를 사용해서 학습을 진행하게 됩니다.
자세히 설명하면 실제 데이터 내에서 뽑고, Generator(생성자)를 이용해서 유사 데이터를 뽑습니다.
그 뒤 Discriminator를 이용해서 주어진 데이터가 실제 데이터인지 Generator를 이용해서 만든 가짜 데이터인지 구분합니다.
이 과정을 반복해서 Generator가 생성하는 데이터를 실제 데이터와 유사하게 만들어서 Discriminator가 구분하지 못할 정도로 만드는 것이 목표입니다.
방금 말한 이론을 현실세계에 간단한 예시를 통해 관찰하겠습니다. 지폐위조범과 경찰의 관계입니다.
지폐위조범이 가짜돈을 찍어냅니다 -> 그럼 경찰이 이걸 보고 가짜인지 진짜인지 구별합니다.
이때 처음 찍어낸 돈은 품질이 안좋아서 경찰이 쉽게 가짜를 구별합니다.
하지만 지폐위조범은 점점 더 정교하게 가짜돈을 만들고, 경찰은 그걸 보고 계속 돈을 구분할 것입니다.
이 상황에서 경찰은 최대한 가짜 돈을 잘 구분하려고 노력하겠죠.
반복되면 경찰이 지폐위조범이 만든 돈을 구분 못하는 상황이 올겁니다. 가짜 돈을 잘 만드는 생성자를 만들어 낸 것입니다.
(참고자료 : https://lifeignite.tistory.com/53)
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt
import numpy as np
import time
(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()
train_images.shape
텐서플로 패키지를 다운하고, 텐서플로우 내 mnist 데이터를 불러옵니다.
테스트 데이터의 개수는 6만개이고 28 * 28 형태이며 값은 0~255 사이인 이미지 데이터 입니다.
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # 값이 0~255에 존재하는데, -1~1 사이 데이터로 변환해줍니다.
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1).astype('float32')
test_images = (test_images - 127.5) / 127.5
입력된 데이터를 형변환하고, 값을 -1 ~ 1 내에 존재하도록 전처리합니다.
BUFFER_SIZE = 60000
BATCH_SIZE = 128
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
텐서형태로 데이터를 변환해 넣어줍니다. 이때 데이터를 섞어주며, 배치사이즈로 분리합니다.
inputs = keras.Input(shape=(100,))
x = inputs
x = layers.Dense(256)(x)
x = layers.LeakyReLU()(x)
x = layers.Dense(28*28, activation = 'tanh')(x)
outputs = layers.Reshape((28, 28))(x)
G = keras.Model(inputs, outputs)
G.summary()
100차원의 값을 입력받아 이미지 데이터와 같은 형태인 28 * 28 형태로 출력하는 모델을 만들었습니다.
더 자세히 살펴보면 100개의 입력에서 256개의 특성으로, 또 다음 층을 지나면 784개의 특성을 만들어내는 모델입니다.
이 모델은 Generator(생성자) 모델로, 가짜 이미지를 만드는 모델입니다.
inputs = keras.Input(shape=(28, 28))
x = layers.Flatten()(inputs)
x = layers.Dense(256)(x)
x = layers.LeakyReLU()(x)
x = layers.Dropout(0.3)(x)
outputs = layers.Dense(1)(x)
D = keras.Model(inputs, outputs)
D.summary()
28 * 28 형태의 이미지 데이터를 입력받아 256 차원으로 바꾼뒤, 최종 1개의 값을 내보내는 모델입니다.
이미지를 입력받아 실제 이미지인지, 생성자에서 만든 이미지인지 구분하는 Discriminator(구별자) 입니다.
test_noise = tf.random.normal([1, 100])
fake_image_test = G(test_noise, training = False)
plt.imshow(fake_image_test[0], cmap = 'gray')
임의에 값 100개를 사용하여 생성자 G 모델을 통과시켜서 28 * 28 이미지를 만들었습니다.
랜덤 값으로 만든 이미지이기 때문에 아무런 특이점이 없는 형태이죠.
decision = D(fake_image_test, training = False)
print(decision)
방금 만든 이미지 데이터를 가지고 실제 데이터가 맞는지 판단하는 구별자 D 모델을 통과시킨 모습입니다.
아직 학습을 안시켰는데 우연인건지 0에 가까운 정확한 결과를 출력시켰네요.
EPOCHS = 50
noise_dim = 100
seed = tf.random.normal([BATCH_SIZE, noise_dim])
G_optimizer = tf.keras.optimizers.Adam(1e-4)
D_optimizer = tf.keras.optimizers.Adam(1e-4)
에포크는 50으로, 시드값도 넣어주고 옵티마이저는 생성자 구별자 모두 Adam을 사용했습니다.
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits = True)
def G_loss(fake_output):
return cross_entropy(tf.ones_like(fake_output), fake_output)
def D_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
크로스 엔트로피 방식으로 손실함수를 정의했습니다. 여기서 진짜 이미지를 의미하는 값은 1, 가짜 이미지를 의미하는 값은 0인데요.
생성자는 최대한 생성한 이미지가 판별자에게 1값을 배출하는 이미지를 만들기 위해 파라미터를 업데이트 해야합니다.
구별자는 최대한 진짜 이미지는 1, 생성자가 만든 이미지는 0값을 출력해주도록 파라미터를 업데이트 해야합니다.
두 모델의 최종 목적이 상충되는 것이 있는데, 두 모델을 경쟁시키며 둘 다 우수한 모델이 되는 것이 목표입니다.
@tf.function # 데코레이션?
def train_step(real_images):
# 100개의 변수는 랜덤값을 계속 줌. 이건 중요한 값이 아닙니다.
noises = tf.random.normal([BATCH_SIZE, noise_dim])
with tf.GradientTape() as gen_tape, tf.GradientTape() as dsc_tape:
# 생성자로 가짜 이미지를 생성합니다.
fake_images = G(noises, training = True)
# 진짜 이미지를 구별자에 학습시킵니다.
real_output = D(real_images, training = True)
# 가짜 이미지를 구별자에 학습시킵니다.
fake_output = D(fake_images, training = True)
# 로스를 출력합니다.
gen_loss = G_loss(fake_output)
dsc_loss = D_loss(real_output, fake_output)
# 로스 값을 통해 각 모델의 파라미터를 개산해줍니다.
gen_gradients = gen_tape.gradient(gen_loss, G.trainable_variables)
dsc_gradients = dsc_tape.gradient(dsc_loss, D.trainable_variables)
# 옵티마이저를 이용해 파라미터를 업데이트 해줍니다.
G_optimizer.apply_gradients(zip(gen_gradients, G.trainable_variables))
D_optimizer.apply_gradients(zip(dsc_gradients, D.trainable_variables))
배치 한 개의 값이 입력됬을때 수행하는 일을 모은 train_step 함수를 만듭니다.
gen_losses = []
dsc_losses = []
def test_step(real_images):
noises = tf.random.normal([BATCH_SIZE, noise_dim])
fake_images = G(noises, training = False)
real_output = D(real_images, training = False)
fake_output = D(fake_images, training = False)
gen_loss = G_loss(fake_output)
gen_losses.append(gen_loss)
dsc_loss = D_loss(real_output, fake_output)
dsc_losses.append(dsc_loss)
print('Generator loss:', gen_loss.numpy(), ', Discriminator loss:', dsc_loss.numpy())
테스트 하는 부분도 비슷하게 test_step 함수를 만듭니다.
def train(dataset, epochs):
for epoch in range(epochs):
start = time.time()
for i, image_batch in enumerate(dataset):
train_step(image_batch)
if i == 0:
test_step(image_batch)
print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))
앞서 정의한 train_step과 test_step 함수를 작동시키는 train 함수를 만듭니다.
train(train_dataset, EPOCHS)
epo = range(1,EPOCHS + 1)
plt.plot(epo, gen_losses)
plt.plot(epo, dsc_losses)
plt.show()
두 로스 값이 에포크마다 어떻게 변하는지 관찰해봤습니다. 파란색이 생성자 로스, 노란색이 구별자 로스입니다.
다른 딥러닝 로스 값은 에포크가 진행될 때 마다 계속 감소하는 모양을 띄거나 과적합이 되면 더 이상 감소되지 않는 모양을 보입니다.
반면 이 두 모델의 로스 값은 다소 특이하죠. 로스 값이 증가와 감소를 계속 반복합니다.
생성자가 계속해서 구별자를 햇갈리계 하면서 빈틈을 노리고, 구별자는 진화하는 생성자에 대항하기 위해 업데이트 되는 재밌는 현상이네요.
noises = tf.random.normal([50, 100])
generated_image = G(noises, training = False)
fig, axes = plt.subplots(nrows = 3, ncols = 2, figsize = (10, 10))
for ax in axes.flat:
ax.axis('off')
axes[0,0].imshow(generated_image[0], cmap = 'gray')
axes[0,1].imshow(generated_image[1], cmap = 'gray')
axes[1,0].imshow(generated_image[2], cmap = 'gray')
axes[1,1].imshow(generated_image[3], cmap = 'gray')
axes[2,0].imshow(generated_image[4], cmap = 'gray')
axes[2,1].imshow(generated_image[5], cmap = 'gray')
plt.show()
이미지가 원하는 만큼까진 아니지만 어느정도 MNIST 그림과 유사하려고 노력한 것 같습니다.
심플한 GAN모델을 사용했기 때문이며, 더 깊은 층을 쌓거나 비지도 학습에서 벗어나 label 정보를 더 준다면(숫자 값) 더 정교한 가짜 이미지도 만들 수 있을 것입니다.
딥러닝의 개넘, 구조는 많이 학습했지만 텐서플로/파이토치 같은 도구들을 활용하는데 아직 어색합니다.
때문에 GAN이라는 더 응용된 분야를 마음껏 학습하는데 막히는 것이 많았습니다. 오늘 공부한 내용은 GAN이 무엇인지, 간단한 실습 정도 해봤는데요.
개인적으로 아쉽습니다. 더 디테일하게 공부하지 못해서. 빨리 텐서플로/파이토치와 친해져야겠다는 생각이 들었습니다.
오늘 느낀것은 딥러닝구조를 잘 이해만 한다면 이런식의 응용이 또 있을 수 있겠다는 생각이 들었습니다.
딥러닝 기술을 어떻게 응용할지 어느 산업에서 일을 하던 끊임없이 생각하도록 노력해야겠습니다.