MNIST 숫자 인식 프로젝트


목적

  • CNN을 활용하여 MNIST의 숫자를 인식하는 모델을 만든다.
  • 라즈베리 파이에 학습한 모델을 활용하여 손글씨 숫자를 인식하는 장치를 만든다.
    • 버튼을 누르면 카메라로 찍은 손글씨 숫자를 인식하여 LED에 표시

과정

1. MNIST 학습 후 모델 저장

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

데이터 로딩(Load Data)

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

하이퍼 파라미터 설정

# Hyper Prarameters
training_epochs = 15
batch_size = 100
learning_rate = 0.001
class Model:

    def __init__(self, sess, name):
        self.sess = sess
        self.name = name
        self._build_net()

    def _build_net(self):

        # 입력 받은 이름으로 변수 명을 설정한다.
        with tf.variable_scope(self.name):

            # Boolean Tensor 생성 for dropout
            # tf.layers.dropout( training= True/Fals) True/False에 따라서 학습인지 / 예측인지 선택하게 됨
            # default = False
            self.training = tf.placeholder(tf.bool)

            # 입력 그래프 생성
            self.X = tf.placeholder(tf.float32, [None, 784])
            # 28x28x1로 사이즈 변환
            X_img = tf.reshape(self.X, [-1, 28, 28, 1])
            self.Y = tf.placeholder(tf.float32, [None, 10])

            # Convolutional Layer1
            conv1 = tf.layers.conv2d(inputs=X_img, filters=32, kernel_size=[3,3], padding='SAME', activation=tf.nn.relu)
            # Pooling Layer1
            pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2,2], strides=2, padding="SAME" )
            # Dropout Layer1
            dropout1 = tf.layers.dropout(inputs=pool1, rate=0.7, training=self.training)


            # Convolutional Layer2
            conv2 = tf.layers.conv2d(inputs=dropout1, filters=64, kernel_size=[3,3], padding='SAME', activation=tf.nn.relu)
            # Pooling Layer2
            pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2,2],strides=2, padding='SAME' )
            # Dropout Layer2
            dropout2 = tf.layers.dropout(inputs=pool2, rate=0.7, training=self.training)


            # Convolutional Layer3
            conv3 = tf.layers.conv2d(inputs=dropout2, filters=128, kernel_size=[3, 3], padding='SAME', activation=tf.nn.relu)
            # Pooling Layer3
            pool3 = tf.layers.max_pooling2d(inputs=conv3, pool_size=[2,2], strides=2, padding='SAME')
            # Dropout Layer3
            dropout3 = tf.layers.dropout(inputs=pool3, rate=0.7, training=self.training)

            # Dense Layer4 with Relu
            flat = tf.reshape(dropout3, [-1, 128*4*4])
            dense4 = tf.layers.dense(inputs=flat, units=625, activation=tf.nn.relu)
            # Dropout layer4
            dropout4 = tf.layers.dropout(inputs=dense4, rate=0.5, training=self.training)

            # Dense Layer5 with Relu
            dense5 = tf.layers.dense(inputs=dropout4, units=1050, activation=tf.nn.relu)
            # Dropout Layer5
            dropout5 = tf.layers.dropout(inputs=dense5, rate=0.5, training=self.training)


            # Logits layer : Final FC Layer5 Shape = (?, 625) -> 10
            self.logits = tf.layers.dense(inputs=dropout5, units=10)

        # Cost Function
        self.cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=self.logits, labels=self.Y))
        self.optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(self.cost)

        # Test Model
        correct_prediction = tf.equal(tf.argmax(self.logits, 1), tf.argmax(self.Y, 1))
        self.accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    def train(self, x_data, y_data, training = False):
        return self.sess.run([self.cost, self.optimizer], feed_dict={self.X: x_data, self.Y:y_data, self.training:training})

    def predict(self, x_test, training = False):
        return self.sess.run(self.logits, feed_dict={self.X : x_test, self.training:training})

    def get_accuracy(self, x_test, y_test, training=False):
        return self.sess.run(self.accuracy, feed_dict={self.X: x_test, self.Y : y_test, self.training: training})

세션 생성

# Initialize
sess = tf.Session()

Ensemble

models = []
num_models = 5
for m in range(num_models):
    models.append(Model(sess, "model"+str(m)))

학습

sess.run(tf.global_variables_initializer())
for epoch in range(training_epochs):
    avg_cost_list = np.zeros(len(models))
    total_batch = int(mnist.train.num_examples / batch_size)

    for i in range(total_batch):
        batch_xs, batch_ys = mnist.train.next_batch(batch_size)

        # Train each model
        for m_idx, m in enumerate(models):
            c, _ = m.train(batch_xs, batch_ys)
            avg_cost_list[m_idx] += c / total_batch

    print('Epoch: ', '%04d' %(epoch + 1), 'Cost = ', avg_cost_list)
