Tensorflow-GAN

7 minute read

GAN

GAN이란 상대적 적대 신경망으로서 Generative Adversarial Network이다.

  • Generative(생성적): 데이터 자체를 생성한다.
  • Adversarial(적대적): 적대란 대립하거나 상반되는 관계를 뜻한다. GAN에서는 생성네트워크와 구분 네트워크간의 상반되는 목적함수로 인해 적대성이 생기게 된다.
  • Network: 생성자와 구분자의 구조가 인공 신경망의 형태를 이룬다.

자세한 내용은 앞선 Post Pytorch-GAN을 참조

이번 Post에서는 Tensorflow로서 같은 작업을 해보며 성능 비교 및 결과를 확인하는 것을 목표로 한다.

GAN 구현


필요한 라이브러리 임포트

1
2
3
4
5
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import os



MNIST Dataset & Plot

MNIST데이터를 다운받고, 생성된 MNIST 이미지를 8x8 그리드 형태로 그려주는 plot 함수 정의
matplotlib.gridspec.GridSpec 사용
정식 사이트 사용 방법
이번 코드에서 사용한 방법을 알아보면

  • parameter: 1 x (28x28)형태로 들어옴: samples
  • figsize = (8,8): 최종적인 보여주는 suplot의 형태를 8 x 8로 지정
  • plt.imshow(samples.reshape(28,28)): 1 x (28x28)형태를 image로 보기위하여 원본 image의 크기로 변환

gridspec로서 사용한 Parameter중 사용한 Parameter만 알아본다.
추가적인 자세한 Parameter는 위의 정식 사이트 이용방법에서 참조

matplotlib.gridspec.GridSpec Parameter

Parameter 설명
nrows Number of rows in grid
ncols Number of columns in grid
wsapce The amount of width reserved for space between subplots
hspace The amount of height reserved for space between subplots
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# MNIST 데이터를 다운로드하고 불러옵니다.
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

# 생성된 MNIST 이미지를 8x8 Grid로 보여주는 plot 함수를 정의합니다.
def plot(samples):
    fig = plt.figure(figsize=(8, 8))
    gs = gridspec.GridSpec(8, 8)
    gs.update(wspace=0.05, hspace=0.05)

    for i, sample in enumerate(samples):
        ax = plt.subplot(gs[i])
        plt.axis('off')
        plt.imshow(sample.reshape(28, 28))

    return fig



하이퍼 파라미터 설정

  • num_epoch: 반복 횟수
  • batch_size: 경사하강법의 한 스템에서 사용할 배치 개수
  • num_input: 입력층의 Input size
  • num_latenet_variable: z의 크기이다. 즉 생성할 Noise의 차원
  • num_hidden: Hidden Size크기
  • learning_rate: Learning Rate
1
2
3
4
5
6
num_epoch = 100000
batch_size = 64
num_input = 28 * 28
num_latent_variable = 100   # 잠재 변수 z의 차원
num_hidden = 128
learning_rate = 0.001



PlaceHolder 설정

사용할 진짜 이미지 x 와 임의로 생성할 이미지 z(Noise)를 입력받을 변수 설정

1
2
X = tf.placeholder(tf.float32, shape=[None, num_input])
z = tf.placeholder(tf.float32, shape=[None, num_latent_variable])



생성자, 구분자 변수 선언

생성자(Generator)와 구분자(Discriminator)에 대한 변수를 각각 설정한다.

생성자(Generator)
num_latenent_variable -> num_hidden -> num_input으로서 최종적인 크기는 Input Size와 동일한 크기를 가지도록 한다.

구분자(Discriminator)
num_input -> num_hidden -> 1으로서 최종적인 크기는 1의 크기를 가지게 한다.

tf.variable_scope
변수를 공유하기 위해서 사용하는 방식이다.
예시는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def my_image_filter(input_images):
    conv1_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv1_weights")
    conv1_biases = tf.Variable(tf.zeros([32]), name="conv1_biases")
    conv1 = tf.nn.conv2d(input_images, conv1_weights,
        strides=[1, 1, 1, 1], padding='SAME')
    relu1 = tf.nn.relu(conv1 + conv1_biases)

    conv2_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv2_weights")
    conv2_biases = tf.Variable(tf.zeros([32]), name="conv2_biases")
    conv2 = tf.nn.conv2d(relu1, conv2_weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv2 + conv2_biases)

