딥러닝으로 손글씨를 인식하는 모델을 만들어보자.

Mnist 데이터셋을 이용한 손글씨 분류하기 : DNN, CNN

지금까지 진행한 포스팅을 기반으로 딥러닝 튜토리얼 시 가장 흔하게 접할 수 있는 손글씨 분류하기를 해보겠습니다.
MNIST 문제는 다중 분류 문제로써 0~9까지의 손글씨를 분류하는 문제입니다.
이전 포스팅에서 사용한 선형 레이어를 이용하여 0~9의 숫자를 분류해보고, 이후에는 CNN을 이용해 정확도를 개선해보도록 하겠습니다.

Dataset

사용할 MNIST 데이터 세트는 텐서플로 패키지에서 다운로드까지 진행해주는 코드를 포함시켜두었습니다.
우리가 사용할 데이터셋은 텐서플로 패키지에서 제공하는 데이터셋을 이용할건데요.
다른곳에서 MNIST 데이터셋을 이용하여 진행해봐도 무방합니다.

우리가 사용할 MNIST 데이터는 손글씨 데이터로써 흑백 Gray Scale로 된 데이터셋을 말합니다.
28x28 사이즈의 단일 색상채널을 가지고 있으며, 트레이닝셋과 테스트셋이 각각 60,000개와 10,000개로 구성되어 있습니다.

Download

1
2
3
4
import tensorflow as tf
import numpy as np
mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()

데이터 확인

1
2
3
4
5
6
display(x_train.shape)
display(y_train.shape)
display(x_test.shape)
display(y_test.shape)

display(y_test)
(60000, 28, 28)



(60000,)



(10000, 28, 28)



(10000,)



array([7, 2, 1, ..., 4, 5, 6], dtype=uint8)

데이터는 60,000개의 트레이닝셋과 10,000개의 테스트셋을 확인할 수 있고,
테스트 데이터는 28x28 사이즈의 이미지 데이터임을 확인할 수 있습니다. (색상 채널은 단일 채널이라 따로 표기되지 않아요.)
레이블 데이터는 0~9까지의 10개 클래스로 구성되어 있습니다.

데이터 시각화

우리가 다룰 Mnist 데이터는 이미지 데이터 기반입니다.
해당 이미지를 시각화하여 확인해보겠습니다.

1
2
3
4
5
6
7
8
9
10
import matplotlib.pyplot as plt

def show_data(arr):
plt.imshow(arr, cmap=plt.cm.binary)

reshape_data = arr.reshape(-1, )
for index, data in enumerate(reshape_data):
print('{:3d}'.format(data), end='')
if index % 28 == 27:
print()
1
show_data(x_train[1])
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0 51159253159 50  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0 48238252252252237  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0 54227253252239233252 57  6  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0 10 60224252253252202 84252253122  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0163252252252253252252 96189253167  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0 51238253253190114253228 47 79255168  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0 48238252252179 12 75121 21  0  0253243 50  0  0  0  0  0
0  0  0  0  0  0  0  0 38165253233208 84  0  0  0  0  0  0253252165  0  0  0  0  0
0  0  0  0  0  0  0  7178252240 71 19 28  0  0  0  0  0  0253252195  0  0  0  0  0
0  0  0  0  0  0  0 57252252 63  0  0  0  0  0  0  0  0  0253252195  0  0  0  0  0
0  0  0  0  0  0  0198253190  0  0  0  0  0  0  0  0  0  0255253196  0  0  0  0  0
0  0  0  0  0  0 76246252112  0  0  0  0  0  0  0  0  0  0253252148  0  0  0  0  0
0  0  0  0  0  0 85252230 25  0  0  0  0  0  0  0  0  7135253186 12  0  0  0  0  0
0  0  0  0  0  0 85252223  0  0  0  0  0  0  0  0  7131252225 71  0  0  0  0  0  0
0  0  0  0  0  0 85252145  0  0  0  0  0  0  0 48165252173  0  0  0  0  0  0  0  0
0  0  0  0  0  0 86253225  0  0  0  0  0  0114238253162  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0 85252249146 48 29 85178225253223167 56  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0 85252252252229215252252252196130  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0 28199252252253252252233145  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0 25128252253252141 37  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

png

위는 해당 데이터를 시각화 한 결과입니다.
28x28의 데이터는 각 픽셀별로 해당 픽셀이 표시해야 할 색상값을 표시하는데,
이걸 28x28 사이즈에 맞춰 해당 데이터를 출력한 결과입니다.