print('Training Finished')
  • 결과
Epoch:  0001 Cost =  [ 0.16365541  0.15805094  0.15816862  0.16383813  0.16415393]
Epoch:  0002 Cost =  [ 0.04496201  0.0429047   0.04390503  0.04575218  0.04640629]
Epoch:  0003 Cost =  [ 0.03376844  0.03230815  0.03093393  0.0311225   0.03254555]
...
Epoch:  0014 Cost =  [ 0.00832863  0.00841471  0.01017939  0.00869219  0.01102423]
Epoch:  0015 Cost =  [ 0.00701145  0.00803191  0.00734568  0.0069111   0.00499205]
Training Finished

모델 테스트

test_size = len(mnist.test.labels)
predictions = np.zeros([test_size, 10])
best_models = []
for m_idx, m in enumerate(models):
    best_models.append(m.get_accuracy(mnist.test.images, mnist.test.labels))
    print(m_idx, 'Accuracy: ', best_models[m_idx] )
    p = m.predict(mnist.test.images)
    predictions += p
  • 결과
0 Accuracy:  0.9925
1 Accuracy:  0.9918
2 Accuracy:  0.9923
3 Accuracy:  0.9928
4 Accuracy:  0.9901
ensemble_correct_prediction = tf.equal(tf.argmax(predictions, 1), tf.argmax(mnist.test.labels, 1))
ensemble_accuracy = tf.reduce_mean(tf.cast(ensemble_correct_prediction, tf.float32))
print('Ensemble accuracy:', sess.run(ensemble_accuracy))
  • 결과
Ensemble accuracy: 0.9954

모델 저장

  • 5개의 모델 중 가장 뛰어난 모델 선택 후 저장
best_model = models[np.argmax(best_models)]
saver = tf.train.Saver()
save_path = saver.save(best_model.sess, './mnist_cnn.ckpt')
print("Model saved to %s" % save_path)

참고 사항

  • Tensor 이름 확인(Tensor 이름을 정의하지 않았을 때)하는 법
best_model.logits
<tf.Tensor 'model3/dense_3/BiasAdd:0' shape=(?, 10) dtype=float32> ```python best_model.X ```
<tf.Tensor 'model3/Placeholder_1:0' shape=(?, 784) dtype=float32> ```python best_model.training ```
<tf.Tensor 'model3/Placeholder:0' shape=<unknown> dtype=bool>

2. MNIST 모델 불러오기 및 숫자 테스트

import matplotlib.pyplot as plt
import tensorflow as tf
from PIL import Image
import numpy as np
import sys
%matplotlib inline
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)

세션 열기

sess = tf.InteractiveSession()

모델 불러오기(저장된 변수도 함께 불러오기)

new_saver = tf.train.import_meta_graph('./mnist_cnn.ckpt.meta')
new_saver.restore(sess, './mnist_cnn.ckpt')

Tensor 그래프 불러오기

  • 모델 저장 시 그래프 정보가 포함되어 있으므로, 데이터 테스트에 사용할 텐서 그래프만 불러오면 된다.
  • 이름을 별도로 지정하지 않았을 경우 해당 저장 할 때 텐서를 실행시켜서 확인이 가능하다.
X = sess.graph.get_tensor_by_name("model3/Placeholder_1:0")
logits = sess.graph.get_tensor_by_name("model3/dense_3/BiasAdd:0")
training = sess.graph.get_tensor_by_name("model3/Placeholder:0")

MNIST 데이터 테스트

  • Validation 이미지에서 임의의 숫자를 불러온다.
image_b = mnist.validation.images[np.random.randint(0, len(mnist.validation.images))]
plt.imshow(image_b.reshape([28, 28]), cmap='Greys')

png

  • 불러온 숫자를 모델에 넣어 제대로 숫자가 인식되는지 확인한다.
image_b = image_b.reshape([1, 784])
result = sess.run(logits, feed_dict={X:image_b, training:False})
print("MNIST predicted Number : ", sess.run(tf.argmax(result, 1)))
  • 결과
MNIST predicted Number :  [4]

이미지 파일 테스트

  • 라즈베리 파이의 카메라 모듈에서 찍은 사진을 바로 넣어서 숫자를 인식해야 한다.
  • 따라서, MNIST 이미지가 이닌 숫자를 적은 이미지를 불러와서 인식 시켜본다.

