### 2021.12.17

 

MNIST 손글씨 이미지 데이터셋

MNIST 데이터셋은 위 그림과 같이 0에서 9까지 10가지로 분류될 수 있는 손글씨 숫자 이미지 70,000개로 이루어져 있습니다.

각 이미지는 28×28 픽셀로 구성되고 각 픽셀은 아래와 같이 0~255 사이의 숫자 행렬로 표현됩니다.

 

이러한 60,000개의 이미지는 인공 신경망의 훈련 (Training)에 사용되고, 10,000개의 이미지는 테스트 (Test)에 사용됩니다.

 

이번 페이지에서는 Dense 층들로 구성되는 완전 연결된 인공신경망 (Fully-Connected Neural Network)을 이용해서 MNIST 데이터셋을 분류해 보겠습니다.

 

예제

import tensorflow as tf

# 1. MNIST 데이터셋 임포트
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 2. 데이터 전처리
x_train, x_test = x_train/255.0, x_test/255.0

# 3. 모델 구성
model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(512, activation=tf.nn.relu),
    tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])

# 4. 모델 컴파일
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# 5. 모델 훈련
model.fit(x_train, y_train, epochs=5)

# 6. 정확도 평가
test_loss, test_acc = model.evaluate(x_test, y_test)
print('테스트 정확도:', test_acc)

model.save('my_model1.h5')
#Out[]
Epoch 1/5
1875/1875 [==============================] - 7s 4ms/step - loss: 0.2026 - accuracy: 0.9408
Epoch 2/5
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0808 - accuracy: 0.9753
Epoch 3/5
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0514 - accuracy: 0.9837
Epoch 4/5
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0376 - accuracy: 0.9880
Epoch 5/5
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0266 - accuracy: 0.9916
313/313 [==============================] - 1s 2ms/step - loss: 0.0643 - accuracy: 0.9811
테스트 정확도: 0.9811000227928162

 

설명

0. tensorflow 불러오기

#tensorflow 라이브러리를 불러옵니다.
import tensorflow as tf

 

1. MNIST 데이터셋 임포트

mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

tensorflow에서 직접 MNIST 손글씨 이미지 데이터셋을 불러와서 사용합니다.

load_data() 함수는 x_train, y_train, x_test, y_test 네 개의 NumPy 어레이를 반환합니다.

x_train, x_test는 28×28 픽셀의 각 손글씨 이미지 데이터이고, y_train, y_test는 분류에 사용되는 0~9 사이의 레이블 값을 갖습니다.

#In[1]
print("x_train 데이터 형태 :", x_train.shape)
print("x_train[0] 데이터 형태 :", x_train[0].shape)
print("y_train 데이터 형태 :", y_train.shape)

#Out[1]
x_train 데이터 형태 : (60000, 28, 28)
x_train[0] 데이터 형태 : (28, 28)
y_train 데이터 형태 : (60000,)
#In[2]
num = x_train[0]
for i in range(28):
    for j in range(28):
         print("{:4d}".format(num[i][j]), end="")
    print()

#Out[2]

 

각 이미지는 28×28 픽셀로 구성되고 각 픽셀은 아래와 같이 0~255 사이의 숫자 행렬로 표현됩니다.

 

이러한 60,000개의 이미지는 인공 신경망의 훈련 (Training)에 사용되고, 10,000개의 이미지는 테스트 (Test)에 사용됩니다.

이번 페이지에서는 Dense 층들로 구성되는 완전 연결된 인공신경망 (Fully-Connected Neural Network)을 이용해서 MNIST 데이터셋을 분류해 보겠습니다.

 

2.  데이터 전처리

# 0 ~ 255.0 사이의 값을 갖는 픽셀값들을 0~1.0 사이의 값을 갖도록 변환합니다.

x_train, x_test = x_train/255.0, x_test/255.0

 

3.  모델 구성

model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(512, activation=tf.nn.relu),
    tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])

tf.keras.models.Sequential()을 이용해서 인공신경망 모델을 구성합니다.

입력층 (Input layer)에서 Flatten()을 이용해서 28×28 픽셀의 값을 784개의 1차원 배열로 변환합니다.

다음 두 개의 뉴런 층 (Neuron layer)은 Dense()를 이용해서 완전 연결된 층 (Fully-connected layer)를 구성합니다.

