본문 바로가기
machine learning

[TENSOR FLOW] MNIST 인식하기

by 유주원 2016. 4. 11.


01_MNIST_For_ML_Beginners.ipynb

우리는 지금부터 MNIST 필기체 데이터를 인식하는 프로그램을 만들어 볼 것이다. 일단 MNIST DATA를 가져오기 위해서 아래의 url에서 input_data.py를 다운 받는다.


https://tensorflow.googlesource.com/tensorflow/+/master/tensorflow/examples/tutorials/mnist/input_data.py


우리는 지금부터 MNIST 필기체 데이터를 인식하는 프로그램을 만들어 볼 것이다. 일단 MNIST DATA를 가져오기 위해서 아래의 url에서 input_data.py를 다운 받는다.


다운 받았으면 이제 import로 input_data를 호출하여 MNIST DATA를 가져온다.


import sys

sys.path.append("/root/work/")

import input_data

mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)


sys.path.append("/root/work/")는 실제 input_data.py가 있는 위치를 정의하기 위해 작성한 구문이다.

input_data.read_data_sets를 하면 서버로부터 MNIST_DATA를 가져 올 수 있다. one_hot encoding을 True로 설정하여 vector 표현이 가능하게 설정한다.


MNIST DATA 객체는 train과 test 속성을 가지고 있으며 각각의 속성은 images와 labels 속성을 가지고 있다. train과 test는 각각 train을 위한 data set, test를 위한 data set을 나타내며, images는 예측을 위한 입력 값, labels는 결과 값이라고 생각하면 쉽다.


print mnist.train.labels[1]

print mnist.train.images[1] 




해당 image에 대해 그림을 그려보면 아래와 같다. 현재 images에 있는 내용은 1차원 행렬로 표현되어 있기 때문에 28x28의 2차원 행렬로 바꾸어 주어야 한다. numpy로 차원을 행렬의 shape를 바꿔준 후, matplot을 이용해서 그림을 그려주면 아래와 같이 나타난다.


import tensorflow as tf

import numpy as np


arr = np.array(mnist.train.images[1])

arr.shape = (28,28)


%matplotlib inline

import matplotlib.pyplot as plt


plt.imshow(arr)


이제 해당 데이터를 이용해 train을 해보자.


x = tf.placeholder(tf.float32, [None, 784])

W = tf.Variable(tf.zeros([784, 10]))

b = tf.Variable(tf.zeros([10]))


y = tf.nn.softmax(tf.matmul(x, W) + b)


y_ = tf.placeholder(tf.float32, [None, 10])

cross_entropy = -tf.reduce_sum(y_ * tf.log(y))

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)


init = tf.initialize_all_variables()

sess = tf.Session()

sess.run(init)


for i in range(1000):

    batch_xs, batch_ys = mnist.train.next_batch(100)

    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})




input은 총 55000개의 784 pixel을 가진 학습 데이터가 사용되며, output는 10개의 classification을 가진 55000개의 결과가 만들어질 것이다. 






우리는 이제 tensorflow 연산 시 데이터를 tensorflow에게 보내기 위한 공간을 만들 필요가 있다. placeholder라고 하는 함수를 이용하자. [None, 784]는 행 길이는 제한을 두지 않겠다는 것을 의미한다. 

W는 784 x 10 차원의 0 값을 가진 행렬로 정의하자. None x 784 행렬을 10개의 class로 분류하기 위해 W는 10차원의 행렬이 되어야 한다. 0은 초기값이며 학습을 통해 그 값을 계속 변경해 나갈 것이다.

b도 10차원의 0 값을 가진 행렬로 정의해 두자. b는 bias의 줄임 표현으로 W와 입력값의 결과에 추가적인 정보를 더하기 위한 값을 의미한다.


y값을 계산하는 것을 보면 tf.matmul(x,W) + b라고 적혀 있는데 이것은 단순하게 행렬 곱을 의미하며 방정식으로 말하자면 Wx + b를 의미한다. 

이 결과 값에 대해 softmax라는 함수를 취하는데 해당 결과 값에 대해서 softmax를 취하게 되면 확률 값으로 변하게 된다. 

위의 8이란 필기체 사진을 예로 들어보면 위의 사진을 80% 가량 8이라고 인식할 수 있지만 10%가량은 9라고 인식할 수 있고 그 나머지는 나머지 숫자들로 인식할 수가 있다.

Wx는 해당 숫자가 무엇을 나타내는지 그 증거를 찾는 과정이라고 할 수가 있으며 b는 추가적인 증거를 더하는 과정이라고 생각하면 된다.


