35. 딥러닝으로 MNIST 98%이상 해보기 (lab 10) 소스 코드

이번 동영상에서 교수님께서 여러 가지 코드를 말씀하셨다. 종류도 많고 코드도 길고 해서 따로 정리했다.

모든 코드의 learning_rate은 0.001로 고정시켰고, training_epochs 또한 15회로 고정시켰다. 교수님께서 말씀하신 부분만 수정했다. 다만 XavierForMNIST.py에서 동영상에 나오지 않은 코드가 일부 있다. bias를 초기화하는 부분인데, 동영상에는 없었다.

교수님 동영상을 보고 나처럼 정리하는 사람들이 많다. 도움을 받은 사이트를 소개한다. 아래 코드에 대한 설명은 이미 이전 글에서 했다. 그래도 부족한 사람은 아래 링크를 참고하기 바란다.

  MNIST 데이터 셋을 이용한 손글씨 인식 Deep Neural Network 구현


# NeuralNetworkForMnist.py : 94.57%

import tensorflow as tf

# Import MINST data
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

# Parameters. 반복문에서 사용하는데, 미리 만들어 놓았다.
learning_rate = 0.001
training_epochs = 15
batch_size = 100
display_step = 1

# tf Graph Input
X = tf.placeholder(tf.float32, [None, 784]) # mnist data image of shape 28*28=784
Y = tf.placeholder(tf.float32, [None, 10]) # 0-9 digits recognition => 10 classes

# --------------------------- 수정한 부분 ------------------------------ #
# Set model weights
W1 = tf.Variable(tf.random_normal([784, 256]))
W2 = tf.Variable(tf.random_normal([256, 256]))
W3 = tf.Variable(tf.random_normal([256, 10]))

B1 = tf.Variable(tf.random_normal([256]))
B2 = tf.Variable(tf.random_normal([256]))
B3 = tf.Variable(tf.random_normal([ 10]))

# Construct model
L1 = tf.nn.relu(tf.add(tf.matmul(X, W1), B1))
L2 = tf.nn.relu(tf.add(tf.matmul(L1, W2), B2)) # Hidden layer with ReLU activation
hypothesis = tf.add(tf.matmul(L2, W3), B3) # No need to use softmax here
# ---------------------------- 여기까지 ------------------------------- #

# Minimize error using cross entropy
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(hypothesis, Y)) # softmax loss
# Gradient Descent
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)

# Initializing the variables
init = tf.initialize_all_variables()

# Launch the graph
with tf.Session() as sess:
sess.run(init)

# Training cycle
for epoch in range(training_epochs):
avg_cost = 0.
# 나누어 떨어지지 않으면, 뒤쪽 이미지 일부는 사용하지 않는다.
total_batch = int(mnist.train.num_examples/batch_size)
# Loop over all batches
for i in range(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# Run optimization op (backprop) and cost op (to get loss value)
_, c = sess.run([optimizer, cost], feed_dict={X: batch_xs, Y: batch_ys})

# 분할해서 구동하기 때문에 cost를 계속해서 누적시킨다. 전체 중의 일부에 대한 비용.
avg_cost += c / total_batch
# Display logs per epoch step. display_step이 1이기 때문에 if는 필요없다.
if (epoch+1) % display_step == 0:
print("Epoch:", '%04d' % (epoch+1), "cost=", "{:.9f}".format(avg_cost))

print("Optimization Finished!")

# Test model
correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(Y, 1))
# Calculate accuracy
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print("Accuracy:", accuracy.eval({X: mnist.test.images, Y: mnist.test.labels}))
[출력 결과]
Epoch: 0001 cost= 230.355805381
Epoch: 0002 cost= 44.542764827
Epoch: 0003 cost= 27.731916585
Epoch: 0004 cost= 19.474284099
Epoch: 0005 cost= 13.878197979
Epoch: 0006 cost= 10.265536548
Epoch: 0007 cost= 7.547600131
Epoch: 0008 cost= 5.628515447
Epoch: 0009 cost= 4.225564419
Epoch: 0010 cost= 3.117088286
Epoch: 0011 cost= 2.374554315
Epoch: 0012 cost= 1.842727151
Epoch: 0013 cost= 1.413149142
Epoch: 0014 cost= 1.093248269
Epoch: 0015 cost= 0.855855221
Optimization Finished!
Accuracy: 0.9457