이미지는 matplotlib을 이용하여 해당 데이터를 시각화 한 결과입니다.

데이터 변환

이미지 데이터를 확인했을 때 0~255까지의 값을 가지고 있는걸 확인할 수 있습니다.
이걸 모델에 효율적으로 학습시키기 위해선 0~1 사이의 값으로 일반화(Nomalization)시키는 과정을 거쳐야 합니다.
그 이외에도 2D컨볼루셔널 레이어를 사용하기 위해선 채널에 대한 값도 명시되어 있어야하기 때문에
(넓이, 높이)의 형태를 가진 데이터를 (넓이,높이,채널)의 형태로 변환시켜보겠습니다.
구조는 어떤식으로든 상관없으며 (채널, 넓이, 높이) 순으로 정렬을 시켜도 됩니다.

1
2
reshape_x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)
reshape_x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)

모델 구성

이전 포스팅에서 다뤘던 Dense 레이어를 이용해 기본적인 신경망으로 학습을 진행해봅시다.

1
2
3
4
5
6
7
8
9
10
11
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers

model = Sequential()
model.add(layers.InputLayer(input_shape=(28, 28, 1)))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten (Flatten)            (None, 784)               0         
_________________________________________________________________
dense (Dense)                (None, 64)                50240     
_________________________________________________________________
dense_1 (Dense)              (None, 16)                1040      
_________________________________________________________________
dense_2 (Dense)              (None, 10)                170       
=================================================================
Total params: 51,450
Trainable params: 51,450
Non-trainable params: 0
_________________________________________________________________

총 3개의 레이어로 구성되어 있으며, MNIST 데이터셋에 맞춰 (28, 28, 1)의 형태에 맞춰 인풋 데이터를 받도록 구성했습니다.
마지막층은 손글씨 데이터의 10개 클래스(0~9까지의 레이블)를 예측하기 위해 출력을 10으로, 활성함수는 softmax를 사용합니다.

모델 컴파일

1
2
3
4
5
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['acc']
)

모델 학습

CPU로 학습 시 대략 25분 정도의 시간이 소요됩니다. (맥북프로 15인치 2017 CTO 기준)
그래서 저는 GPU 환경을 세팅하고 학습을 진행했습니다.
GPU 환경을 구축하기 위한 데스크탑을 만들기 어려우신 분들은 AWS로 GPU환경 세팅하기포스팅을 참고하세요.

모델의 구성을 변경했습니다.
CPU에서도 에폭당 1초의 결과를 보여줍니다.

