티스토리 뷰

빅데이터

MNIST 분석

한 혜윰 2017. 9. 5. 14:50


MNIST Database 분석





사진 출처: Kepler-mapper Github


안녕하세요. 마지막 포스트에 MNIST 데이터베이스 시각화를 해봤으니 이번 포스트에서는 텐서플로를 이용한 분석과 텐서보드를 이용한 시각화를 해보겠습니다! 코딩을 하기 전에 짚고 넘어가야할 중요한 컨셉들이 있습니다. 바로 로짓 회기 분석(logits), 소프트 맥스(softmax), 크로스 엔트로피(cross entropy) 그리고 기울기 하강 알고리즘(Gradient descent optimization) 입니다. 천천히 살펴보자면, 로짓 회기 분석은 통계학의 분석 방법 중 하나로써 입력값이 연속적이지 않고 범주형일때 사용됩니다. 예를들어 독립 변수가 예/아니오, 생존/사망(이항)이거나 마름/통통함/비만(명목형), 초등학생/중학생/고등학생(순서형)일 때 자주 이용합니다. 핵심은 각 변수들의 확률이 주어졌을 때 그 확률의 특이성(승산비: odds ratio)을 로그화 한게 로짓입니다. 이항 로짓 공식을 선형화 하면 다음과 같습니다.





p는 변수의 확률로써 p/(1-p)는 승산비를 의미합니다. 즉 식은 승산비의 로그화 입니다. 이 식을 다항화(독립 변수가 여러개 일 때) 하면 다음과 같습니다.





함수 는 i 관측에 대한 k 결과입니다. 각 사진 숫자의 픽셀은 28x28=784 그러므로 편차값(bias)까지 더하면 총 M은 785의 값을 가짐을 알 수 있습니다. 다음 그래프에서 볼 수 있다시피 x축은 확률값을 가지며 값은 0 이상 1 이하입니다.


In [20]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(0.01,0.99,100)
Y = np.log(X/(1-X))
plt.plot(X,Y)
plt.axhline(y=0, color='k', linestyle='--', linewidth=1)
plt.xlabel('Possibility', fontsize=14)
plt.ylabel('Log odds', fontsize=14)
plt.axis([0, 1, -5, 5])
plt.show()


y축 값은 연속되는 실수의 값입니다. 이렇게 이 로짓 공식을 사용해 범주형이라든지 순서형을 컴퓨터가 인식하기 좋게 연속되는 실수의 값으로 변환시켜 줍니다. 이렇게 나온 값에 다음과 같이 소프트맥스 공식을 적용시켜 정규화 합니다. 





소프트맥스는 여러 관점에서 일반적인 정규화, 예를들어 표준편차, 보다 머신 러닝에서 더 적합합니다. 확률론적인 관점에서 소프트맥스 적용 은 원본에 가까운 데이터를 산출합니다. 로짓은 확률의 로그값이고 소프트맥스는 이를 다시 거듭 제곱한 값 입니다. 따라서 이는 최대우도추정(MLE: Maximum Likelihood Estimation)을 따라 원본에 가장 가깝습니다. 이렇게 로짓에 적용한 소프트맥스가 아니더라도 컴퓨터로 계산을 하면 표준편차에 비한 소프트맥스의 장점을 바로 볼 수 있습니다.



출처: Stackoverflow


첫번째 사진에서 볼 수 있다시피 소프트맥스를 적용하면 정규화 전 데이터 값의 차이가 적어도 차이가 확연하게 드러납니다. 데이터의 분포도가 크면 정규화 후에 차이는 더욱더 선명하게 드러남을 볼 수 있습니다. 또한 정보화 이론적 관점에서 소프트맥스는 크로스-엔트로피(Cross-Entropy)를 낮춘다는 장점이 있습니다. 크로스 엔트로피의 공식은 다음과 같습니다.