여러분이 쉽게 상상할 수 있듯이, 모델은 이것보다 훨씬 더 복잡하며, 여기에도 이미 4개의 다른 변수가 있습니다: conv1_weights, conv1_biases, conv2_weights, 그리고 conv2_biases. 문제는 이 모델을 다시 사용하고자 할 때 발생합니다. 2개의 다른 이미지, image1과 image2를 여러분의 이미지 필터에 적용하기를 원한다고 가정하십시오. 여러분은 같은 파라미터로 같은 필터에서 처리된 이미지가 필요합니다. my_image_filter()를 두 번 호출할 수 있지만, 이것은 두 세트의 변수를 생성합니다

First call creates one set of variables
result1 = my_image_filter(image1) Another set is created in the second call
result2 = my_image_filter(image2)
즉 지속적인 변수 생성을 막아 메모리를 효율적으로 사용하고 변수의 범위를 지정해줄 수 있는 Tensorflow의 기능이라고 생각하면 된다.
자세한 사항은 아래 링크 참조
tensorflowkorea 사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
with tf.variable_scope('generator'):
    # 히든 레이어 파라미터 
    G_W1 = tf.Variable(tf.random_normal(shape=[num_latent_variable, num_hidden], stddev=5e-2))   
    G_b1 = tf.Variable(tf.constant(0.1, shape=[num_hidden]))
    # 아웃풋 레이어 파라미터
    G_W2 = tf.Variable(tf.random_normal(shape=[num_hidden, num_input], stddev=5e-2))   
    G_b2 = tf.Variable(tf.constant(0.1, shape=[num_input]))

with tf.variable_scope('discriminator'):
    # 히든 레이어 파라미터
    D_W1 = tf.Variable(tf.random_normal(shape=[num_input, num_hidden], stddev=5e-2))   
    D_b1 = tf.Variable(tf.constant(0.1, shape=[num_hidden]))
    # 아웃풋 레이어 파라미터
    D_W2 = tf.Variable(tf.random_normal(shape=[num_hidden, 1], stddev=5e-2))   
    D_b2 = tf.Variable(tf.constant(0.1, shape=[1]))



생성자, 구분자 생성

위에서 tf.variable_scope에서 선언한 Parameter를 활용하여 실제 Generator와 discriminator를 선언한다.
각각의 Hidden Layer는 1개 뿐이며 단순한 matmul과 sigmoid를 통하여 이루워 진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Generator를 생성하는 함수를 정의합니다.
# Inputs:
#   X : 인풋 Latent Variable
# Outputs:
#   generated_mnist_image : 생성된 MNIST 이미지
def build_generator(X):
    hidden_layer = tf.nn.relu((tf.matmul(X, G_W1) + G_b1))
    output_layer = tf.matmul(hidden_layer, G_W2) + G_b2
    generated_mnist_image = tf.nn.sigmoid(output_layer)

    return generated_mnist_image

# Discriminator를 생성하는 함수를 정의합니다.
# Inputs:
#   X : 인풋 이미지
# Outputs:
#   predicted_value : Discriminator가 판단한 True(1) or Fake(0)
#   logits : sigmoid를 씌우기전의 출력값
def build_discriminator(X):
    hidden_layer = tf.nn.relu((tf.matmul(X, D_W1) + D_b1))
    logits = tf.matmul(hidden_layer, D_W2) + D_b2
    predicted_value = tf.nn.sigmoid(logits)

    return predicted_value, logits



D,G 생성

GAN설명에서의 D,G를 정의하는 과정이다.

  • D(x): Data를 실제 데이터는 1, 생성 데이터는 0으로 판별하는 함수
  • G(x): Data를 생성하는 함수
  • x: 실제 데이터
  • z: Noise

위의 최종적인 결과로서
실제 Data를 분별한 값: D_real, D_real_logits 와
생성한 Data(G(z))를 분별한 값: D_fake, D_fake_logits가 생성된다.

1
2
3
4
5
6
# 생성자(Generator)를 선언합니다.
G = build_generator(z)

# 구분자(Discriminator)를 선언합니다.
D_real, D_real_logits = build_discriminator(X)  # D(x)
D_fake, D_fake_logits = build_discriminator(G)  # D(G(z))