각 층은 512개와 10개의 인공 뉴런 노드를 갖고 활성화 함수 (activation function)로는 각각 ReLU (tf.nn.relu)와 소프트맥스 (tf.nn.softmax)를 사용합니다.

 

4.  모델 컴파일

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

다음은 모델 컴파일 단계입니다. 학습 과정에서 손실 함수 (Loss function)를 줄이기 위해 사용되는 optimizer로는 Adam (Adaptive Momentum estimation)을 사용합니다.

손실 함수는 ‘sparse_categorical_crossentropy’를 지정하고, 평가 지표로는 정확도 (accuracy)를 사용합니다.

정확도는 테스트 이미지 중 올바르게 분류한 비율을 의미합니다.

 

5. 모델 훈련

#In[]
model.fit(x_train, y_train, epochs=5)

#Out[]
Epoch 1/5
1875/1875 [==============================] - 10s 5ms/step - loss: 0.1985 - accuracy: 0.9413
Epoch 2/5
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0806 - accuracy: 0.9743
Epoch 3/5
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0526 - accuracy: 0.9835
Epoch 4/5
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0373 - accuracy: 0.9881
Epoch 5/5
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0275 - accuracy: 0.9911
<tensorflow.python.keras.callbacks.History at 0x7fcb10ae1d90>

학습 과정은 위와 같이 이루어지며, 학습 데이터에 대한 정확도가 출력됩니다.

model.fit() 메서드에 학습 데이터와, 레이블, 에포크를 순서대로 입력하면, 학습이 이루어집니다.

에포크(epoch)는 60,000개의 전체 학습 데이터를 몇 번 반복해서 학습할지를 의미합니다.

model.save('my_model2.h5')

 

6. 정확도 평가

#In[]
test_loss, test_acc = model.evaluate(x_test, y_test)
print('테스트 정확도:', test_acc)

#Out[]
313/313 [==============================] - 1s 3ms/step - loss: 0.0707 - accuracy: 0.9785
테스트 정확도: 0.9785000085830688

결과는 위와 같습니다. 

model.evaluate()를 이용해서 10,000개의 테스트 샘플에 대해 손실 (loss)과 정확도 (accuracy)를 평가합니다.

 

간단한 인공신경망과 다섯 번 에포크의 학습만으로 98.02%의 정확도로 MNIST 이미지를 분류할 수 있음을 알 수 있습니다.

 

 

Matplotlib을 이용해서 에포크에 따른 정확도 (accuracy)와 손실 (loss) 값을 확인할 수 있습니다.

#In[]
loss, accuracy = [], []
for i in range(10):
    model.fit(x_train, y_train, epochs=1)
    loss.append(model.evaluate(x_test, y_test)[0])
    accuracy.append(model.evaluate(x_test, y_test)[1])

print(accuracy)
#Out[]
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0202 - accuracy: 0.9933
313/313 [==============================] - 1s 2ms/step - loss: 0.0603 - accuracy: 0.9814
313/313 [==============================] - 1s 2ms/step - loss: 0.0603 - accuracy: 0.9814
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0168 - accuracy: 0.9942
313/313 [==============================] - 1s 2ms/step - loss: 0.0819 - accuracy: 0.9775
313/313 [==============================] - 1s 2ms/step - loss: 0.0819 - accuracy: 0.9775
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0148 - accuracy: 0.9950
313/313 [==============================] - 1s 3ms/step - loss: 0.0738 - accuracy: 0.9806
313/313 [==============================] - 1s 3ms/step - loss: 0.0738 - accuracy: 0.9806
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0134 - accuracy: 0.9955
313/313 [==============================] - 1s 3ms/step - loss: 0.0662 - accuracy: 0.9833
313/313 [==============================] - 1s 2ms/step - loss: 0.0662 - accuracy: 0.9833
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0101 - accuracy: 0.9965
313/313 [==============================] - 1s 2ms/step - loss: 0.0771 - accuracy: 0.9820
313/313 [==============================] - 1s 2ms/step - loss: 0.0771 - accuracy: 0.9820
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0112 - accuracy: 0.9964
313/313 [==============================] - 1s 2ms/step - loss: 0.0744 - accuracy: 0.9821
313/313 [==============================] - 1s 2ms/step - loss: 0.0744 - accuracy: 0.9821
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0075 - accuracy: 0.9974
313/313 [==============================] - 1s 2ms/step - loss: 0.0820 - accuracy: 0.9828
313/313 [==============================] - 1s 2ms/step - loss: 0.0820 - accuracy: 0.9828
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0104 - accuracy: 0.9968
313/313 [==============================] - 1s 3ms/step - loss: 0.1053 - accuracy: 0.9808
313/313 [==============================] - 1s 2ms/step - loss: 0.1053 - accuracy: 0.9808
1875/1875 [==============================] - 10s 5ms/step - loss: 0.0066 - accuracy: 0.9979
313/313 [==============================] - 1s 2ms/step - loss: 0.0901 - accuracy: 0.9814
313/313 [==============================] - 1s 2ms/step - loss: 0.0901 - accuracy: 0.9814
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0084 - accuracy: 0.9975
313/313 [==============================] - 1s 3ms/step - loss: 0.0798 - accuracy: 0.9840
313/313 [==============================] - 1s 2ms/step - loss: 0.0798 - accuracy: 0.9840
[0.9814000129699707, 0.9775000214576721, 0.9805999994277954, 0.983299970626831, 0.9819999933242798, 0.9821000099182129, 0.9828000068664551, 0.9807999730110168, 0.9814000129699707, 0.984000027179718]