한번에 확인하기

result_show = []
fig = plt.figure(figsize=(15,5))
for i in range(0, 9):
    im=Image.open("./Number_data/number_{}.jpeg".format(i+1))
    img = np.array(im.resize((28, 28), Image.ANTIALIAS).convert("L"))
    data = img.reshape([1, 784])
    data = 1-(data/255)
    ax = fig.add_subplot(1,10,i+1)
    ax.imshow(img.reshape(28, 28), cmap='gray', interpolation='nearest', aspect='auto')

    result = sess.run(logits, feed_dict={X:data, training:False})
    result_show.append(sess.run(tf.argmax(result, 1)))
print("MNIST predicted Number")
print(result_show)   
MNIST predicted Number
[array([9]), array([1]), array([1]), array([8]), array([3]), array([5]), array([1]), array([8]), array([1])]

png

이미지 하나씩 확인

im=Image.open("./Number_data/number_5.jpeg")
img = np.array(im.resize((28, 28), Image.ANTIALIAS).convert("L"))
data = img.reshape([1, 784])
data = 1-(data/255)
plt.imshow(img.reshape(28, 28), cmap='gray', interpolation='nearest')
result = sess.run(logits, feed_dict={X:data, training:False})
print("MNIST predicted Number : ", sess.run(tf.argmax(result, 1)))
  • 결과
MNIST predicted Number :  [3]

png

이미지 명암 조절 후 숫자 인식

im=Image.open("./Number_data/number_1.jpeg")
im_light = Image.eval(im, lambda x:x+80)
plt.imshow(im_light)
img = np.array(im_light.resize((28, 28), Image.ANTIALIAS).convert("L"))
data = img.reshape([1, 784])
data = 1-(data/255)
plt.imshow(img.reshape(28, 28), cmap='gray', interpolation='nearest')

png

result = sess.run(logits, feed_dict={X:data, training:False})
print("MNIST predicted Number : ", sess.run(tf.argmax(result, 1)))
  • 결과
MNIST predicted Number :  [8]

보너스 : HTML 숫자 그림 테스트

  • HTML에서 숫자를 직접 그려서 테스트가 가능하다.
input_form = """
<table>
<td style="border-style: none;">
<div style="border: solid 2px #666; width: 143px; height: 144px;">
<canvas width="140" height="140"></canvas>
</div></td>
<td style="border-style: none;">
<button onclick="clear_value()">Clear</button>
</td>
</table>
"""
javascript = """
<script type="text/Javascript">
    var pixels = [];
    for (var i = 0; i < 28*28; i++) pixels[i] = 0
    var click = 0;


    var canvas = document.querySelector("canvas");
    canvas.addEventListener("mousemove", function(e){
        if (e.buttons == 1) {
            click = 1;
            canvas.getContext("2d").fillStyle = "rgb(0,0,0)";
            canvas.getContext("2d").fillRect(e.offsetX, e.offsetY, 8, 8);
            x = Math.floor(e.offsetY * 0.2)
            y = Math.floor(e.offsetX * 0.2) + 1
            for (var dy = 0; dy < 2; dy++){
                for (var dx = 0; dx < 2; dx++){
                    if ((x + dx < 28) && (y + dy < 28)){
                        pixels[(y+dy)+(x+dx)*28] = 1
                    }
                }
            }
        } else {
            if (click == 1) set_value()
            click = 0;
        }
    });

    function set_value(){
        var result = ""
        for (var i = 0; i < 28*28; i++) result += pixels[i] + ","
        var kernel = IPython.notebook.kernel;
        kernel.execute("image = [" + result + "]");
    }

    function clear_value(){
        canvas.getContext("2d").fillStyle = "rgb(255,255,255)";
        canvas.getContext("2d").fillRect(0, 0, 140, 140);
        for (var i = 0; i < 28*28; i++) pixels[i] = 0
    }
</script>
"""
from IPython.display import HTML
HTML(input_form + javascript)
result = sess.run(logits, feed_dict={X:[image], training:False})
print("MNIST predicted Number : ", sess.run(tf.argmax(result, 1)))

References


결과

  • 라즈베리 파이의 ‘파이 카메라(Pi Camera)’ 모듈로 찍은 이미지가 명암이 분명하지 않아서 ‘포토샵’으로 숫자를 제외한 배경 부분을 하얗게 처리하거나, plt 라이브러리를 통해서 명암을 조절해줘야 인식이 가능했음
  • 라즈베리 파이 적용 시 ‘조명’이나 ‘웹 카메라’등을 사용해서 이미지 퀄리티를 높이는 방법이 있을 수 있음