1
2
3
4
5
6
7
history = model.fit(
reshape_x_train,
y_train,
batch_size=128,
epochs=50,
validation_split=.1,
)
Train on 54000 samples, validate on 6000 samples
Epoch 1/50
54000/54000 [==============================] - 1s 20us/step - loss: 6.2426 - acc: 0.5904 - val_loss: 4.6204 - val_acc: 0.6893
Epoch 2/50
54000/54000 [==============================] - 1s 14us/step - loss: 3.7938 - acc: 0.7398 - val_loss: 1.9817 - val_acc: 0.8462
Epoch 3/50
54000/54000 [==============================] - 1s 15us/step - loss: 1.2180 - acc: 0.8636 - val_loss: 0.6667 - val_acc: 0.8982
Epoch 4/50
54000/54000 [==============================] - 1s 12us/step - loss: 0.6308 - acc: 0.8874 - val_loss: 0.4537 - val_acc: 0.9087
Epoch 5/50
54000/54000 [==============================] - 1s 12us/step - loss: 0.4778 - acc: 0.9019 - val_loss: 0.3766 - val_acc: 0.9243
Epoch 6/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.3996 - acc: 0.9122 - val_loss: 0.3421 - val_acc: 0.9307
Epoch 7/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.3400 - acc: 0.9253 - val_loss: 0.2921 - val_acc: 0.9385
Epoch 8/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.2954 - acc: 0.9321 - val_loss: 0.2361 - val_acc: 0.9437
Epoch 9/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.2549 - acc: 0.9403 - val_loss: 0.2291 - val_acc: 0.9452
Epoch 10/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.2292 - acc: 0.9447 - val_loss: 0.2212 - val_acc: 0.9495
Epoch 11/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.2008 - acc: 0.9490 - val_loss: 0.2298 - val_acc: 0.9518
Epoch 12/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.1871 - acc: 0.9531 - val_loss: 0.1879 - val_acc: 0.9533
Epoch 13/50
54000/54000 [==============================] - 1s 12us/step - loss: 0.1681 - acc: 0.9563 - val_loss: 0.1907 - val_acc: 0.9565
Epoch 14/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.1610 - acc: 0.9572 - val_loss: 0.2007 - val_acc: 0.9540
Epoch 15/50
54000/54000 [==============================] - 1s 12us/step - loss: 0.1477 - acc: 0.9603 - val_loss: 0.1550 - val_acc: 0.9582
Epoch 16/50
54000/54000 [==============================] - 1s 12us/step - loss: 0.1330 - acc: 0.9646 - val_loss: 0.1695 - val_acc: 0.9598
Epoch 17/50
54000/54000 [==============================] - 1s 12us/step - loss: 0.1262 - acc: 0.9661 - val_loss: 0.1596 - val_acc: 0.9627
Epoch 18/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.1226 - acc: 0.9669 - val_loss: 0.1563 - val_acc: 0.9642
Epoch 19/50
54000/54000 [==============================] - 1s 12us/step - loss: 0.1081 - acc: 0.9708 - val_loss: 0.1706 - val_acc: 0.9580
Epoch 20/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0998 - acc: 0.9729 - val_loss: 0.1407 - val_acc: 0.9658
Epoch 21/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0970 - acc: 0.9733 - val_loss: 0.1428 - val_acc: 0.9653
Epoch 22/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0829 - acc: 0.9774 - val_loss: 0.1584 - val_acc: 0.9637
Epoch 23/50
54000/54000 [==============================] - 1s 15us/step - loss: 0.0851 - acc: 0.9763 - val_loss: 0.1609 - val_acc: 0.9645
Epoch 24/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0833 - acc: 0.9766 - val_loss: 0.1462 - val_acc: 0.9682
Epoch 25/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0730 - acc: 0.9794 - val_loss: 0.1344 - val_acc: 0.9658
Epoch 26/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0781 - acc: 0.9788 - val_loss: 0.1881 - val_acc: 0.9623
Epoch 27/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0766 - acc: 0.9786 - val_loss: 0.1654 - val_acc: 0.9615
Epoch 28/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0664 - acc: 0.9809 - val_loss: 0.1617 - val_acc: 0.9663
Epoch 29/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0661 - acc: 0.9819 - val_loss: 0.1393 - val_acc: 0.9670
Epoch 30/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0649 - acc: 0.9814 - val_loss: 0.1492 - val_acc: 0.9655
Epoch 31/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0552 - acc: 0.9842 - val_loss: 0.1669 - val_acc: 0.9647
Epoch 32/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0523 - acc: 0.9849 - val_loss: 0.1880 - val_acc: 0.9648
Epoch 33/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0534 - acc: 0.9852 - val_loss: 0.1748 - val_acc: 0.9652
Epoch 34/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0533 - acc: 0.9847 - val_loss: 0.1453 - val_acc: 0.9708
Epoch 35/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0528 - acc: 0.9850 - val_loss: 0.1709 - val_acc: 0.9673
Epoch 36/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0498 - acc: 0.9856 - val_loss: 0.1492 - val_acc: 0.9653
Epoch 37/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0523 - acc: 0.9850 - val_loss: 0.1639 - val_acc: 0.9663
Epoch 38/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0467 - acc: 0.9863 - val_loss: 0.1682 - val_acc: 0.9660
Epoch 39/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0430 - acc: 0.9876 - val_loss: 0.1630 - val_acc: 0.9657
Epoch 40/50
54000/54000 [==============================] - 1s 13us/step - loss: 0.0397 - acc: 0.9890 - val_loss: 0.1517 - val_acc: 0.9688
Epoch 41/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0450 - acc: 0.9871 - val_loss: 0.1916 - val_acc: 0.9662
Epoch 42/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0416 - acc: 0.9877 - val_loss: 0.1888 - val_acc: 0.9640
Epoch 43/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0457 - acc: 0.9867 - val_loss: 0.1827 - val_acc: 0.9670
Epoch 44/50
54000/54000 [==============================] - 1s 15us/step - loss: 0.0375 - acc: 0.9892 - val_loss: 0.1546 - val_acc: 0.9708
Epoch 45/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0357 - acc: 0.9897 - val_loss: 0.1752 - val_acc: 0.9673
Epoch 46/50
54000/54000 [==============================] - 1s 15us/step - loss: 0.0391 - acc: 0.9894 - val_loss: 0.1772 - val_acc: 0.9672
Epoch 47/50
54000/54000 [==============================] - 1s 15us/step - loss: 0.0386 - acc: 0.9893 - val_loss: 0.1684 - val_acc: 0.9700
Epoch 48/50
54000/54000 [==============================] - 1s 15us/step - loss: 0.0392 - acc: 0.9886 - val_loss: 0.1530 - val_acc: 0.9723
Epoch 49/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0336 - acc: 0.9905 - val_loss: 0.1777 - val_acc: 0.9667
Epoch 50/50
54000/54000 [==============================] - 1s 14us/step - loss: 0.0315 - acc: 0.9907 - val_loss: 0.1884 - val_acc: 0.9678

