본문 바로가기
ML & DL/tensorflow

MNIST dataset 예제(ConvNet, VGG16)

by 별준 2020. 11. 22.

Tensorflow.keras에서 기본으로 제공하는 MNIST dataset을 사용해 CNN 기본 구조와 VGG16구조, 이 두가지를 사용해서 분류해보려고 합니다.

 

1. Dataset 준비

기본적으로 사용될 package와 MNIST dataset을 읽어옵니다.

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import time

(x_train_orig, y_train_orig), (x_test_orig, y_test_orig) = tf.keras.datasets.mnist.load_data()

print(f'input shape : {x_train_orig.shape}')
print(f'output shape : {y_train_orig.shape}')
input shape : (60000, 28, 28)
output shape : (60000,)

학습에 사용되는 data는 총 60,000개로 28x28의 shape를 가지고 있습니다. 즉, 흑백이미지로 1채널만 존재합니다. data가 많으니, 50,000개는 학습에 사용하고, 나머지 10,000개는 validation에 사용해보도록 하겠습니다.

 

그리고, 우리는 Conv2D와 MaxPool2D를 사용해서 layer를 구성할 예정이기 때문에, channel을 나타내는 dimension을 추가해주어야 합니다. 추가를 해주고, 255로 나누어서 normalization도 같이 진행해줍니다.

그리고, training dataset 50,000개, validation dataset 10,000개로 나누어 줍니다.

x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]

x_train, x_test = x_train / 255.0, x_test / 255.0

x_val = x_train[:10000]
x_train = x_train[10000:]
y_val = y_train_orig[:10000]
y_train = y_train_orig[10000:]

 

2. ConvNet 구성

첫 번째로 구성해볼 네트워크는 다음과 같습니다.

def build_model():
    tf.random.set_seed(2)
    model = tf.keras.models.Sequential([
        tf.keras.layers.Conv2D(filters=32, kernel_size=(3,3), strides=(1,1), padding='same', activation='relu', input_shape=(28, 28, 1)),
        tf.keras.layers.MaxPool2D(pool_size=(2,2), strides=(2,2), padding='same'),
        tf.keras.layers.Conv2D(filters=16, kernel_size=(2,2), strides=(1,1), padding='same', activation='relu'),
        tf.keras.layers.MaxPool2D(pool_size=(2,2), strides=(2,2), padding='same'),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(10, activation='softmax')
    ])

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

[Conv -> MaxPool -> Conv -> MaxPool -> Flatten -> Dense]

아주 기본적인 구조입니다.

m1 = build_model()
m1.summary()
start_time = time.time()
m1_hist = m1.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_val, y_val))
print(f"--- time : {time.time() - start_time} sec ---")
Epoch 1/5
1563/1563 [==============================] - 6s 4ms/step - loss: 0.2784 - acc: 0.9164 - val_loss: 0.1086 - val_acc: 0.9674
Epoch 2/5
1563/1563 [==============================] - 6s 4ms/step - loss: 0.0950 - acc: 0.9716 - val_loss: 0.0868 - val_acc: 0.9742
Epoch 3/5
1563/1563 [==============================] - 6s 4ms/step - loss: 0.0704 - acc: 0.9785 - val_loss: 0.0749 - val_acc: 0.9782
Epoch 4/5
1563/1563 [==============================] - 6s 4ms/step - loss: 0.0576 - acc: 0.9820 - val_loss: 0.0701 - val_acc: 0.9802
Epoch 5/5
1563/1563 [==============================] - 6s 4ms/step - loss: 0.0493 - acc: 0.9843 - val_loss: 0.0650 - val_acc: 0.9804

training acc : 98.43% / validation acc : 98.04%로 overfitting없이 학습이 잘 된 것으로 보입니다.

 

학습한 모델에 test set을 입력해보면,

m1.evaluate(x_test, y_test)

 

313/313 [==============================] - 1s 2ms/step - loss: 0.0508 - acc: 0.9827

98.27%의 정확도를 보여주고 있습니다.

학습동안의 loss와 acc를 살펴보면 이상적으로 학습이 되고 있는 것을 확인할 수 있습니다. 아마 epoch를 더 증가해서 학습하면 조금 더 좋은 결과가 나올 수도 있을 것 같네요.

 

2. VGG16

이번에는 VGG16의 구조를 사용해서 학습을 진행해볼 것입니다.(GPU를 사용해서 학습을 진행했습니다. 모델이 너무 커서 GPU가 아니면 너무 느려요..)

기본적으로 VGG16은 위와 같이 구성되어 있는데, 아무래도 MNIST dataset의 input shape가 (28, 28, 1)이다보니 조금 과한 모델이 아니지 않나라는 생각이 들기는 하지만... 한 번 테스트해보도록 하겠습니다.

거의 동일하게 구성을 했고, 마지막 flatten 해주는 부분만 약간 변경하였습니다.

from tensorflow.keras import layers

