import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
# mnist data 불러옴
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("./mnist/data/", one_hot=True)
# epoch, batch size, noise 로 쓸 상수 변수 선언
total_epoch = 100
batch_size = 100
n_noise = 100
# 전역 변수로 쓸 변수 0으로 초기화
D_global_step = tf.Variable(0, trainable=False, name='D_global_step')
G_global_step = tf.Variable(0, trainable=False, name='G_global_step')
# 이미지를 담는 변수 X와 노이즈 변수 Z
X = tf.placeholder(tf.float32, [None, 28, 28, 1])
Z = tf.placeholder(tf.float32, [None, n_noise])
is_training = tf.placeholder(tf.bool)
# Leaky ReLU : ReLU가 음수영역에서 0을 출력하던 것과 달리, 약간의 기울기를 갖는 값을 출력한다.
def leaky_relu(x, leak=0.2):
return tf.maximum(x, x * leak)
# 생성기(Generator) 모델 구성
# DCGAN의 큰 특징 convlution layer와 batch normalization, 마지막은 tanh로 output이 나온다
def generator(noise):
with tf.variable_scope('generator'):
output = tf.layers.dense(noise, 128*7*7)
output = tf.reshape(output, [-1, 7, 7, 128])
output = tf.nn.relu(tf.layers.batch_normalization(output, training=is_training))
output = tf.layers.conv2d_transpose(output, 64, [5, 5], strides=(2, 2), padding='SAME')
output = tf.nn.relu(tf.layers.batch_normalization(output, training=is_training))
output = tf.layers.conv2d_transpose(output, 32, [5, 5], strides=(2, 2), padding='SAME')
output = tf.nn.relu(tf.layers.batch_normalization(output, training=is_training))
output = tf.layers.conv2d_transpose(output, 1, [5, 5], strides=(1, 1), padding='SAME')
output = tf.tanh(output)
return output
# 판별기(Discriminator) 모델 구성
# leaky_relu, convolution layer를 사용. 마지막은 flatten 해준다. (그림에서는 sigmoid이지만 해당 코드에서는 그냥 flatten 해줌)
def discriminator(inputs, reuse=None):
with tf.variable_scope('discriminator') as scope:
# scope.reuse_variables()이후에는 같은 scope내에서 같은 name을 갖는 변수를 새로 만들지 않고 재사용 한다.
if reuse:
scope.reuse_variables()
output = tf.layers.conv2d(inputs, 32, [5, 5], strides=(2, 2), padding='SAME')
output = leaky_relu(output)
output = tf.layers.conv2d(output, 64, [5, 5], strides=(2, 2), padding='SAME')
output = leaky_relu(tf.layers.batch_normalization(output, training=is_training))
output = tf.layers.conv2d(output, 128, [5, 5], strides=(2, 2), padding='SAME')
output = leaky_relu(tf.layers.batch_normalization(output, training=is_training))
flat = tf.contrib.layers.flatten(output)
output = tf.layers.dense(flat, 1, activation=None)
return output
# 노이즈 생성
def get_noise(batch_size, n_noise):
return np.random.uniform(-1.0, 1.0, size=[batch_size, n_noise])
# interpolation 되는 과정을 보기위해 노이즈를 두개 생성하는 부분
def get_moving_noise(batch_size, n_noise):
assert batch_size > 0
# 노이즈 두개 생성
noise_list = []
base_noise = np.random.uniform(-1.0, 1.0, size=[n_noise])
end_noise = np.random.uniform(-1.0, 1.0, size=[n_noise])
# 임의의 step 설정
step = (end_noise - base_noise) / batch_size
noise = np.copy(base_noise)
for _ in range(batch_size - 1):
noise_list.append(noise)
noise = noise + step
noise_list.append(end_noise) # 변해가는 모습을 볼 수 있게 base 부터 end 까지 노이즈 리스트 만듦
return noise_list
G = generator(Z)
D_real = discriminator(X)
D_gene = discriminator(G, True)
# loss 함수 cross entropy 사용
loss_D_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_real, labels=tf.ones_like(D_real)))
loss_D_gene = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_gene, labels=tf.zeros_like(D_gene)))
loss_D = loss_D_real + loss_D_gene
loss_G = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_gene, labels=tf.ones_like(D_gene)))
교차 엔트로피는 주로 범주형 데이터에 사용되는 손실함수이며, 다음과 같다.
loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=y_true, logits_y_pred) loss = tf.reduce_mean(loss)
교차 엔트로피는 두 분포 사이의 유사성을 측정하는 척도이다. 딥러닝에서의 분류 모델은 각 클래스의 확률값을 계산하므로 실제 클래스(y_true)와 모델이 예측한 클래스(y_pred)를 비교할 수 있으며, 두 분포가 가까울수록 교차 엔트로피값은 더 작아진다.
# tensorflow의 namefield를 이용해 변수를 재활용하기 위해 해당 collection의 variable들을 불러온다.
# tf.get_collection(key)가 실행되면, key의 collection에 속하는 variable들의 리스트가 리턴된다.
# 출처: https://eyeofneedle.tistory.com/24 [Technology worth spreading]
vars_D = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,scope='discriminator')
vars_G = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,scope='generator')
# training 시점에서 변수 update_ops에 업데이트가 저장됨. 그래서 update_ops를 학습할 때 넣어준다.
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
# loss를 adamoptimizer를 이용해서 minimize하게 학습
with tf.control_dependencies(update_ops):
train_D = tf.train.AdamOptimizer().minimize(loss_D,var_list=vars_D, global_step=D_global_step)
train_G = tf.train.AdamOptimizer().minimize(loss_G,var_list=vars_G, global_step=G_global_step)
# TensorBoard 사용하게 위해 loss값들을 기록
tf.summary.scalar('loss_D', loss_D)
tf.summary.scalar('loss_G', loss_G)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
merged = tf.summary.merge_all()
writer = tf.summary.FileWriter('./logs', sess.graph) # 로그로 저장
total_batch = int(mnist.train.num_examples / batch_size) # 전체 배치 사이즈 계산
for epoch in range(total_epoch):
loss_val_D, loss_val_G = 0, 0
batch_xs, batch_ys = None, None
noise = None
for i in range(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
batch_xs = batch_xs.reshape(-1, 28, 28, 1)
noise = get_noise(batch_size, n_noise)
# 학습 돌림
_, loss_val_D = sess.run([train_D, loss_D],feed_dict={X: batch_xs, Z: noise, is_training: True})
_, loss_val_G = sess.run([train_G, loss_G],feed_dict={X: batch_xs, Z: noise, is_training: True})
summary = sess.run(merged,feed_dict={X: batch_xs, Z: noise, is_training: True})
writer.add_summary(summary, global_step=sess.run(G_global_step)) # log로 저장
# epoch과 loss 프린트
print('Epoch:', '%04d' % epoch,
'D loss: {:.4}'.format(loss_val_D),
'G loss: {:.4}'.format(loss_val_G))
# 5번째 마다 그림 보여줌 및 저장
if epoch == 0 or (epoch + 1) % 5 == 0:
sample_size = 10
noise = get_noise(sample_size, n_noise)
samples = sess.run(G, feed_dict={Z: noise, is_training: False})
test_noise = get_moving_noise(sample_size, n_noise) # 두개의 노이즈 만들고 그 사이에 step을 더해서 noise list 가져옴
test_samples = sess.run(G, feed_dict={Z: test_noise, is_training: False})
fig, ax = plt.subplots(2, sample_size, figsize=(sample_size, 2))
for i in range(sample_size):
ax[0][i].set_axis_off()
ax[1][i].set_axis_off()
ax[0][i].imshow(np.reshape(samples[i], (28, 28))) # generator가 만든 그림들
ax[1][i].imshow(np.reshape(test_samples[i], (28, 28))) # 두개의 노이즈를 만들어 interpolation되어가는 과정을 보여준 그림
plt.savefig('{}.png'.format(str(epoch).zfill(3)),bbox_inches='tight')
plt.close(fig)
첫째줄 - Generator가 생성한 그림
둘째줄 - 노이즈 두개를 생성해서 Z(latent vector)를 이용해 점점 변하는 것을 보여줌
009.png
054.png
074.png
099.png