현재 저의 GPU는 GTX 750TI를 사용중입니다.
에폭당 8초정도의 시간을 보여주고 있네요.

CPU로도 빠른속도를 보일 수 있도록 모델을 변경했습니다.
변경된 모델로 학습에 약 1분정도 소요됩니다.

정확도, 손실 시각화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import matplotlib.pyplot as plt

def show_graph(history_dict):
accuracy = history_dict['acc']
val_accuracy = history_dict['val_acc']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(loss) + 1)

plt.figure(figsize=(16, 1))

plt.subplot(121)
plt.subplots_adjust(top=2)
plt.plot(epochs, accuracy, 'ro', label='Training accuracy')
plt.plot(epochs, val_accuracy, 'r', label='Validation accuracy')
plt.title('Trainging and validation accuracy and loss')
plt.xlabel('Epochs')
plt.ylabel('Accuracy and Loss')

plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1),
fancybox=True, shadow=True, ncol=5)
# plt.legend(bbox_to_anchor=(1, -0.1))

plt.subplot(122)
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1),
fancybox=True, shadow=True, ncol=5)
# plt.legend(bbox_to_anchor=(1, 0))

plt.show()
1
show_graph(history.history)

png

첫번째 에폭의 결과가 너무 편차가 심하여 그래프를 알아보기 힘드니 10번째 에폭의 결과부터 그래프로 그려보도록 하겠습니다.

1
2
3
m = map(lambda x: (x[0], x[1][10:]), history.history.items())
not_noise_history = dict(list(m))
show_graph(not_noise_history)

png

대략 20 에폭부터 과대적합이 시작되는걸 확인할 수 있습니다.

1
model.evaluate(reshape_x_test, y_test)
10000/10000 [==============================] - 0s 14us/step





[0.21370663271014564, 0.9631]

96%의 정확도를 보여주고 있습니다.
레이어를 더 깊고 넓게 구성을 하여 높은 정확도를 만들어보세요.

모델 구성 - CNN

지금까지 포스팅한 간단한 DNN으로도 MNIST 데이터셋의 분류를 높은 정확도로 만들 수 있습니다.
하지만 CNN을 이용하면 적은 파라메터로 좀더 정확한 모델을 구성할 수 있습니다.
비전 인식에서 딥러닝이 크게 활약한 이유가 이런곳에 있습니다.
간단한 CNN 모델을 구성해보겠습니다.

네트워크 구성

Kernel Initializer로는 유명한 Xavier Initializer를 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
model = Sequential()
# model.add(layers.InputLayer())
model.add(layers.Conv2D(
input_shape=(28, 28, 1),
filters=64,
kernel_size=(2, 2),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='glorot_normal',
))
model.add(layers.MaxPool2D())
model.add(layers.Conv2D(
filters=128,
kernel_size=(2, 2),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='glorot_normal'
))
model.add(layers.MaxPool2D())
model.add(layers.Flatten())
model.add(layers.Dense(10, activation='softmax'))

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 28, 28, 64)        320       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 14, 14, 128)       32896     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 6272)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 10)                62730     
=================================================================
Total params: 95,946
Trainable params: 95,946
Non-trainable params: 0
_________________________________________________________________

모델 컴파일

저희는 레이블 데이터를 원-핫 인코딩을 진행하지 않았기 때문에 Loss 펑션에 sparse_categorical_crossentropy를 지정합니다.

1
2
3
4
5
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['acc']
)

모델 학습

