머신러닝

6-28 RNN 알고리듬에 의한 MNIST 머신러닝

coding art 2019. 9. 22. 12:17
728x90

 

 

 

지난해 겨울 텐서플로우 OpenCV 출간 이후 7개월 만에 2권에 해당하는 “Scikit PyTorch 머신러닝을 출간하게 되었다. 각권이 450 페이지이므로 합 900 페이지에 달하는 내용이라 무슨 머신러닝을 공부하는데 분량이 왜 이렇게 많은가? 하고 의문을 가질 수도 있겠으나 그 내용이 튜토리얼성에 가까워 사실 그렇게 큰 부담은 없는 책이다. 물론 책 내부에 파이선 코드를 끼워 넣는 먹통 짓은 지금 세상에서는 할 필요가 없을 것이다. 해당 책의 머리말에 써둔 하이퍼링크 목차를 다운받으면 블로그를 직접 열어 볼 수 있으며 거기서 예제 코드를 다운 받을 수 있다.

 

 

1권에 해당하는 텐서플로우 OpenCV 머신러닝에서는 20179월경부터 201812월 사이에 머신러닝을 이해하고자 하는 필자의 열공(?) 내용을 담아 보았다면 2권에서는 1권에서 제기되었던 여러 내용들에 대해서 해답을 찾아가는 내용들을 꽤 많이 포함하였다. 2권이라고 해서 내용적으로 완전히 정리된 것은 아니기 때문에 다시 3권의 출발점이 될 수도 있을 것이다.

 

다소 아쉬운 점은 흥미 위주로 시작했던 1, 2권의 Softmax 관련 내용을 완전히 정리하지는 못했는데 이 그 이유는 R&D 영역으로 넘어갔기 때문이다. 2020년에는그 내용까지도 포함하여 출간할 계획이다.

 

본 서의 출간 목적은 작가들과 출판사가 염원하는 베스트셀러 화가 목표가 아니다. 사회적으로 인공지능(머신러닝)에 대한 이해 필요성이 점증하는 시기이며, 인공지능 분야의 발전 속도가 상당히 빠르기 때문에 그에 맞춰서 비전공자라 할지라도 머신러닝에 입문해 볼 수 있도록 경험과 생각을 공유해 보고자 하는 것이다.

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++MNIST  수기문자의 판독율을 높이기 위해 고안된 수많은 알고리듬들 중에 RNN을 사용해 보자. 하나의 MNIST 문자 데이터는 28X28 2차원 흑백 데이터로 구성된다. 전통적인 가장 간단한 네트워크 수준에서는 784개의 데이터를 사용하여 뉴럴 네트워크를 구성하여 학습을 시키며, Convolution 필터 기법을 사용 시에는 28X28 데이터를 패딩 후 3X3 필터를 적용하여 스트라이딩(Striding) 시키게 된다.

 

이와 유사하게 MNIST 문자 데이터를 RNN을 사용하여 처리하려면 28X28 데이터 한 세트를 28개 스텝 X 28줄 데이터로 구성하자. 28개의 스텝은 펼쳐진(unrolled) 28개의 Cell을 의미하며 28줄의 데이터는 입력 데이터의 dimension 28임을 뜻한다.

 

 

 

랜덤한 증권가격 예측 RNN 문제를 참고해 보면 스텝은 대개 일주일 단위를 한 묶음으로 sequenth_length를 잡는데 즉 7개의 Cell을 사용하여 스텝을 구성한다. 아울러 당리의 시가, 고가, 저가, 거래량 및 종가를 대상으로 입력 데이터 dimension 5가 된다. 물론 증권가격 예측 RNN 문제는 Time Series 문제이므로 CNN Striding 작업과 유사하게 time step = 1일로 두어 이동시키면서 학습을 시키게 된다. 아울러 7:1 many to on 모델에 의거 7 거래일 이후의 8일째 알려진 종가를 학습 데이터로 사용하게 된다. 비록 사용하는 5종의 데이터 수가 1,000 개인데 이 전체가 batch_size = 1에 해당한다.

 

반면에 MNIST 문자 판독에서는 28개 스텝 X 28줄 데이터 세트 하나를 기준으로 흔히 batch_size = 100 으로 잡아 학습을 시키며 증권가격 예측 RNN에서처럼 time step 증가에 따른 이동 계산은 없다.

 

MNIST RNN 코딩에서 tensoflow 외에 tensorflow.contrib rnn 라이브러리 모듈 사용이 필수적이다. tf.reset_default_graph() 명령은 Back end에서 코드를 재 실행 할 때에 컴퓨터 Back end 메모리에 저장된 Graph를 삭제하는 역할을 한다. time_steps Time Series 문제가 아니기 때문에 스텝 수를 나타내며 데이터의 줄 수는 n_input = 28 로 설정한다. 문자 수준 RNN 예제에서는 input_dim 이라는 변수명을 흔히 쓰기도 한다.

num_units = 64 LSTM 내부에서 설정하는 hidden layer 의 출력 수로서 임의 설정이 가능하지만 MNIST 문제의 accuracy를 최고로 올리기 위해서는 64 가 경험적으로 최적 값이지만 사용자 필요에 의해서 임의로 설정할 수 있다.

  

 

 

learning_rate = 0.001 tensorflow 코드에서 AdamOptimizer를 사용할 경우에 Default 값이다. AdamOptimizer 내부적으로도 momentum = 0.999 epsilon = 1.0 e-08 Default 값이 사용된다. n_classes MNIST 수기 문자의 종류 수인 즉 09 까지의 10개 클라스 값을 의미한다. batch_size = 100 MNIST 수기문자 판독에서 사용하는 6만개 데이터 중 임의 추출한 한 묶음 단위로서 경험적으로 최적 값이라 볼 수 있다.

 