위의 그림에서 색이 푸른색으로 나타나는 지점은 W를 마이너스를 줌으로써 해당 증거 결과 값을 음수값을 띄게 하고, 숫자 부분은 W를 플러스 값을 줌으로써 해당 증거 값을 양수 값을 띄게 한다.

이렇게 나타난 증거 결과에 대해서 softmax 값을 취함으로써 확률로 변환시켜 주는 것이다.


y_는 tensorflow로부터 결과 값을 받아오기 위한 placeholder를 정의한 것이다.


그 후에 loss 함수를 정의해 주는데, cross_entropy라 불리우는 방식을 사용한다. 기존 RMSE와 그 의미 및 목표는 같다고 볼 수가 있다. 다른 점은 cross_entropy는 확률 분포에 대한 차이점을 나타내는 것이라고 하겠다. 




우리가 one_hot encode로 표현한 확률 분포와 실제 계산해서 나온 확률 분포 간의 차이를 구해서 그 값이 가장 작은 지점에서의 weight 값을 찾아내는 것이다. 



loss 함수까지 정의가 끝났으면 이제 gradient descent optimizer에 learning_rate와 loss 함수를 등록해 주면 사전 작업은 모두 끝났다.


이제 training을 돌리기 전에 tf의 모든 변수들을 초기화 시켜준다. tensor flow는 lazy evaluation 방식이라 코드가 작성되면 바로 실행되는 것이 아니라 session.run이 실행 되어야 실제 함수가 동작한다. 세션을 선언한 후 session.run에 정의한 init 함수를 집어 넣자.


이제 for 문을 1000번을 돌려, 100개 씩 input_images 데이터와 input_labels 데이터를 가져온다. session.run을 실행하여 아까 정의한 training 함수를 실행시키면 모든 training이 완료된다.


이제 만들어진 model에 대해 테스트 데이터를 돌려 검증을 해 보자.


correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))


print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))


argmax는 해당 output 에서 가장 index가 큰 결과를 가져 온다. index가 크다는 의미는 가장 점수가 높게 설정되었다는 말이고 해당 결과를 정답으로 볼 수 있다는 말이 된다. 예측한 값에서의 argmax 와 실제 onehot encode에서의 argmax를 각각 가져와서 비교해 보자. 해당 값이 같으면 true, 틀리면 false를 리턴할 것이다. correct_prediction은 true, false 배열을 나타낸다. 

correct_prediction에 대해 출력해 보고 싶다면 직접 호출할 수는 없고 session.run을 실행해서 결과를 확인해야 한다. 아래와 같이 코드를 작성한 후 결과를 확인해 보자.




print(sess.run(correct_prediction, feed_dict={x:mnist.test.images, y_:mnist.test.labels}))




accuracy는 위의 boolean 배열을 True일 경우에는 1 False일 경우에는 0으로 변환 한 후 평균을 구한 값이다. 해당 결과를 확인해 보자.


print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_:mnist.test.labels}))


결과는 0.9235가 나온다.


mnist.test.images와 mnist.test.labels의 실제 값들을 직접 보고 싶다면 아래와 같이 작성한 후 확인한다. 실제로 1000번을 돌려서 해봤는데 jupyter가 뻗을뻔 했다... (print가 공수가 많이 드는 로직인가...)

range를 1로만 주고 확인해보자.


for i in range(1):

    batch_x, batch_y = mnist.test.next_batch(100)

    diff_a = sess.run(tf.argmax(y,1), feed_dict={x:batch_x})

    diff_b = sess.run(tf.argmax(y_,1), feed_dict={y_:batch_y})


    print diff_a

    print diff_b




조금 더 보기 편하게 하기 위해 아래와 같이 수정하였다.


for i in range(2):

    result_boolean = []

    batch_x, batch_y = mnist.test.next_batch(9)

    diff_a = sess.run(tf.argmax(y,1), feed_dict={x:batch_x})

    diff_b = sess.run(tf.argmax(y_,1), feed_dict={y_:batch_y})

    print "sample output : " + str(diff_a)


    for k in range(9):

        if diff_a[k] == diff_b[k]:

            result_boolean.append("T")

        else:

            result_boolean.append("F")

    print "compare : " + str(result_boolean)


    plt.figure(i)

    coordi = [191, 192, 193, 194, 195, 196, 197, 198, 199]


    for index, image in enumerate(batch_x):

        image.shape(28,28)

        plt.subplot(coordi[index])

        plt.imshow(image)

print "sample input : "