CPU로 돌리시는 경우 시간이 조금 걸릴 수 있으니 커피한잔 하고 오세요~

1
2
3
4
5
6
7
history = model.fit(
reshape_x_train,
y_train,
batch_size=128,
epochs=20,
validation_split=.1,
)
Train on 54000 samples, validate on 6000 samples
Epoch 1/20
54000/54000 [==============================] - 15s 278us/step - loss: 4.6634 - acc: 0.6814 - val_loss: 0.0709 - val_acc: 0.9788
Epoch 2/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0665 - acc: 0.9799 - val_loss: 0.0560 - val_acc: 0.9840
Epoch 3/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0472 - acc: 0.9854 - val_loss: 0.0516 - val_acc: 0.985570 - acc: 0
Epoch 4/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0372 - acc: 0.9880 - val_loss: 0.0491 - val_acc: 0.9863 loss: 0.0353 - ac - ETA
Epoch 5/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0290 - acc: 0.9904 - val_loss: 0.0449 - val_acc: 0.9882
Epoch 6/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0235 - acc: 0.9919 - val_loss: 0.0557 - val_acc: 0.9848
Epoch 7/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0222 - acc: 0.9928 - val_loss: 0.0687 - val_acc: 0.9840
Epoch 8/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0228 - acc: 0.9926 - val_loss: 0.0524 - val_acc: 0.9882
Epoch 9/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0176 - acc: 0.9940 - val_loss: 0.0798 - val_acc: 0.9818
Epoch 10/20
54000/54000 [==============================] - 9s 161us/step - loss: 0.0171 - acc: 0.9943 - val_loss: 0.0748 - val_acc: 0.9853
Epoch 11/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0167 - acc: 0.9946 - val_loss: 0.0734 - val_acc: 0.9860
Epoch 12/20
54000/54000 [==============================] - 9s 161us/step - loss: 0.0136 - acc: 0.9953 - val_loss: 0.0706 - val_acc: 0.9860
Epoch 13/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0148 - acc: 0.9953 - val_loss: 0.0928 - val_acc: 0.9842
Epoch 14/20
54000/54000 [==============================] - 9s 161us/step - loss: 0.0130 - acc: 0.9958 - val_loss: 0.0892 - val_acc: 0.9847
Epoch 15/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0143 - acc: 0.9954 - val_loss: 0.0949 - val_acc: 0.9847
Epoch 16/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0141 - acc: 0.9952 - val_loss: 0.0868 - val_acc: 0.9855
Epoch 17/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0123 - acc: 0.9958 - val_loss: 0.0988 - val_acc: 0.9838
Epoch 18/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0067 - acc: 0.9976 - val_loss: 0.1250 - val_acc: 0.9805
Epoch 19/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0077 - acc: 0.9977 - val_loss: 0.0855 - val_acc: 0.9860
Epoch 20/20
54000/54000 [==============================] - 9s 160us/step - loss: 0.0117 - acc: 0.9963 - val_loss: 0.0942 - val_acc: 0.9865

제 노트북 CPU를 기준으로 모델을 학습하는데 에폭당 48초정도 걸리네요.
20 에폭을 돌렸으니 대략 16분 정도 걸릴거라 보시면 됩니다.

저는 GPU를 사용하겠습니다.

정확도 및 손실 시각화

1
show_graph(history.history)

png

첫 에폭의 결과 때문에 편차가 심하여 첫 에폭을 제외하고 보도록 하겠습니다.

1
2
3
m = map(lambda x: (x[0], x[1][1:]), history.history.items())
not_noise_history = dict(list(m))
show_graph(not_noise_history)

png

검증세트의 손실을 보니 8번째 에폭부터 과대적합이 된 것 같군요.
과대적합을 해결할 수 있다면 정확도를 높일 수 있을까요 ?

과대적합 해결

위와 같은 과대적합을 피하는 방법으로는 여러가지 방법이 있는데요.
L1, L2 규제와 같은 정규화를 이용하여 가중치를 감쇠(Weight Decay)시키는 방법과 드랍아웃, 모델의 하이퍼파라메터 튜닝 등으로 과대적합을 피하려 할 수 있습니다.

모델의 재구성

기존 모델은 2단의 컨볼루션 레이어와 풀링 레이어 사용했습니다.
모델이 정보를 더 가질 수 있도록 레이어를 더 구성시키고, 드랍아웃과 L2 규제를 걸어 더 복잡한 모델이지만 과대적합은 피할 수 있도록 재구성해보죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras import regularizers

