/**
2017.02.02 작성자 : 3개월
Tensorflow - MNIST 단일 계층 신경망 트레이닝
*/
환경 : windows tensorflow, anaconda 4.3, python 3.5
Tensorflow를 통해 단일 계층 신경망을 구축하고 MNIST 데이터를 training하고 test 하는 예제입니다. 많은 예제들이 tensorflow 내부에 example로 있는 MNIST 데이터를 이용하지만 이 포스팅에서는 외부에 있는 MNIST pickle 파일을 다운로드하고 이를 읽어들여 모델을 구축해보았습니다. 사용한 MNIST pickle 파일은 https:na//github.com/mnielsen/neural-networks-and-deep-learning/blob/master/data/mnist.pkl.gz 이곳에서 다운로드 받을 수 있습니다.
1. 데이터 읽기
import tensorflow as tf
import gzip, numpy
import pickle as cPickle
import pandas as pd
# Load the dataset
f = open('data/mnist.pkl', 'rb')
train_set, valid_set, test_set = cPickle.load(f, encoding='latin1')
f.close()
x_train, y_train = train_set
x_test, y_test = test_set
pickle로 불러온 변수에는 train_set, valid_set, test_set이 나뉘어져 있기 때문에 위와 같이 load하면 알아서 training, validation, test set으로 나눌 수 있습니다. 또한 각각은 튜플 자료구조형으로 또 다시 x, y로 나뉘어져 있기 때문에 x_train, y_train = train_set 구문을 통해 feature과 label을 분리할 수 있습니다. 이렇게 데이터를 빠르게 training을 적용할 수 있는 형태로 만들어낼 수 있다는 것이 pickle 파일의 좋은 점입니다. 하지만 예제가 아닌 실제 머신러닝 모델을 구축할 때는 이러한 과정을 모두 직접하여야 합니다. 이미지 파일을 읽어들여야하고 또 이것을 적절한 사이즈의 numpy array로 바꾸어야합니다.
2. 데이터 전처리
y_train = pd.get_dummies(y_train)
y_test = pd.get_dummies(y_test)
label들을 one hot encoding합니다. tensorflow에서는 label을 one hot encoding 하여 제공하여야 합니다.
print('x_train shape : ',x_train.shape) # (50000, 784)
print('y_train shape : ',y_train.shape) # (50000, 10)
print('x_test shape : ',x_test.shape) # (10000, 784)
print('y_test shape : ',y_test.shape) # (10000, 10)
training data와 test data의 형태(shape)를 프린트하여 보겠습니다. MNIST 데이터는 28*28 흑백 이미지이므로 총 784개의 픽셀이 있는데 이를 feature로 보아 784개의 feature가 있음을 알 수 있습니다.
[mnist 데이터 모양]
또한 training data의 갯수는 50000개이기 때문에 x_train의 모양은 50000*784 입니다. label의 갯수도 50000개이며 class의 갯수가 0~9까지 10개이므로 y_train은 50000*10입니다. 만약 label이 1인 경우 y는 [0 1 0 0 0 0 0 0 0 0 0] 으로 encoding 됩니다. y_train에는 이러한 y가 50000개 있다고 생각하면 됩니다.
3. 모델 작성
x = tf.placeholder('float', [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('float', [None, 10])
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
윗 부분은 데이터를 읽어들이고 numpy와 pandas를 이용하여 데이터를 전처리 한 것이었다면 이제부터는 본격적으로 tensorflow 코드를 작성하여 보겠습니다.
x = tf.placeholder('float', [None, 784])
우선 placeholder을 이용해 x가 들어갈 공간을 만들어줍니다. 이 때 [None, 784]에서 None은 데이터의 갯수는 자유롭게 설정하겠다는 뜻입니다. 하지만 feature의 갯수 784는 고정되어 있습니다.
W = tf.Variable(tf.zeros([784,10]))
그 다음 weight matrix를 만드는데 input layer의 노드가 784개이고 output layer의 노드가 10개이므로 총 784*10개의 weights가 필요한 것을 알 수 있습니다. 이를 위해 W라는 tensorflow 변수를 만들고 0으로 초기화 시킵니다.
b = tf.Variable(tf.zeros([10]))
bias는 output layer의 node수만큼 필요하므로 10개를 만들고 0으로 초기화 시킵니다.
y = tf.nn.softmax(tf.matmul(x, W) + b)
그 다음 x와 W를 매트릭스 곱셈을 하고 bias를 더한 것에 softmax 함수를 취하여 이를 y 변수로 만들어줍니다. 수식으로 표현하면 y = softmax(xW+b) 이 됩니다. 이 때 y는 '예측값' 입니다.
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
손실 함수로는 classification의 경우 크로스 엔트로피를 사용하게 됩니다. -y_*log(y) 에는 데이터들의 크로스 엔트로피들의 2차원 텐서가 담겨져 있습니다. reduce_sum 함수를 통해 이 크로스 엔트로피들의 평균을 구합니다. 이 때 reduce_sum 함수는 지정된 차원을 따라 평균을 내는 것입니다. 2차원인 경우 지정할 차원이 1개 밖에 없으므로 차원을 따로 지정한 필요가 없습니다. 그래서 이 경우에는 reduce_sum 함수는 평균을 구한다고 생각하시면 됩니다.
4. 배치 트레이닝
sess = tf.Session()
sess.run(tf.global_variables_initializer())
batch_size = 100 # 배치 사이즈 지정
training_epochs = 3 # epoch 횟수 지정
# epoch 횟수만큼 반복
for epoch in range(training_epochs) :
batch_count = int(x_train.shape[0]/100) # 배치의 갯수
for i in range(batch_count) : # 배치를 순차적으로 읽는 루프
# 배치사이즈 만큼 데이터를 읽어옴
batch_xs, batch_ys = x_train[i*batch_size:i*batch_size+batch_size], y_train[i*batch_size:i*batch_size+batch_size]
# training, 이를 통해 W, b Variable 값을 조정함
sess.run(train_step, feed_dict = {x : batch_xs, y_ : batch_ys})
# [True False True False False True True ....] 와 같은 boolean vector
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
# [1 0 1 0 0 1 1 ...]로 변환 후 평균값을 구하면 accuracy 구할 수 있음
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
# 5번 배치를 읽고 이를 트레이닝한 후 정확도 출력
if i % 5 == 0 :
print(str(epoch+1)+' epoch '+ str(i)+' batch ', sess.run(accuracy, feed_dict={x: x_test[0:100], y_: y_test[0:100]}))
이 부분을 이해하기 위해서는 우선 배치 트레이닝에 대하여 알아야 합니다. 배치 트레이닝이란 트레이닝 데이터를 일정 사이즈로 쪼갠 미니 배치(mini batch)를 통해 Gradient Descent를 하는 방법입니다. 일반적인 Gradient Descent 방법은 전체 트레이닝 데이터를 한 번 쭉 보고 gradient를 구한 후 weights와 bias를 '한 번' 업데이트 시킵니다. 하지만 이런 방법은 computation cost가 높습니다. 하지만 미니 배치를 이용한 방법은 전체 데이터의 샘플인 미니 배치를 통해 전체 데이터를 근사 시킵니다. 그리고 이것만 보고 gradient를 구한 후 weight, bias를 업데이트 시킵니다. 예를 들어 배치 사이즈가 128이면 128개의 데이터 샘플들만 보고 gradient를 구한 후 weight, bias를 업데이트 하게 됩니다. 이전 방식에 비해 훨씬 빠르게 weight를 업데이트 시킬 수 있죠. 이렇게 weight를 구하는 방식은 Stochastic Gradient Descent 방법이라고 부릅니다. 실제로 일반적인 Gradient Descent 방법보다는 Stochastic Gradient Descent 방법이 더 많이 쓰입니다. 팁으로는 배치 사이즈는 메모리가 허용하는 범위 내에서 최대한 크게 잡는 것이 좋습니다. 그리고 보통 배치사이즈는 128, 256, 512와 같이 2의 배수로 잡는 경우가 많은데 혹자는 이를 CPU 연산의 효율을 증가시킨다고 합니다. 하지만 111, 234와 같이 아무 배치 사이즈나 잡아도 트레이닝은 잘 합니다.
이러한 배치 트레이닝의 방식을 이해한다면 위의 텐서플로우 코드도 어렵지 않게 이해할 수 있습니다.
sess = tf.Session()
sess.run(tf.global_variables_initializer())
세션을 만들고 variable을 초기화 시킵니다.
- 텐서플로우에서 Variable은 반드시 위와 같이 초기화 시키고 이용해야합니다.
batch_size = 100 # 배치 사이즈 지정
training_epochs = 3 # epoch 횟수 지정
배치 사이즈와 epoch를 정합니다. epoch은 '전체 데이터를 보는 횟수' 입니다.
# epoch 횟수만큼 반복
for epoch in range(training_epochs) :
batch_count = int(x_train.shape[0]/100) # 배치의 갯수
for i in range(batch_count) : # 배치를 순차적으로 읽는 루프
# 배치사이즈 만큼 데이터를 읽어옴
batch_xs, batch_ys = x_train[i*batch_size:i*batch_size+batch_size], y_train[i*batch_size:i*batch_size+batch_size]
# training, 이를 통해 W, b Variable 값을 조정함
sess.run(train_step, feed_dict = {x : batch_xs, y_ : batch_ys})
# [True False True False False True True ....] 와 같은 boolean vector
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
# [1 0 1 0 0 1 1 ...]로 변환 후 평균값을 구하면 accuracy 구할 수 있음
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
# 5번 배치를 읽고 이를 트레이닝한 후 정확도 출력
if i % 5 == 0 :
print(str(epoch+1)+' epoch '+ str(i)+' batch ', sess.run(accuracy, feed_dict={x: x_test[0:100], y_: y_test[0:100]}))
이제 마지막 트레이닝 파트입니다. 미니 배치를 구할 때 전체 데이터에서 샘플 데이터를 랜덤으로 뽑는 방식도 있지만 여기서는 그냥 순차적으로 배치를 만들었습니다. 0번부터 100번까지를 1배치, 101번부터 200번까지를 2배치와 같은 방식으로 말입니다. (사실 데이터의 순서에 어떠한 규칙이 있다면 이러한 방식은 좋지 않을 수 있습니다.) epoch만큼 전체 데이터를 보아아햐고 또 배치 사이즈만큼 전체 데이터를 쪼개야하니까 이중 for문을 써야하는 것을 알 수 있습니다. 그리고 두 번째 for문에서는 i라는 인덱스와 batch_size라는 변수를 이용하여 원하는 만큼 데이터를 계속해서 불러온후 Gradient Descent를 하면됩니다. 각각의 문장이 의미하는바는 코드에 주석으로 써두었습니다.
5. 결과
배치 트레이닝을 100번 했을 때 이미 정확도가 0.93에 육박하는 것을 알 수 있습니다.
트레이닝이 끝났을 때의 최종 정확도는 0.96 이었습니다. 이미 100번 트레이닝을 했을 때 정확도가 0.93이었기 때문에 그 이상의 training은 의미가 없었을지도 모릅니다. 지금은 validation과정 없이 마구잡이로 트레이닝을 했지만 보통은 언제 training을 끝내야하는지 잘 선택하여야합니다. 계속되는 불필요한 training은 overfitting을 유발할 수 있기 때문입니다.