LossFunction 정의

$$ \underset{G}{min} \underset{D}{max}V(D,G)$$

$$= \mathbb{E}_{x\text{~}P_{data}(x)}[logD(x)] + \mathbb{E}_{z\text{~}P_{z}(z)}[log(1 - D(G(z)))]$$

$$\mathbb{E}: \text{기대값}$$

$$x\text{~}P_{data}(x): \text{x를 실제 data의 분포에서 샘플링}$$

$$z\text{~}P_{z}(z): \text{z를 Noise의 분포에서 샘플링}$$

구분자

$$ \underset{D}{max}V(D,G)$$

$$= \mathbb{E}_{x\text{~}P_{data}(x)}[logD(x)] + \mathbb{E}_{z\text{~}P_{z}(z)}[log(1 - D(G(z)))]$$

생성자

$$ \underset{G}{min}V(D,G)$$

$$= \mathbb{E}_{x\text{~}P_{data}(x)}[logD(x)] + \mathbb{E}_{z\text{~}P_{z}(z)}[log(1 - D(G(z)))]$$

$$=> Trainning의 시간을 줄이기 위하여 식 변경$$

$$ \underset{G}{max}V(D,G) = \mathbb{E}_{z\text{~}P_{z}(z)}[log( D(G(z)))]$$

log 함수를 적용하기 위하여 tf.nn.sigmoid_cross_entropy_with_logits 사용

tf.nn.sigmoid_cross_entropy_with_logits
tf.nn.sigmoid_cross_entropy_with_logits( labels=None, logits=None, name=None )

x = logits, z = labels tf.nn.sigmoid_cross_entropy_with_logits(logits = x, labels =z) = z * -log(sigmoid(x)) + (1-z) * -log(1 - sigmoid(x))

TensorFlow 정식 홈페이지 사용방법

위의 식을 아래에 적용 시켜보자

1
d_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_real_logits, labels=tf.ones_like(D_real_logits)))

위의 코드를 식으로서 표현하게 된다면

$$d-loss-real$$

$$= mean(1 * -log(sigmoid(\text{D_real_logits})$$

$$+ (1-1) * -log(1-sigmoid(\text{D_real_logits}))))$$

$$= mean(-log(sigmoid(\text{D_real_logits})))$$

$$= mean(-log(\frac{1}{1+e^{-\text{D_real_logits}}}))$$

$$= mean(log(1+e^{-\text{D_real_logits}}))$$

최종적인 위의 식에서 간단하게 식을 \(log(1+e^{-x})\)라고 생각하고 그래프를 그려보면 다음과 같다.

위에서 tf.variable_scope로서 변수 선언을 할 때 분산값을 작게 주어서 대부분의 값은 0의 가깝게 위치하게 되고 bias를 통하여 +0.1을 하여도 그래프의 값은 0.5 이하일 것을 예측할 수 있다.
(Gradient Vanishing이나 Gradient exploding현상이 일어나지 않을 것 이다.)

1
2
3
4
5
6
7
# Discriminator의 손실 함수를 정의합니다.
d_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_real_logits, labels=tf.ones_like(D_real_logits)))    # log(D(x))
d_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_fake_logits, labels=tf.zeros_like(D_fake_logits)))   # log(1-D(G(z)))
d_loss = d_loss_real + d_loss_fake  # log(D(x)) + log(1-D(G(z)))

# Generator의 손실 함수를 정의합니다.
g_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_fake_logits, labels=tf.ones_like(D_fake_logits)))         # log(D(G(z))



각각의 Parameter들을 List로 통합

Tensorflow tf.trainable_variable() 사용법의 사이트를 찾아가면 tf.trainable_variables() 는 다음과 같다.

Returns all variables created with trainable = True
Returns: A list of Variable objects

위의 코드를 활용하여 각각의 Trainning가능한 Parameter들을 Discriminator와 Generator에 관련된 파라미터로서 모았다.

1
2
3
tvar = tf.trainable_variables()
dvar = [var for var in tvar if 'discriminator' in var.name]
gvar = [var for var in tvar if 'generator' in var.name]



Optimizer