여기서 p(x)는 실제 확률분포(하지만 실질적으로 데이터의 분포를 사용합니다), q(x)는 예측한 확률 값입니다. 엔트로피는 정보의 불확실성의 정도이며 크로스-엔트로피는 현실적 한계에 의해 높아진 엔트로피 값입니다. 예를들어 여러가지 색의 동전이 주머니에 있을 때 각 동전의 비율을 알면 좀 더 효율적인 방법으로 동전의 색을 판별할 수 있겠죠. 이렇게 좀 더 효율적인 방법을 쓰면 동전을 알아내는 방식의 정보에 대한 엔트로피가 낮아집니다. 크로스-엔트로피는 이 엔트로피에 대한 예측 값이라 할 수 있습니다. 그렇기에 크로스-엔트로피는 언제나 엔트로피보다 클 수 밖에 없습니다. 크로스-엔트로피에 대해선 박효균씨의 블로그Quora에서 좀 더 자세히 살펴볼 수 있습니다. 결국 MNIST 인식은 이 크로스-엔트로피를 최대한 줄이는것에 달려있습니다. 이는 여러가지 방법이 있는데 이번 포스트에서는 경사 하강 방식을 이용한 최적화(Gradient Descent Optimizer)를 사용하겠습니다. 이는 다른 이론들에 비해 이해하기 간단합니다. 원리는 한 데이터에서 에러를 구하고 그 지점에서 경사를 구해 에러값이 낮은쪽으로 나아가는 것 입니다.


출처: mlxtend



그래프에서도 한 지점의 가중치(weight)에서 경사(gradient)를 구해 최저점(Global cost minimum)으로 진행해 감을 볼 수 있습니다. 좀 더 자세한 설명은 오레도아님의 블로그 포스트를 읽어보시면 됩니다. 경사 하강 방식이 최고는 아닙니다. 모든 함수가 위의 그래프처럼 이상적인 U를 지닐 순 없고 계곡이 생겨 저 원이 중간에 멈출 수 도 있습니다. 이를 방지하기 위해 여러가지 방법, 예를들어 알고리즘의 진행 단계 크기(step size)를 조정하는 방법이 있지만 이 포스트에서는 가장 기본적인 방법, 일정한 진행 단계 크기만 사용 하겠습니다.


텐서플로를 이용해 MNIST분석을 하기 전에 텐서플로의 특징에 대해 몇 가지만 언급 하겠습니다. 텐서플로는 노드와 그래프 바탕으로 돌아갑니다. 그래프를 생성하고 그 위에 연산 노드를 작성해 계산을 합니다. 노드가 복잡해지면 위해 그래프의 일부분을 다른 gpu에 할당할 수 있습니다. 또한 수많은 노드를 포함하는 그래프를 시각화 할 수 있는 텐서보드를 지원하기에 텐서플로는 다른 딥러닝 프레임에 비해 많은이들의 관심을 받고 있습니다.








위에서 볼 수 있다시피 2015년 까지는 케라스와 토치가 관심을 받고 있다면 현재는 텐서플로가 가장 많은 관심을 받고 있습니다. 그러면 텐서플로를 이용한 MNIST분석을 시작하겠습니다! 노드와 하이퍼파라미터(hyperparameter: 미리 지정해줘야 하는 매개 변수)부터 작성하겠습니다.



In [29]:
#필요한 hyperparameter
learning_rate = 0.01
batch_size = 128
n_epochs = 30

#홀더.
X = tf.placeholder(tf.float32, shape=[batch_size, 784], name='X_placeholder')
Y = tf.placeholder(tf.float32, shape=[batch_size, 10], name='Y_placeholder')

#weights and bias
W = tf.Variable(tf.random_normal(shape=[784, 10], stddev=0.01), name='weights')
b = tf.Variable(tf.zeros([1, 10]), name='bias')

#logits
logits = tf.matmul(X,W) + b

#loss
entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=Y, name='loss')
loss = tf.reduce_mean(entropy)