model = Sequential()
model.add(layers.Conv2D(
input_shape=(28, 28, 1),
filters=32,
kernel_size=(2, 2),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='glorot_normal',
kernel_regularizer=regularizers.l2(0.01),
))
model.add(layers.MaxPool2D())
model.add(layers.Dropout(.5))

model.add(layers.Conv2D(
filters=64,
kernel_size=(2, 2),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='glorot_normal',
kernel_regularizer=regularizers.l2(0.01),
))
model.add(layers.MaxPool2D())
model.add(layers.Dropout(.5))

model.add(layers.Conv2D(
filters=128,
kernel_size=(2, 2),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='glorot_normal',
kernel_regularizer=regularizers.l2(0.01),
))
model.add(layers.MaxPool2D())
model.add(layers.Dropout(.5))

model.add(layers.Conv2D(
filters=256,
kernel_size=(2, 2),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='glorot_normal',
kernel_regularizer=regularizers.l2(0.01),
))
model.add(layers.MaxPool2D())
model.add(layers.Dropout(.5))

model.add(layers.Flatten())
model.add(layers.Dense(10, activation='softmax', kernel_regularizer=regularizers.l2(0.01),))

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_2 (Conv2D)            (None, 28, 28, 32)        160       
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 14, 14, 32)        0         
_________________________________________________________________
dropout (Dropout)            (None, 14, 14, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 14, 14, 64)        8256      
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 7, 7, 64)          0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 7, 7, 64)          0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 7, 7, 128)         32896     
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 3, 3, 128)         0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 3, 3, 128)         0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 3, 3, 256)         131328    
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 1, 1, 256)         0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 1, 1, 256)         0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 256)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 10)                2570      
=================================================================
Total params: 175,210
Trainable params: 175,210
Non-trainable params: 0
_________________________________________________________________

아까보다 두개의 레이어를 더 추가했고, 파라메터 수가 2배 가까이 늘어난게 보입니다.
중간중간 드랍아웃이 적용되었고, 레이어별로 L2 정규화를 추가했습니다.
이러한 정규화 기법을 추가하지 않고 학습을 했다면 이전보다 더 빠르게 과대적합이 일어나겠죠?
그럼 드랍아웃과 L2규제를 추가한 이후로 모델이 과대적합을 어떻게 견뎌내는지 확인해봅시다.

모델 컴파일

1
2
3
4
5
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['acc']
)

학습 시작

Dropout과 Weight Decay를 추가함으로써 과대적합을 막았고, 뉴런의 학습에 제약을 두었으니, 학습이 더뎌질 수 밖에 없습니다.
충분히 학습할 수 있도록 에폭수를 조금 더 늘려 학습을 진행하도록 합니다.