def VGG16_model():
    tf.random.set_seed(2)
    model = tf.keras.models.Sequential([
        # Conv 1
        layers.Conv2D(64, (3, 3), strides=1, padding='same', activation='relu',
                     input_shape=(28,28,1), name='conv1_1'),
        layers.Conv2D(64, (3, 3), strides=1, padding='same', activation='relu', name='conv1_2'),
        layers.MaxPool2D((2,2), padding='same', name='conv1_MaxPool'),
        # Conv 2
        layers.Conv2D(128, (3, 3), strides=1, padding='same', activation='relu', name='conv2_1'),
        layers.Conv2D(128, (3, 3), strides=1, padding='same', activation='relu', name='conv2_2'),
        layers.MaxPool2D((2,2), padding='same', name='conv2_MaxPool'),
        # Conv3
        layers.Conv2D(256, (3, 3), strides=1, padding='same', activation='relu', name='conv3_1'),
        layers.Conv2D(256, (3, 3), strides=1, padding='same', activation='relu', name='conv3_2'),
        layers.Conv2D(256, (3, 3), strides=1, padding='same', activation='relu', name='conv3_3'),
        layers.MaxPool2D((2,2), padding='same', name='conv3_MaxPool'),
        # Conv4
        layers.Conv2D(512, (3, 3), strides=1, padding='same', activation='relu', name='conv4_1'),
        layers.Conv2D(512, (3, 3), strides=1, padding='same', activation='relu', name='conv4_2'),
        layers.Conv2D(512, (3, 3), strides=1, padding='same', activation='relu', name='conv4_3'),
        layers.MaxPool2D((2,2), padding='same', name='conv4_MaxPool'),
        # Conv5
        layers.Conv2D(512, (3, 3), strides=1, padding='same', activation='relu', name='conv5_1'),
        layers.Conv2D(512, (3, 3), strides=1, padding='same', activation='relu', name='conv5_2'),
        layers.Conv2D(512, (3, 3), strides=1, padding='same', activation='relu', name='conv5_3'),
        layers.MaxPool2D((2,2), padding='same', name='conv5_MaxPool'),
        # Flatten
        layers.Flatten(),
        # Dropout
        layers.Dropout(0.5),
        # FC1
        layers.Dense(512, activation='relu'),
        # output
        layers.Dense(10, activation='softmax'),
    ])
    
    model.compile(optimizer=tf.keras.optimizers.Adam(),
                 loss='sparse_categorical_crossentropy',
                 metrics=['acc'])
    
    return model

그리고 model을 만들어주고, 학습을 해보도록 하겠습니다.

m2 = VGG16_model()
m2.summary()

start_time = time.time()
m2_hist = m2.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_val, y_val))
print(f"--- time : {time.time() - start_time} sec ---")

간단한 dataset에 너무나 과한 model을 적용하는것이 확실한 것 같네요... 

Epoch 1/5
1563/1563 [==============================] - 103s 66ms/step - loss: 2.3020 - acc: 0.1114 - val_loss: 2.3007 - val_acc: 0.1127
Epoch 2/5
1563/1563 [==============================] - 102s 65ms/step - loss: 2.3016 - acc: 0.1123 - val_loss: 2.3009 - val_acc: 0.1127
Epoch 3/5
1563/1563 [==============================] - 102s 65ms/step - loss: 2.3015 - acc: 0.1123 - val_loss: 2.3008 - val_acc: 0.1127
Epoch 4/5
1563/1563 [==============================] - 102s 65ms/step - loss: 2.3015 - acc: 0.1123 - val_loss: 2.3007 - val_acc: 0.1127
Epoch 5/5
1563/1563 [==============================] - 102s 65ms/step - loss: 2.3015 - acc: 0.1123 - val_loss: 2.3007 - val_acc: 0.1127
--- time : 512.0835151672363 sec ---

약 8분이 조금 넘게 걸렸네요. 그런데 결과는 뭐.. 학습이 거의 되지 않은 것 같습니다. 아무래도 모델이 너무 deep 하다보니 Gradient Vanishing과 같은 문제가 발생하지 않았을까 추측이 되네요.

 

그래서.. !

keras에 패키지로 포함되어있는 학습이 완료된 VGG16 모델을 가져와서 적용을 한 번 해보려고 합니다 !

keras.applications 모듈에서 import할 수 있고, 이 모듈에서 여러가지 모듈을 가져와서 사용이 가능합니다.

 

이렇게 해보려고 했으나... ! 

제공되는 VGG16의 input의 채널이 3채널이여야지 입력으로 사용이 가능한 것 같습니다.... 

따라서, 28x28x1의 shape인 현재 dataset의 입력은 적용이 힘듭니다...

 

이 방법은 다음에 다른 dataset을 가지고 한 번 시도해보도록 하겠습니다.

 

그래도 한 가지, dataset에 적당한 모델을 사용하는 것이 더 좋다라는 것을 알게된 예제였습니다 ㅎㅎ

 

댓글