정말로 아무 것도 하지 않고, 앞의 코드에 대해 초기값만 xavier 알고리듬을 사용했다. 전체 코드 대신 수정이 발생한 부분에 대해서만 코드를 표시했다.

# XavierForMnist.py : 97.78%

import tensorflow as tf

# --------------------------- 추가한 부분 ------------------------------ #
# http://stackoverflow.com/questions/33640581/how-to-do-xavier-initialization-on-tensorflow
def xavier_init(n_inputs, n_outputs, uniform=True):

if uniform:
# 6 was used in the paper.
init_range = tf.sqrt(6.0 / (n_inputs + n_outputs))
return tf.random_uniform_initializer(-init_range, init_range)
else:
# 3 gives us approximately the same limits as above since this repicks
# values greater than 2 standard deviations from the mean.
stddev = tf.sqrt(3.0 / (n_inputs + n_outputs))
return tf.truncated_normal_initializer(stddev=stddev)
# ---------------------------- 여기까지 ------------------------------- #

# 수정하지 않은 부분의 마지막 줄
X = tf.placeholder(tf.float32, [None, 784])
Y = tf.placeholder(tf.float32, [None, 10])

# --------------------------- 수정한 부분 ------------------------------ #
# Store layers weight & bias
W1 = tf.get_variable("W1", shape=[784, 256], initializer=xavier_init(784, 256))
W2 = tf.get_variable("W2", shape=[256, 256], initializer=xavier_init(256, 256))
W3 = tf.get_variable("W3", shape=[256, 10], initializer=xavier_init(256, 10))

B1 = tf.Variable(tf.zeros([256])) # 동영상에 없는 코드
B2 = tf.Variable(tf.zeros([256]))
B3 = tf.Variable(tf.zeros([ 10]))
# ---------------------------- 여기까지 ------------------------------- #

# 수정한 내용없음
[출력 결과]
Epoch: 0001 cost= 0.283199429
Epoch: 0002 cost= 0.100252399
Epoch: 0003 cost= 0.062173021
Epoch: 0004 cost= 0.046147975
Epoch: 0005 cost= 0.034034847
Epoch: 0006 cost= 0.024862914
Epoch: 0007 cost= 0.022365937
Epoch: 0008 cost= 0.018858417
Epoch: 0009 cost= 0.015144211
Epoch: 0010 cost= 0.011575518
Epoch: 0011 cost= 0.015414950
Epoch: 0012 cost= 0.011545146
Epoch: 0013 cost= 0.012465422
Epoch: 0014 cost= 0.010137170
Epoch: 0015 cost= 0.012283421
Optimization Finished!
Accuracy: 0.9778

xavier 초기화에서 눈여겨봐야 할 것이 있다면, [출력 결과]이다. 첫 번째로 출력된 cost가 0.283으로 시작한다. 반면 첫 번째 코드에서는 마지막의 cost가 0.855이다. hinton 교수님께서 말씀하셨던 "좋은 초기화"가 무엇인지 제대로 보여주고 있다.


바로 앞에 있는 xavier 초기화 코드에 dropout 알고리듬을 다시 추가했다. 마찬가지로 수정이 발생한 부분에 대해서만 표시했다.

# DropoutForMnist.py : 98.19%

# 수정하지 않은 부분의 마지막 줄
X = tf.placeholder(tf.float32, [None, 784])
Y = tf.placeholder(tf.float32, [None, 10])

# --------------------------- 수정한 부분 ------------------------------ #
# set dropout rate
dropout_rate = tf.placeholder("float")

# set model weights
W1 = tf.get_variable("W1", shape=[784, 256], initializer=xavier_init(784, 256))
W2 = tf.get_variable("W2", shape=[256, 256], initializer=xavier_init(256, 256))
W3 = tf.get_variable("W3", shape=[256, 256], initializer=xavier_init(256, 256))
W4 = tf.get_variable("W4", shape=[256, 256], initializer=xavier_init(256, 256))
W5 = tf.get_variable("W5", shape=[256, 10], initializer=xavier_init(256, 10))