1
2
3
4
5
6
7
history = model.fit(
reshape_x_train,
y_train,
batch_size=128,
epochs=30,
validation_split=.1,
)
Train on 54000 samples, validate on 6000 samples
Epoch 1/30
54000/54000 [==============================] - 11s 199us/step - loss: 16.5374 - acc: 0.0995 - val_loss: 15.8382 - val_acc: 0.0952
Epoch 2/30
54000/54000 [==============================] - 9s 166us/step - loss: 15.3383 - acc: 0.0990 - val_loss: 15.0753 - val_acc: 0.0952 -
Epoch 3/30
54000/54000 [==============================] - 9s 165us/step - loss: 11.6865 - acc: 0.1130 - val_loss: 2.4193 - val_acc: 0.3727
Epoch 4/30
54000/54000 [==============================] - 9s 165us/step - loss: 1.6351 - acc: 0.5584 - val_loss: 0.6810 - val_acc: 0.9327
Epoch 5/30
54000/54000 [==============================] - 9s 165us/step - loss: 0.9334 - acc: 0.8091 - val_loss: 0.4898 - val_acc: 0.9630 - acc
Epoch 6/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.7393 - acc: 0.8710 - val_loss: 0.4268 - val_acc: 0.9718
Epoch 7/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.6583 - acc: 0.8908 - val_loss: 0.4011 - val_acc: 0.9752
Epoch 8/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.6127 - acc: 0.9045 - val_loss: 0.3935 - val_acc: 0.9735
Epoch 9/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.6006 - acc: 0.9076 - val_loss: 0.3735 - val_acc: 0.9792: 0s - loss: 0.6030 -
Epoch 10/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5846 - acc: 0.9099 - val_loss: 0.3665 - val_acc: 0.9803
Epoch 11/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5774 - acc: 0.9119 - val_loss: 0.3618 - val_acc: 0.9782
Epoch 12/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5714 - acc: 0.9148 - val_loss: 0.3764 - val_acc: 0.9780
Epoch 13/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5682 - acc: 0.9154 - val_loss: 0.3707 - val_acc: 0.9763
Epoch 14/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5633 - acc: 0.9163 - val_loss: 0.3611 - val_acc: 0.9813
Epoch 15/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5628 - acc: 0.9177 - val_loss: 0.3728 - val_acc: 0.9792
Epoch 16/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5665 - acc: 0.9181 - val_loss: 0.3630 - val_acc: 0.9820
Epoch 17/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5650 - acc: 0.9168 - val_loss: 0.3526 - val_acc: 0.9835 - ETA: 3s - los
Epoch 18/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5632 - acc: 0.9172 - val_loss: 0.3584 - val_acc: 0.9823
Epoch 19/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5597 - acc: 0.9176 - val_loss: 0.3576 - val_acc: 0.9818
Epoch 20/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5548 - acc: 0.9193 - val_loss: 0.3577 - val_acc: 0.9813
Epoch 21/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5558 - acc: 0.9202 - val_loss: 0.3812 - val_acc: 0.9758
Epoch 22/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5601 - acc: 0.9179 - val_loss: 0.3601 - val_acc: 0.9830
Epoch 23/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5571 - acc: 0.9189 - val_loss: 0.3596 - val_acc: 0.9818
Epoch 24/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5495 - acc: 0.9200 - val_loss: 0.3568 - val_acc: 0.9817
Epoch 25/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5490 - acc: 0.9202 - val_loss: 0.3602 - val_acc: 0.9820
Epoch 26/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5529 - acc: 0.9205 - val_loss: 0.3563 - val_acc: 0.9822
Epoch 27/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5538 - acc: 0.9198 - val_loss: 0.3534 - val_acc: 0.9840
Epoch 28/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5492 - acc: 0.9204 - val_loss: 0.3573 - val_acc: 0.9812
Epoch 29/30
54000/54000 [==============================] - 9s 164us/step - loss: 0.5530 - acc: 0.9198 - val_loss: 0.3508 - val_acc: 0.9848
Epoch 30/30
54000/54000 [==============================] - 9s 163us/step - loss: 0.5521 - acc: 0.9198 - val_loss: 0.3648 - val_acc: 0.9792

모델 평가

정규화 기법을 적용한 학습 결과가 어떻게 변했는지 확인 해봅시다.

1
show_graph(history.history)

png

가중치의 급격환 변화를 L2 규제를 통해 막아두어 처음 2에폭까지는 큰 변화가 없다가 3~5 에폭이 지나서 모델이 높은 성능에 근접해지는걸 확인할 수 있습니다.
그럼 조금 더 자세하게 볼 수 있도록 첫 5에폭까지의 데이터는 제외하고 이후의 데이터를 그래프로 확인해보겠습니다.

1
2
3
m = map(lambda x: (x[0], x[1][5:]), history.history.items())
not_noise_history = dict(list(m))
show_graph(not_noise_history)

png

트레이닝 세트의 정확도가 낮게 측정되는건 드랍아웃 때문입니다.
학습마다 몇몇의 특정 뉴런을 비활성화 시키기 때문에 정확도가 낮아지게 됩니다.
훈련을 마친 이후 예측시에는 드랍아웃 시키는 뉴런의 수를 0%로 맞추고 사용합니다.
(모든 뉴런을 사용)

1
2
3
4
model.evaluate(
reshape_x_train,
y_train
)
60000/60000 [==============================] - 6s 103us/step





[0.38104470246632893, 0.9750666666666666]

드랍아웃을 해제한 후 훈련세트의 손실값과 정확도입니다.

1
2
3
4
model.evaluate(
reshape_x_test,
y_test
)
10000/10000 [==============================] - 1s 98us/step





[0.3756561163902283, 0.9753]

테스트 세트의 손실과 정확도입니다.
98%의 정확도를 보여주고 있습니다.
모델의 네트워크를 변경하여 99% 정확도에 도전해보세요.

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2019 Commit once a day All Rights Reserved.

UV : | PV :