Discriminator와 Generator를 구별하여 각각의 Netowrk에 관련된 Parameter들을 따로 분리하여 Optimier하는 방식이다.
Optimizer는 Adam을 사용하였다.

1
2
d_train_step = tf.train.AdamOptimizer(learning_rate).minimize(d_loss, var_list=dvar)
g_train_step = tf.train.AdamOptimizer(learning_rate).minimize(g_loss, var_list=gvar)



학습 결과 저장 폴터 지정

학습 결과로 생성된 이미지를 지정할 폴더 설정

1
2
3
4
# 생성된 이미지들을 저장할 generated_output 폴더를 생성합니다.
num_img = 0
if not os.path.exists('generated_output/'):
    os.makedirs('generated_output/')



Trainning 및 결과 확인

정의된 Parameter, LossFunction, Optimizer를 통하여 Trainning한다.
매 Epoch마다 HyperParameter를 Update한다.
500 Epoch마다 생성된 Image를 저장한다.
Image는 위에서 8 x 8 개수로서 Plot하기로 하였으므로 Image를 생성하는 BatchSize는 64로서 고정한다.

np.random.uniform(-1., 1., [batch_size, 100])
numpy.random.uniform(low=0.0, high=1.0, size=None)

Draw samples from a uniform distribution

scipy 사용법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 그래프를 실행합니다.
with tf.Session() as sess:
    # 변수들에 초기값을 할당합니다.
    sess.run(tf.global_variables_initializer())
    
    # num_epoch 횟수만큼 최적화를 수행합니다.
    for i in range(num_epoch):
        # MNIST 이미지를 batch_size만큼 불러옵니다. 
        batch_X, _ = mnist.train.next_batch(batch_size)
        # Latent Variable의 인풋으로 사용할 noise를 Uniform Distribution에서 batch_size 개수만큼 샘플링합니다.
        batch_noise = np.random.uniform(-1., 1., [batch_size, 100])
        
        # 500번 반복할때마다 생성된 이미지를 저장합니다.
        if i % 500 == 0:
            samples = sess.run(G, feed_dict={z: np.random.uniform(-1., 1., [64, 100])})
            fig = plot(samples)
            plt.savefig('generated_output/%s.png' % str(num_img).zfill(3), bbox_inches='tight')
            num_img += 1
            plt.close(fig)
    
        # Discriminator 최적화를 수행하고 Discriminator의 손실함수를 return합니다.
        _, d_loss_print = sess.run([d_train_step, d_loss], feed_dict={X: batch_X, z: batch_noise})

        # Generator 최적화를 수행하고 Generator 손실함수를 return합니다.
        _, g_loss_print = sess.run([g_train_step, g_loss], feed_dict={z: batch_noise})

        # 100번 반복할때마다 Discriminator의 손실함수와 Generator 손실함수를 출력합니다.
        if i % 5000 == 0:
            print('반복(Epoch): %d, Generator 손실함수(g_loss): %f, Discriminator 손실함수(d_loss): %f' % (i, g_loss_print, d_loss_print))


반복(Epoch): 0, Generator 손실함수(g_loss): 1.462668, Discriminator 손실함수(d_loss): 1.422923
반복(Epoch): 5000, Generator 손실함수(g_loss): 5.218184, Discriminator 손실함수(d_loss): 0.094545

...

반복(Epoch): 90000, Generator 손실함수(g_loss): 2.534064, Discriminator 손실함수(d_loss): 0.544332
반복(Epoch): 95000, Generator 손실함수(g_loss): 2.208852, Discriminator 손실함수(d_loss): 0.578487




결과 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from matplotlib.image import imread
path = './generated_output/'

for i in range(10):
    image_name = i * 20
    image_title = i * 20 * 500
    
    if image_name == 0:
        image_name = '00'+str(image_name)
    elif image_name < 100:
        image_name = '0'+str(image_name)
    else:
        image_name = str(image_name)
    
    image_name = path + image_name + '.png'
    image = imread(image_name)
    plt.figure(figsize=(5,5))
    plt.title('Epoch: '+str(image_title))
    plt.imshow(image)
    plt.show()





참조:원본코드
참조:텐서플로로 배우는 딥러닝
문제가 있거나 궁금한 점이 있으면 wjddyd66@naver.com으로 Mail을 남겨주세요.

Categories:

Updated:

Leave a comment