머신러닝은 트레이닝 기간이 여러 세대(epoch)에 걸쳐 있습니다. 한 세대마다 모든 트레이닝 데이터를 사용하지만 좀더 정확도를 높이기 위해 이를 반복합니다. 저는 n_epochs에 따라 30세대에 걸쳐 트레이닝을 하겠습니다. 또 한 세대마다 batch_size가 128개니 트레이닝마다 128개의 데이터를 이용하겠습니다. 그리고 위에서 설명한 경사 하강 최적화에서는 각 트레이닝마다 learning_rate 만큼 진행하겠습니다. 그리고나서 placeholderVariable 노드를 만듭니다. placeholder는 데이터를 그래프와 다른 노드에 공급하기 위한 노드입니다. 나중에 보시면 알겠지만 MNIST 트레이닝 데이터는 X로, 레이블은 Y로 입력됩니다. 그리고 가중치와 편중치,W, b, Variable 노드를 만듭니다. XW 노드를 내적한 값에 b를 더하는 연산노드 logits을 만들고 이에 따른 소프트맥스와 크로스-엔트로피 값을 계산하는 entropy노드 그리고 이 엔트로피의 평균을 구하는 loss노드를 생성합니다. 그리고 경사 하강 알고리즘을 따라 최적화 된 값을 구하는 노드를 만듭니다.



In [30]:
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)



필요한 노드는 전부 만들었습니다. 이제 트레이닝을 할텐데 하기 전에 트레이닝을 분류하기 위해 필요한 모듈을 가져오겠습니다.



In [31]:
from datetime import datetime
import time

now = datetime.now().strftime("%Y%m%d%H%M")
root_logdir = "/Users/yoon/Desktop/87_105_110_107/TP/MNIST/graphs"
logdir = "{}/run-{}/".format(root_logdir, now)



datetime 모듈은 보시다시피 당일의 년도, 월, 일, 시간, 분을 구합니다. time 모듈은 1970년 1월 1일 0시 0분 0초 부터 지금까지의 시간을 초 단위로 보여줍니다. 이를 이용해 트레이닝을 하는데 얼마만큼의 시간이 소모됬는지 계산할 수 있습니다. root_logdir은 나중에 텐서플로 그래프를 시각화 하기 위한 파일을 저장하는 장소 기본 장소고 logdir은 그래프 파일을 따로 저장하기 위한 변수입니다. 그래프 파일을 같은 파일에 저장하면 모든 그래프가 동시에 열리기에 이렇게 따로 그래프를 저장해야 합니다. 텐서플로에 있는 Variable 노드를 구동할려면 세션(session) 명령어를 사용해야 합니다. 



In [32]:
with tf.Session() as sess:
    writer = tf.summary.FileWriter(logdir, sess.graph)
    
    start_time = time.time()
    sess.run(tf.global_variables_initializer())
    #변수 설정
    n_batches = int(mnist.train.num_examples/batch_size)
    for i in range(n_epochs): #모델을 n_epochs 만큼 훈련
        total_loss = 0
        
        for _ in range(n_batches):
            X_batch, Y_batch = mnist.train.next_batch(batch_size)
            _, loss_batch = sess.run([optimizer, loss], feed_dict={X: X_batch, Y:Y_batch})
            total_loss += loss_batch
            
        print('Epoch {}  평균 손실: {:3.5f}'.format(i, total_loss/n_batches))
        
    print('총 소요 시간: {:5.2f}초'.format(time.time() - start_time))
    
    print('최적화 완료!')
    
    #테스트!
    
    preds = tf.nn.softmax(logits) #정규화
    correct_preds = tf.equal(tf.argmax(preds, 1), tf.argmax(Y, 1))
    accurate_ones = tf.reduce_sum(tf.cast(correct_preds, tf.float32))
    
    n_batches = int(mnist.test.num_examples/batch_size)
    total_correct_preds = 0.0
    
    for i in range(n_batches):
        X_batch, Y_batch = mnist.test.next_batch(batch_size)
        accurate_ones_batch = sess.run(accurate_ones, feed_dict={X: X_batch, Y:Y_batch})
        total_correct_preds += accurate_ones_batch
        
    print('정확도: {:1.5f}'.format(total_correct_preds/mnist.test.num_examples))
    
    writer.close()