B1 = tf.Variable(tf.random_normal([256]))
B2 = tf.Variable(tf.random_normal([256]))
B3 = tf.Variable(tf.random_normal([256]))
B4 = tf.Variable(tf.random_normal([256]))
B5 = tf.Variable(tf.random_normal([ 10]))

# Construct model
_L1 = tf.nn.relu(tf.add(tf.matmul(X,W1),B1))
L1 = tf.nn.dropout(_L1, dropout_rate)
_L2 = tf.nn.relu(tf.add(tf.matmul(L1, W2),B2)) # Hidden layer with ReLU activation
L2 = tf.nn.dropout(_L2, dropout_rate)
_L3 = tf.nn.relu(tf.add(tf.matmul(L2, W3),B3)) # Hidden layer with ReLU activation
L3 = tf.nn.dropout(_L3, dropout_rate)
_L4 = tf.nn.relu(tf.add(tf.matmul(L3, W4),B4)) # Hidden layer with ReLU activation
L4 = tf.nn.dropout(_L4, dropout_rate)

hypothesis = tf.add(tf.matmul(L4, W5), B5) # No need to use softmax here
# ---------------------------- 여기까지 ------------------------------- #

# 여기서부터는 run 호출에 dropout_rate 추가한 부분만 수정
_, c = sess.run([optimizer, cost],
feed_dict={X: batch_xs, Y: batch_ys, dropout_rate: 0.7})
print("Accuracy:", accuracy.eval({X: mnist.test.images,
Y: mnist.test.labels, dropout_rate: 1}))
[출력 결과]
Epoch: 0001 cost= 0.285693568
Epoch: 0002 cost= 0.102228592
Epoch: 0003 cost= 0.065145561
Epoch: 0004 cost= 0.047662693
Epoch: 0005 cost= 0.034276855
Epoch: 0006 cost= 0.027074164
Epoch: 0007 cost= 0.021216354
Epoch: 0008 cost= 0.019675067
Epoch: 0009 cost= 0.015092999
Epoch: 0010 cost= 0.014085908
Epoch: 0011 cost= 0.013787527
Epoch: 0012 cost= 0.010379254
Epoch: 0013 cost= 0.010147506
Epoch: 0014 cost= 0.013407918
Epoch: 0015 cost= 0.009291326
Optimization Finished!
Accuracy: 0.9819

레이어 갯수가 많지 않아서 dropout이 좋은 효과를 내지 못하는 것처럼 보인다. CPU 버전에서는 dropout 코드가 그래도 가장 좋은 성능을 보여줬는데, GPU 버전에서 돌려보니까 의외로 dropout 알고리듬을 적용하지 않은 xavier 초기화 코드가 가장 좋은 성능을 냈다. CPU와 GPU의 버전 차이가 있는지는 모르겠지만, "성능 차이는 없겠다"라는 생각이 들었다.

횟수를 150으로 늘려봤다. cost는 0.002까지 떨어졌으니까, 마지막의 0.009에 비하면 많이 떨어지긴 했는데 정확도는 조금 올라간 것으로 나왔다. 텐서플로우 샘플에 포함된 코드처럼 99%를 찍거나 하지는 않았다. xavier와 dropout 모두 95.25%까지 나오고 더 이상 발전이 없었다. 

이런.. 마지막으로 다시 돌렸는데, 98.56%가 나왔다. 100번 반복 이후로는 cost가 0으로 나왔다. 소숫점 9자리에서 0이니까, 그 아래로 미세한 숫자가 있을 수 있지만, 더 이상 줄어들 것이 없기 때문에 이 정도가 최선의 결과일 수밖에 없겠다.

이번 동영상의 코드는 교수님께서 여러 가지 이론들이 어떻게 반영되는지, 성능이 얼마나 좋아지는지 보여주기 위해서 만드셨다. 나중에 반드시 텐서플로우 샘플에 포함된 코드도 봐야 한다. 코드가 쉽진 않지만, 99.2%의 결과를 어떻게 만들 수 있는지는 꼭 확인해야 한다.