1회의 에포크마다 model.evaluate()의 loss, accuracy 값을 저장합니다.

결과는 아래와 같습니다.

model.save('my_model3.h5')
## Ex 10-6. MNIST 손글씨 인식 프로그램.

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import numpy as np
import tensorflow as tf


class MyApp(QMainWindow):

    def __init__(self):
        super().__init__()
        self.image = QImage(QSize(400, 400), QImage.Format_RGB32)
        self.image.fill(Qt.white)
        self.drawing = False
        self.brush_size = 30
        self.brush_color = Qt.black
        self.last_point = QPoint()
        self.loaded_model = None
        self.initUI()

    def initUI(self):
        menubar = self.menuBar()
        menubar.setNativeMenuBar(False)
        filemenu = menubar.addMenu('File')

        load_model_action = QAction('Load model', self)
        load_model_action.setShortcut('Ctrl+L')
        load_model_action.triggered.connect(self.load_model)

        save_action = QAction('Save', self)
        save_action.setShortcut('Ctrl+S')
        save_action.triggered.connect(self.save)

        clear_action = QAction('Clear', self)
        clear_action.setShortcut('Ctrl+C')
        clear_action.triggered.connect(self.clear)

        filemenu.addAction(load_model_action)
        filemenu.addAction(save_action)
        filemenu.addAction(clear_action)

        self.statusbar = self.statusBar()

        self.setWindowTitle('MNIST Classifier')
        self.setGeometry(300, 300, 400, 400)
        self.show()

    def paintEvent(self, e):
        canvas = QPainter(self)
        canvas.drawImage(self.rect(), self.image, self.image.rect())

    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.drawing = True
            self.last_point = e.pos()

    def mouseMoveEvent(self, e):
        if (e.buttons() & Qt.LeftButton) & self.drawing:
            painter = QPainter(self.image)
            painter.setPen(QPen(self.brush_color, self.brush_size, Qt.SolidLine, Qt.RoundCap))
            painter.drawLine(self.last_point, e.pos())
            self.last_point = e.pos()
            self.update()

    def mouseReleaseEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.drawing = False

            arr = np.zeros((28, 28))
            for i in range(28):
                for j in range(28):
                    arr[j, i] = 1 - self.image.scaled(28, 28).pixelColor(i, j).getRgb()[0] / 255.0
            arr = arr.reshape(-1, 28, 28)

            if self.loaded_model:
                pred = self.loaded_model.predict(arr)[0]
                pred_num = str(np.argmax(pred))
                self.statusbar.showMessage('숫자 ' + pred_num + '입니다.')

    def load_model(self):
        fname, _ = QFileDialog.getOpenFileName(self, 'Load Model', '')

        if fname:
            self.loaded_model = tf.keras.models.load_model(fname)
            self.statusbar.showMessage('Model loaded.')

    def save(self):
        fpath, _ = QFileDialog.getSaveFileName(self, 'Save Image', '', "PNG(*.png);;JPEG(*.jpg *.jpeg);;All Files(*.*) ")

        if fpath:
            self.image.scaled(28, 28).save(fpath)

    def clear(self):
        self.image.fill(Qt.white)
        self.update()
        self.statusbar.clearMessage()
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MyApp()
    sys.exit(app.exec_())

 

 

 

+ Recent posts