19. 학습 rate, training-test 셋으로 성능평가 (lab 07)


이번 동영상에서는 learning rate의 중요성과 mnist 모델에 대한 성능을 측정해 본다. 드디어 조작된 데이터를 벗어나 실제 데이터를 사용한다.


동영상 안에서 알려주신 사이트. 이번 동영상에 사용할 소스 코드인 LogisticRegression.py 파일이 포함되어 있다. 이 사이트에는 이번 예제 말고도 좋은 내용이 많이 있다. https://github.com/aymericdamien로 접속하면 더욱 많은 코드가 기다리고 있다.


그림 위쪽에 learning rate 변수가 있다. learning rate을 10으로 바꿨는데, 이렇게 되면 gradient descent에서 성큼성큼 이동하게 된다.


결과는 처참하다. 빨리 가는 것도 좋지만, 목표를 지나친건지 어쩐건지 숫자가 아닌 것들이 나왔다. 그냥 잘못됐다는 뜻이다. 이걸 overshooting이라고 하는데, learning rate이 클 때 발생하는 대표적인 현상이다. nan은 "Not A Number"의 약자다.


이번에는 굉장히 작은 learning rate으로 0.0001을 줬다. 결과를 보면 진행되긴 하는데, 값이 소숫점 세 번째 자리에서 바뀌고 있다. 1800 오른쪽에 있는 것이 cost로, cost의 변화량이 너무 작다. 이렇게 되면 최저점에 도착할 때까지 몇 번을 더 반복해야 할지 알 수 없다. 도착한다고 해도 얼마나 걸릴지 장담하기가 어렵다.


처음에는 0.01로 learning rate을 주셨다. 잘 동작했다. 이어서 지금 그림처럼 0.1로 좀더 과감하게 learning rate을 주었고, 결과 또한 성공적이다. 가장 좋은 learning rate은 적절하게 이동해야 한다고 하는데, 여기에 대한 좋은 숫자는 없다. 여러 번에 걸쳐 실행한 결과를 보고 직접 판단해야 한다. 그것이 learning rate을 결정해야 할 때 가장 어려운 점이다.


mnist 데이터셋이 왜 유명한지는 모르겠지만, tensorflow에서 기본 예제로 사용하기 때문에 tensorflow 계열에서는 가장 유명하다. 기초부터 전문가 수준까지 다양한 예제 코드가 공개되어 있다. (나중에 보니까, 딥러닝을 비롯한 다양한 머신러닝에서 가장 신뢰할 수 있는 데이터로 어디서나 mnist 데이터셋을 사용하고 있었다.)

mnist는 손으로 쓴 우편번호를 기계가 인식해서 자동 분류할 수 있도록 하기 위해 만든 데이터셋이다. 글자 하나의 크기는 28x28이고, 모두 흑백으로 되어 있다. 데이터셋은 image와 label로 구분되어 있고, 각각은 다시 train과 test로 나누어져 있다. validation set은 보이지 않는데, tensorflow 예제를 분석하다 보면 만날 수 있다. (이미지를 분류할 수 있는 샘플이면서, 흑백이어서 GPU가 없어도 돌려볼 수 있는 정도의 가벼움을 지니고 있는 것이 많이 사용하는 이유 중의 하나일 것이다.)


mnist 데이터셋을 읽어오는 소스 코드이다. 이 코드는 tensorflow 배포판에 포함된 예제에 들어있고, 파일 이름은 input_data.py이다. 이 코드 때문에 현재 폴더에 mnist 데이터셋이 없어도 인터넷을 통해 데이터를 가져올 수 있다. mnist 예제를 전문가 수준까지 진행하면서 공부하려면 반드시 이 파일을 분석해야 한다.


이미지 1개는 28x28로 되어 있고, 1차원으로 늘어 놓으면 784가 되고 이것을 feature라고 부른다. 이미지 갯수는 정해져 있긴 하지만 모르는 경우에도 처리할 수 있어야 하니까 갯수를 의미하는 행(row) 크기를 None으로 줬다. feature를 의미하는 열(column) 크기는 앞에서 말한대로 784가 된다. 784픽셀로 이루어진 이미지가 여러 장 있다는 뜻이다. y는 label의 갯수, 즉 0에서 9까지의 숫자를 판단하기 때문에 10개가 되어야 하고, 이미지 갯수는 모르므로 역시 None으로 처리한다.