Epoch 0  평균 손실: 1.29025
Epoch 1  평균 손실: 0.73329
Epoch 2  평균 손실: 0.60095
Epoch 3  평균 손실: 0.53721
Epoch 4  평균 손실: 0.49849
Epoch 5  평균 손실: 0.47092
Epoch 6  평균 손실: 0.45153
Epoch 7  평균 손실: 0.43658
Epoch 8  평균 손실: 0.42402
Epoch 9  평균 손실: 0.41330
Epoch 10  평균 손실: 0.40450
Epoch 11  평균 손실: 0.39722
Epoch 12  평균 손실: 0.38955
Epoch 13  평균 손실: 0.38601
Epoch 14  평균 손실: 0.37847
Epoch 15  평균 손실: 0.37416
Epoch 16  평균 손실: 0.37203
Epoch 17  평균 손실: 0.36604
Epoch 18  평균 손실: 0.36283
Epoch 19  평균 손실: 0.36069
Epoch 20  평균 손실: 0.35631
Epoch 21  평균 손실: 0.35285
Epoch 22  평균 손실: 0.35225
Epoch 23  평균 손실: 0.34984
Epoch 24  평균 손실: 0.34519
Epoch 25  평균 손실: 0.34509
Epoch 26  평균 손실: 0.34159
Epoch 27  평균 손실: 0.34153
Epoch 28  평균 손실: 0.33977
Epoch 29  평균 손실: 0.33514
총 소요 시간: 17.06초
최적화 완료!
정확도: 0.91180



세션 코드를 각 노드를 위해 코드를 작성할 수 있지만 그렇게 하는 것 보다 이렇게 sess 블락을 만들어 활용하는게 보기도 좋고 이해하기도 쉽습니다. 또한 세션을 다 이용했으면 세션을 꺼야하는데 이렇게 블락을 이용하면 그렇게 따로 코드를 작성할 필요도 없습니다. 우선 가장 먼저 작성해야 하는게 나중에 텐서보드를 위한 시각화를 위해 필요한게 tf.Summary.FileWriter 입니다. 파일 저장 장소는 logdir 이고 sess.graph를 텐서보드 연동 파일로 출력합니다. 이 세션이 끝나기 전에 이 FilerWriter를 꺼줘야 합니다. 그리고 한번에 Variable 노드 초기화를 할 수 있도록 sess.run(tf.global_variable_initializer())를 작성합니다. 그리고 트레이닝을 시작합니다. 텐서플로의 장점은 이렇게 노드 여러개를 작성하고 필요한 노드만 실행해도 그 노드에 필요한 노드들도 다 같이 실행됩니다. sess.run([optimizer])만 필요하죠. 그리고 데이터 입력은 간편하게 feed_dict로 데이터를 입력하면 됩니다. 하지만 저희는 정확도를 출력하기 위해 loss 또한 수동적으로 실행해 값을 추출합니다. 트레이닝을 바탕으로 최적화 시킨 가중치와 편중치를 이용하면 정확도는 0.91180 이네요. 그럼 그래프는 어떻게 작성됬는지 확인해 보겠습니다. 그래프 파일이 /Users/yoon/Desktop/87_105_110_107/TP/MNIST/graphs/run-201708161503_MNIST_BASIC에 저장됬습니다. 시각화를 위해 터미널에서 텐서보드를 구동합니다.



Yoon-Macbook-Pro:~yoon$ tensorboard --logdir=/Users/yoon/Desktop/87_105_110_107/TP/MNIST/graphs/run-201708161503_MNIST_BASIC



그리고 나서 인터넷 주소창에 localhost:6006를 치면 그래프가 시각화 되어 나타납니다!





시간이 있으시면 그래프를 한번 둘러보세요. Input/Output/attributes 등과 데이터의 흐름을 볼 수 있습니다. 하지만 이 그래프를 개선할 여지가 많이 남아있습니다. tf.name_scope를 이용하면 좀 더 간단하고 명료하게 그래프를 표현할 수 있습니다. 이는 다음 포스트에서 다루겠습니다.


코드는 이곳에서 찾아볼 수 있습니다.



참조

www.tensorflow.org

https://github.com/ageron/handson-ml

Stanford CS20 SI

'빅데이터' 카테고리의 다른 글

MNIST 데이터베이스 시각화  (0) 2017.11.06
MNIST 인식  (0) 2017.08.21
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
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 30
글 보관함