MNIST 데이터 베이스를 x y placeholder로 읽어 와서 feed 하도록 하자. x 데이터는 이미 None, time_steps X n_input 로 설정하였기 때문에 shape rank 값이 3이다. rank 3x 데이터를 rnn.static_rnn에서 사용이 불가능하므로 rank 값을 1만큼 rank 값의 차원 낮춰줄 필요가 있다. unstack이란 쌓아 놓은 것을 허문다는 의미이다. time_steps X n_input 한 묶음을 풀어서 time_steps n_input 수만큼의 개별 스텝들로 해체한다는 뜻이다.

 

 

 

 

Session 이전 단계에서 아래와 같이 input을 출력해 보면 그 의미를 알 수 있다. shape=(?,28) 이므로 rank 값은 2 이다. 여기에서 물음표 ?None을 뜻하며 그 구체적인 값은 60,000 일 것이다.

 

 

 

tensorflow.contrib rnn 라이브러리 모듈에서 rnn.static_rnn을 가져다 사용하는 반면에 tensorflow 에서는 tf.nn.dynamic_rnn 을 사용하는데 dynamic_rnn rank 값은 3이다. 하지만 MNIST 문자 판독은 문자 레벨 RNN 과는 달리 rank 2이면 충분하므로 굳이 tf.nn.dynamic_rnn을 사용할 수 있도록 코드를 수정할 필요는 없다. 사실 수정 자체가 되질 않는다는 점에 유의하자. 그 이유는 증권가격 RNN 문제에서는 Time Series 문제이기 때문에 time step 방향으로 Striding이 있음으로 인해서 rank 하나가 더 필요하다는 점이다. 반면에 MNIST 문제는 이미지 카드 수는 많을지도 모르겠으나 그 카드들의 순서를 바꾸어도 전체를 다 읽어 들이기 때문에 아무런 문제가 되지 않는다. Time Series 문제가 아니란 점이다.

rnn.static_rnn에서 생성된 outputs[-1] 28개의 성분 숫자 값을 가지며 램덤 넘버 W0 와 램덤 바이아스 b0를 사용하여 prediction(hypothesis)을 계산한다.

 

변수 X_img 는 차후에 CNN 코드의 하이브리드 형 코드를 작성하기 위해 인위적으로 input을 사용하여 reshape 작업을 시험적으로 해보았다. 본 코드와는 아무런 관련이 없음에 유의하자.

 

학습을 위한 loss 함수 구성을 위해 사용하는 prediction 과 라벨 값을 나타내는 y 는 상호 연산을 위해 shape 값이 동일해야 할 필요가 있다. AdamOptimizer 가 사용된다.

 

코드의 실행은 다소 시간이 거릴 수 있으므로 가급적 GPU 컴퓨팅 지원이 되는 구글 Colabo를 사용하기로 하자.

 

2만번 학습에 98.6%, 5만번 학습에 98.7% 수준으로 거의 CNN 에 필적하는 성능으로 볼 수 있다.

 

#mnist_rnn_analysis_01.py

# MNIST 데이터를 다운로드 한다.
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

# TensorFlow 라이브러리를 추가한다.
import tensorflow as tf
from tensorflow.contrib import rnn

tf.reset_default_graph()
time_steps = 28 #unrolled through 28 time steps( or sequence_length)
num_units = 64  #hidden LSTM units

n_input = 28    #rows of 28 pixels
learning_rate = 0.001 #learning rate for adam
n_classes = 10
batch_size = 100 #size of batch

#input placeholders
x = tf.placeholder("float",[None,time_steps,n_input])
y = tf.placeholder("float",[None,n_classes])
#input tensor from [batch_size,n_steps,n_input]
#to "time_steps" number of [batch_size,n_input] tensors
input = tf.unstack(x, time_steps, 1)

#defining the network
lstm_layer = tf.contrib.rnn.BasicLSTMCell(num_units, forget_bias=1)
outputs,_states = rnn.static_rnn(lstm_layer, input, dtype="float32")

#weights and biases of appropriate shape to accomplish above task
W0=tf.Variable(tf.random_normal([num_units,n_classes]))
b0=tf.Variable(tf.random_normal([n_classes]))

#converting last output of dimension [batch_size,num_units]
#to [batch_size,n_classes] by out_weight multiplication
prediction = (tf.matmul(outputs[-1], W0) + b0)
X_img = tf.reshape(input, [-1, 28, 28, 1])

#identify shapes
print(X_img)
print(prediction)
print(input)
print(_states)
print(y)

#loss_function
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits
                      (logits=prediction,labels=y))
#optimization
opt = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)

#model evaluation
correct_prediction = tf.equal(tf.argmax(prediction,1),tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

#initialize variables
init=tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
    iter=1
    while iter<30001:

        batch_x, batch_y = mnist.train.next_batch(batch_size=batch_size)
        batch_x = batch_x.reshape((batch_size, time_steps, n_input))
        sess.run(opt, feed_dict={x: batch_x, y: batch_y})

        if iter % 1000 == 0:
            print("For iter ",iter)

        iter=iter+1


#calculating test accuracy
    test_data = mnist.test.images.reshape((-1, time_steps, n_input))
    test_label = mnist.test.labels
    print("Testing Accuracy:", sess.run(accuracy, feed_dict={x: test_data, y: test_label}))