갑자기 784라는 어마어마한 갯수의 feature를 만나서 믿고 싶지 않을 것이다. 그런데, 사실이다. 공부 시간(x1)과 수업 참석 횟수(x2)와 같은 단순한 데이터일 때는 feature가 2개밖에 없을 수 있다. 여기에 bias를 더한다고 해도 3개에 불과하다. 이미지는 픽셀(pixel, 화소) 단위로 분석해야 하기 때문에 픽셀만큼의 feature가 생길 수밖에 없다.

W(weight)는 x의 feature 갯수와 같아야 한다. x가 갖고 있는 feature 각각에 대한 가중치니까. 또한, 10개의 classification을 구성해야 하니까, binary classification이 10개 있어야 한다. 그래서, W의 크기는 784x10이 된다. b(bias)는 binary classification마다 한 개씩 있으므로 10개가 된다.


이전 동영상에서 multinomial classification을 떠올리면 된다. 이 파일에서 feature는 x0, x1, x2의 3개이고, label에 해당하는 y 또한 3개이다. y가 3개인 이유는 x 때문이 아니라 A, B, C 중에서 하나를 선택해야 하기 때문이다. 그래서, x 데이터가 8개라면 y 데이터 또한 8개가 되어야 한다. 이 코드에서 x는 8행 3열, y도 8행 3열이 된다. 이게 mnist에서 [None, 784], [None, 10]으로 x와 y를 설정하는 이유이다.


hypothesis를 예전에는 sigmoid로도 불렀었고, softmax라고도 불렀었다. hypothesis 자체가 가설을 말하기 때문에 어떤 함수가 됐건 가설이라고 얘기할 수 있었다. Deep Learning에 와서는 activation 함수라고 부른다. 일정 기준을 만족시킬 때 활성화(activation)되기 때문에 붙은 이름이다. 사람의 뇌(brain)가 동작하는 방식과 같다.

이 부분은 이전과 달라진 것이 없다. 행과 열의 갯수가 784와 10으로 커지긴 했지만, 행렬 연산을 하기 때문에 코드에서 변화가 발생하진 않는다. 28x28 크기의 이미지가 갖고 있는 픽셀(pixel) 하나하나를 W와 곱한 다음에 b를 더할 뿐이다. 그러면 어떤 픽셀은 색이 칠해져 있고, 어떤 픽셀은 색이 없을 것이다. 흑백이니까, 있거나 없거나 둘 중의 하나다.

색깔을 갖는 숫자 이미지였다면 색상을 표현하는 RGB(Red, Green, Blue) 각각이 별도의 픽셀이 되어야 하므로 feature 갯수는 768x3만큼이 된다. 텐서플로우에 포함된 cifar10 예제를 보면 나온다.

결국 이 말은 784칸 중에서 픽셀들이 칠해진 방식에 따라 숫자를 구분하겠다는 것과 같다. 이렇게 칠해져 있으면 0, 저렇게 칠해져 있으면 1이라고 판단하는 방식이다. 그래서, 고양이도 찾을 수 있고 자동차도 찾을 수 있는 것이다. 칠해진 방식을 갖고서 판단하니까.


데이터가 너무 커서 한 번에 읽어들이는 것은 무리다. 소스 코드에 보면 training_epochs는 25, batch_size는 100으로 되어 있다. 안쪽 for문에서는 num_examples 갯수만큼의 이미지를 가져와서 학습한다.

num_examples가 반환하는 값이 55,000이니까, 바깥쪽 반복문은 25회, 안쪽 반복문은 550회 반복한다. 한 번에 100개씩의 이미지를 처리하고 있다. next_batch 함수는 지정한 갯수만큼 image와 label을 순차적으로 반환하는 함수이다. 이 코드에서는 55,000개의 이미지를 모두 사용하는 대신, 13,750(25x550)개만 사용하고 있다. 그러나, mnist 전문가 예제에서는 10만개의 이미지를 사용한다.

여기서 중요한 코드는 mnist.train이다. mnist에는 3개의 데이터셋이 있는데, 그 중에서 train을 사용하고 있다. 지금은 학습을 해야 하므로 train을 사용하고, 학습이 끝나면 test를 사용한다.


이 코드는 교수님께서 알려주신 사이트에 없는 부분이다. 아래쪽에 첨부한 소스 코드에는 이 부분을 채워 넣었다. 정말 신기하게도 예측을 하는데, 몇 번 안해 봤지만 잘 맞는다.

여기서는 train이 아니라 test를 사용하고 있다. 전체 test 갯수 중에서 하나를 난수로 선택해서 test set으로부터 image와 label을 1개만 가져온다. [r:r+1]은 슬라이싱(slicing)에 해당하는 문법으로 r에서부터 r+1 이전까지의 범위를 나타낸다. 즉, r번째의 image와 label 하나를 가리킨다.

마지막에 있는 코드는 matplotlib 모듈을 사용해서 숫자 이미지를 출력해 준다. 원래는 그래프를 그리는 모듈인데, 이미지를 출력하기 위한 이미지 모듈까지 같이 갖고 있다.

정확도를 예측했는데, 91.46%가 나왔다. 가장 기본적인 것만 사용해서 91%니까, 굉장히 잘 나온 것이다. 그러나, deep learning을 사용하면 95% 정도는 쉽게 만들 수 있고, mnist 전문가 예제에서는 99.2%의 정확도를 보여준다.

argmax 함수는 가장 큰 값을 찾아서 1로 변환하는 one-hot encoding 알고리듬을 구현한다. argmax에 전달된 두 번째 매개변수 1은 차원을 의미하는데, 이 함수는 별도의 글에 따로 정리를 해두었다. ([텐서플로우 정리] 09. argmax 함수) argmax 함수를 거치면 가장 큰 확률을 갖는 요소만 1로 바뀌고 나머지는 0이 된다. 3번째 요소가 1이 되었다면, 이미지가 0부터 시작하므로 숫자 2를 가리킨다는 뜻이 된다. 이 값을 label과 비교하면 맞았는지 틀렸는지를 알 수 있다. equal 함수의 결과는 True 아니면 False다.

boolean 타입을 float 자료형으로 변환해서 평균을 구한다. True는 1로, False는 0으로 바뀐다. 맞는 것도 있고, 틀린 것도 있겠지만, 전체를 더해서 평균을 내면 0과 1 사이의 값이 나온다. 이 값이 얼마나 맞았는지를 알려주는 accuracy가 된다. 전체가 0이면 하나도 맞은 것이 없으므로 평균은 0이 된다.

accuracy에서 사용할 데이터로 mnist.test.images와 mnist.test.labels를 주었다. images는 예측하기 위해, labels는 예측한 것과 비교하기 위해.


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.1
training_epochs = 25
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
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

# Construct model
activation = tf.nn.softmax(tf.matmul(x, W) + b) # Softmax

# Minimize error using cross entropy
cost = tf.reduce_mean(-tf.reduce_sum(y*tf.log(activation), reduction_indices=1))
# Gradient Descent
optimizer = tf.train.GradientDescentOptimizer(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))

# 추가한 코드. Label과 Prediction이 같은 값을 출력하면 맞는 것이다.
import random
r = random.randrange(mnist.test.num_examples)
print('Label : ', sess.run(tf.argmax(mnist.test.labels[r:r+1], 1)))
print('Prediction :', sess.run(tf.argmax(activation, 1), {x: mnist.test.images[r:r+1]}))

# 1줄로 된 것을 28x28로 변환
import matplotlib.pyplot as plt
plt.imshow(mnist.test.images[r:r+1].reshape(28, 28), cmap='Greys', interpolation='nearest')
plt.show()

print("Optimization Finished!")

# Test model
correct_prediction = tf.equal(tf.argmax(activation, 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}))

앞에서 충분히 설명했기 때문에 추가 설명은 없다. 필요한 부분만 짧게 주석을 달았다. 인터넷에서 가져온 데이터셋은 "/tmp/data/" 폴더에 저장한다고 되어 있다.


[출력 결과]
...
Epoch: 0021 cost= 0.269660726
Epoch: 0022 cost= 0.268667803
Epoch: 0023 cost= 0.267811905
Epoch: 0024 cost= 0.266890075
Epoch: 0025 cost= 0.266161013
label : [8]
Prediction : [8]
Optimization Finished!
Accuracy: 0.9216

출력 결과도 너무 길어서 뒤쪽 일부만 실었다. 난수가 8(label)을 전달했고, 8(Prediction)을 예측했으므로 맞았다. 정확도는 92.16% 나왔다.