딥러닝으로 개와 고양이를 분류해보자 - 전이학습과 미세조정

Transfer Learning & Find Tuning

안녕하세요.
이번 시간은 학습된 모델을 우리의 도메인에 맞춰 사용할 수 있도록 변경하는법을 알아봅시다.
저번 포스팅에서 개와 고양이를 분류하는 CNN 모델을 만들어보고 학습을 진행해봤는데요.
ResNet50 모델의 경우 모델이 무거운데다 저희의 학습 데이터량도 많지 않고(증식을 이용하긴 했으나, 과적합될 가능성이 존재),
학습시간도 모자랐었습니다. (32에폭 도는데 대략 40분정도의 시간이 소요됨. 대회에 나간 모델의 경우 2~3주동안 학습을 진행했다고 함.)

그럼 매번 모델을 만들때마다 새로 학습을 진행해야 할까요 ?
개와 고양이의 분류 모델을 만드는데 2주,
개, 고양이와 거북이를 분류하는 모델을 다시 만드는데 2주… 이렇게 시간이 소요될 수 있습니다.

저희가 사용한 모델들은 CNN 레이어는 이미지의 특징들을 뽑아내주고,
마지막 Fully Connected 레이어에서 그 해당 특징들을 기반으로 분류를 하는 형태를 띄는데요.
그럼 기존에 학습된 모델을 가지고 특징을 추출하여 FC레이어만 새로 재학습을 할수는 없을까요 ?

오늘은 InceptionV3 모델을 이용하여 Feature Extraction, Transfer Learning을 진행해보도록 하겠습니다.
(이번에 사용하는 InceptionV3 모델은 마지막 레이어가 FC 레이어가 아닌 GAP를 통해 분류를 하고 있지만, Transfer learning의 기본적인 부분은 동일합니다.)

Feature Extraction

어파인 레이어(Fully Connected Layer)를 거치기 바로 직전의 추상화된 레이어의 특징들을 보틀넥 피쳐라 말합니다.
저희는 기존 imagenet 데이터셋을 기반으로 학습된 모델을 사용하여, 이미지의 특징을 추출하고,
이 특징을 기반으로 FC레이어를 분류시킬 수 있는 모델을 만들어 보도록 하겠습니다.
이 과정을 Feature Extraction 혹은 Transfer Learning이라고 말하기도 합니다.

이 예제는 마지막 분류 레이어만 학습을 진행해도 되기 때문에 CPU로도 빠른 속도로 학습이 가능합니다.
이 뒤의 Fine Tuning시에는 GPU가 아닐경우 많은 시간이 소요되니 GPU 환경을 권장합니다.
(피쳐 추출 부분에서 시간이 많이 소요되는건 비밀..)

데이터셋 준비

데이터셋은 이전 포스팅에서 이용한 개, 고양이 데이터셋을 이용합니다.

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
import os
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# 위노그라드 알고리즘 설정 (GPU 사용시 conv 연산이 빨라짐)
os.environ['TF_ENABLE_WINOGRAD_NONFUSED'] = '1'

rootPath = './datasets/cat-and-dog'

# 이미지의 사이즈는 논문에서는 (224, 224, 3)을 사용하여, 빠른 학습을 위해 사이즈를 조정
IMG_SIZE = (150, 150, 3)

imageGenerator = ImageDataGenerator(
rescale=1./255,
rotation_range=20,
width_shift_range=0.1,
height_shift_range=0.1,
brightness_range=[.2, .2],
horizontal_flip=True
)

trainGen = imageGenerator.flow_from_directory(
os.path.join(rootPath, 'training_set'),
target_size=IMG_SIZE[:2],
batch_size=20
)

testGen = ImageDataGenerator(
rescale=1./255,
).flow_from_directory(
os.path.join(rootPath, 'test_set'),
target_size=IMG_SIZE[:2],
batch_size=20,
)
Found 8005 images belonging to 2 classes.
Found 2023 images belonging to 2 classes.

모델 구성

모델은 피쳐추출기로 사용할 InceptionV3 모델과 이를 분류할 분류기 두개를 만들겠습니다.
이미지의 특징을 추출해주니 꼭 신경망이 아니더라도 다른 분류기를(예를 들면 SVM 등) 사용하셔도 무방합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from tensorflow.keras import layers
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.applications import InceptionV3

# Fc 레이어를 포함하지 않고, imagenet기반으로 학습된 가중치를 로드한 뒤 GAP 레이어를 추가
extractor = Sequential()
extractor.add(InceptionV3(include_top=False, weights='imagenet', input_shape=IMG_SIZE))
extractor.add(layers.GlobalAveragePooling2D())

extractor_output_shape = extractor.get_output_shape_at(0)[1:]

model = Sequential()
model.add(layers.InputLayer(input_shape=extractor_output_shape))
model.add(layers.Dense(2, activation='sigmoid'))

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 2)                 4098      
=================================================================
Total params: 4,098
Trainable params: 4,098
Non-trainable params: 0
_________________________________________________________________

모델 컴파일

1
2
3
4
5
6
7
8
9
10
11
12
13
from tensorflow.keras.optimizers import Adam
optimizer = Adam(lr=0.001)
model.compile(
optimizer=optimizer,
loss='binary_crossentropy',
metrics=['acc'],
)

extractor.compile(
optimizer=optimizer,
loss='binary_crossentropy',
metrics=['acc'],
)

특징 추출기 생성

이미지 제너레이터에서 특징을 추출하기 위한 펑션을 하나 만들어 줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
def get_features(extractor, gen, cnt):
bs = gen.batch_size
ds = cnt
extractor_shapes = list(extractor.get_output_shape_at(0)[1:])

features = np.empty([0] + extractor_shapes)
labels = np.empty((0, 2))

for i, (trainX, trainY) in enumerate(gen):
features = np.append(features, extractor.predict(trainX), axis=0)
labels = np.append(labels, trainY, axis=0)
print('batch index: {}/{}'.format(i * bs, ds), end='\r')

if bs * i >= cnt:
break
print()
return features, labels

특징추출

1
2
trainX, trainY = get_features(extractor, trainGen, 3000)
testX, testY = get_features(extractor, testGen, 1000)
batch index: 3000/3000
batch index: 1000/1000

학습 시작

1
2
3
4
5
6
7
8
9
epochs = 32

history = model.fit(
trainX,
trainY,
epochs=epochs,
batch_size=32,
validation_split=.1,
)
Train on 2718 samples, validate on 302 samples
Epoch 1/32
2718/2718 [==============================] - 2s 575us/step - loss: 0.3848 - acc: 0.8214 - val_loss: 0.2531 - val_acc: 0.8791
Epoch 2/32
2718/2718 [==============================] - 0s 95us/step - loss: 0.2236 - acc: 0.9031 - val_loss: 0.2380 - val_acc: 0.8891
Epoch 3/32
2718/2718 [==============================] - 0s 88us/step - loss: 0.1946 - acc: 0.9187 - val_loss: 0.2312 - val_acc: 0.8891
Epoch 4/32
2718/2718 [==============================] - 0s 88us/step - loss: 0.1733 - acc: 0.9284 - val_loss: 0.2322 - val_acc: 0.8907
Epoch 5/32
2718/2718 [==============================] - 0s 90us/step - loss: 0.1541 - acc: 0.9391 - val_loss: 0.2334 - val_acc: 0.8940
Epoch 6/32
2718/2718 [==============================] - 0s 93us/step - loss: 0.1441 - acc: 0.9430 - val_loss: 0.2444 - val_acc: 0.8957
Epoch 7/32
2718/2718 [==============================] - 0s 86us/step - loss: 0.1337 - acc: 0.9513 - val_loss: 0.2425 - val_acc: 0.8825
Epoch 8/32
2718/2718 [==============================] - 0s 88us/step - loss: 0.1295 - acc: 0.9507 - val_loss: 0.2432 - val_acc: 0.8940
Epoch 9/32
2718/2718 [==============================] - 0s 87us/step - loss: 0.1211 - acc: 0.9533 - val_loss: 0.2524 - val_acc: 0.8974
Epoch 10/32
2718/2718 [==============================] - 0s 90us/step - loss: 0.1143 - acc: 0.9573 - val_loss: 0.2539 - val_acc: 0.8924
Epoch 11/32
2718/2718 [==============================] - 0s 89us/step - loss: 0.1123 - acc: 0.9562 - val_loss: 0.2560 - val_acc: 0.8874
Epoch 12/32
2718/2718 [==============================] - 0s 85us/step - loss: 0.1009 - acc: 0.9639 - val_loss: 0.2659 - val_acc: 0.8940
Epoch 13/32
2718/2718 [==============================] - 0s 85us/step - loss: 0.0955 - acc: 0.9682 - val_loss: 0.2550 - val_acc: 0.8924
Epoch 14/32
2718/2718 [==============================] - 0s 91us/step - loss: 0.0901 - acc: 0.9673 - val_loss: 0.2738 - val_acc: 0.8940
Epoch 15/32
2718/2718 [==============================] - 0s 90us/step - loss: 0.0897 - acc: 0.9678 - val_loss: 0.2909 - val_acc: 0.8907
Epoch 16/32
2718/2718 [==============================] - 0s 87us/step - loss: 0.0843 - acc: 0.9722 - val_loss: 0.2811 - val_acc: 0.8891
Epoch 17/32
2718/2718 [==============================] - 0s 87us/step - loss: 0.0812 - acc: 0.9731 - val_loss: 0.2788 - val_acc: 0.8924
Epoch 18/32
2718/2718 [==============================] - 0s 90us/step - loss: 0.0800 - acc: 0.9737 - val_loss: 0.3198 - val_acc: 0.8891
Epoch 19/32
2718/2718 [==============================] - 0s 89us/step - loss: 0.0746 - acc: 0.9748 - val_loss: 0.2781 - val_acc: 0.8940
Epoch 20/32
2718/2718 [==============================] - 0s 85us/step - loss: 0.0759 - acc: 0.9748 - val_loss: 0.2898 - val_acc: 0.8924
Epoch 21/32
2718/2718 [==============================] - 0s 87us/step - loss: 0.0704 - acc: 0.9774 - val_loss: 0.2810 - val_acc: 0.8990
Epoch 22/32
2718/2718 [==============================] - 0s 88us/step - loss: 0.0660 - acc: 0.9792 - val_loss: 0.2831 - val_acc: 0.8940
Epoch 23/32
2718/2718 [==============================] - 0s 88us/step - loss: 0.0631 - acc: 0.9825 - val_loss: 0.2886 - val_acc: 0.8974
Epoch 24/32
2718/2718 [==============================] - 0s 89us/step - loss: 0.0611 - acc: 0.9827 - val_loss: 0.2841 - val_acc: 0.8990
Epoch 25/32
2718/2718 [==============================] - 0s 89us/step - loss: 0.0599 - acc: 0.9829 - val_loss: 0.2893 - val_acc: 0.8974
Epoch 26/32
2718/2718 [==============================] - 0s 89us/step - loss: 0.0575 - acc: 0.9845 - val_loss: 0.2965 - val_acc: 0.8990
Epoch 27/32
2718/2718 [==============================] - 0s 89us/step - loss: 0.0556 - acc: 0.9875 - val_loss: 0.2971 - val_acc: 0.9007
Epoch 28/32
2718/2718 [==============================] - 0s 90us/step - loss: 0.0531 - acc: 0.9860 - val_loss: 0.3044 - val_acc: 0.9007
Epoch 29/32
2718/2718 [==============================] - 0s 88us/step - loss: 0.0512 - acc: 0.9875 - val_loss: 0.3063 - val_acc: 0.8974
Epoch 30/32
2718/2718 [==============================] - 0s 87us/step - loss: 0.0498 - acc: 0.9893 - val_loss: 0.3097 - val_acc: 0.8990
Epoch 31/32
2718/2718 [==============================] - 0s 91us/step - loss: 0.0499 - acc: 0.9880 - val_loss: 0.3155 - val_acc: 0.8924
Epoch 32/32
2718/2718 [==============================] - 0s 94us/step - loss: 0.0463 - acc: 0.9904 - val_loss: 0.3197 - val_acc: 0.8957

결과 시각화

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
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')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')

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

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.show()

def smooth_curve(points, factor=.8):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
1
show_graph(history.history)

png

1
2
3
4
smooth_data = {}
for key, val in history.history.items():
smooth_data[key] = smooth_curve(val[:])
show_graph(smooth_data)

png

1
model.evaluate(testX, testY)
1020/1020 [==============================] - 0s 59us/step





[0.16686982696547228, 0.9480392149850434]

imagenet으로 학습한 모델을 이용했을 때 이미지의 분류를 위한 특징을 잘 뽑아내도록 필터가 학습되어 있으므로,
첫 에폭에서부터 높은 결과를 보여주는걸 확인할 수 있습니다.

Fine Tuning

Find Tuning이란 미세조정인데요.
아무래도 기존 학습된 네트워크는 학습한 데이터셋에 잘맞도록 학습이 되어 있겠죠.
그렇다고 우리 데이터셋으로 밑바닥부터 학습시키기엔 데이터를 모으기가 힘든 경우가 대다수입니다.
이런 경우에 학습된 네트워크를 미세조정하여 우리의 데이터셋에 조금 더 잘 맞도록 모델의 파라메터를 조정하는 과정을 거치는데요.
이 과정을 Fine Tuning이라고 합니다.

하지만 연산량이 많아지기 때문에 위의 Transfer Learning보다 상대적으로 시간이 조금 더 필요하게 됩니다.
Fine Tuning은 CPU로 하기엔 연산량이 많아 GPU 사용을 권장합니다.

미세조정의 순서는 대략 아래와 같습니다.

  1. 분류기를 학습한다. (이때 분류기를 제외한 모델의 모든 레이어는 가중치를 업데이트 되지 않도록 동결(Freeze) 시킵니다)
  2. 모델의 마지막 레이어들을 미세조정을 실시한다.

이렇게 먼저 분류기를 학습하는 이유는 저희가 추가한 분류기의 가중치는 랜덤하게 초기화가 되어 있습니다.
이러한 분류기를 통해 그대로 보틀넥 레이어를 재학습 할 경우, Loss가 높아 기존의 레이어가 망가질 우려가 있습니다.
기존 학습된 모델의 가중치를 최대한 미세하게 조정하기 위하여 분류기를 선학습 한 이후, 보틀넥 레이어를 다시 재학습 하도록 합니다.

가장 중요한 부분은 모델의 가중치 동결인데요.
기존 학습된 모델의 경우 앞의 레이어들은 이미지를 직관적으로 이해할 수 있도록 이미지 자체의 엣지 디텍터등의 역할을 수행합니다.
레이어가 뒤로 향할수록 필터에 대한 데이터를 추상적으로 표현하게 되는데, 앞단의 레이어들의 가중치를 동결함으로써 학습 시 필터의 역할이 망가지지 않도록 하는 역할을 합니다.

데이터셋 준비

위에서 사용한 제너레이터를 조금 변경하여 사용하도록 하겠습니다.

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
rootPath = './datasets/cat-and-dog'

IMG_SIZE = (150, 150, 3)
imageGenerator = ImageDataGenerator(
rescale=1./255,
rotation_range=20,
width_shift_range=0.1,
height_shift_range=0.1,
brightness_range=[.2, .2],
horizontal_flip=True,
validation_split=0.1,
)

trainGen = imageGenerator.flow_from_directory(
os.path.join(rootPath, 'training_set'),
target_size=IMG_SIZE[:2],
batch_size=32,
subset='training',
)

validationGen = imageGenerator.flow_from_directory(
os.path.join(rootPath, 'training_set'),
target_size=IMG_SIZE[:2],
batch_size=32,
subset='validation',
)

testGen = ImageDataGenerator(
rescale=1./255,
).flow_from_directory(
os.path.join(rootPath, 'test_set'),
target_size=IMG_SIZE[:2],
batch_size=32,
)
Found 7205 images belonging to 2 classes.
Found 800 images belonging to 2 classes.
Found 2023 images belonging to 2 classes.

모델 구성

위에서 사용한 InceptionV3 모델을 이용해도 되지만, 저희는 새로운 모델을 만들어 진행해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
inception = InceptionV3(include_top=False, weights='imagenet', input_shape=IMG_SIZE)
gap = layers.GlobalAveragePooling2D()(inception.output)

# 마지막 31번째 뒤 레이어들을 제외한 모든 레이어를 가중치 동결합니다.
for l in inception.layers[:-31]:
l.trainable = False

# 위의 Transfer Learning에서 훈련시킨 분류기를 이용합니다.
classifier = model

model = Model(inception.input, classifier(gap))

model.summary()
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_3 (InputLayer)            (None, 150, 150, 3)  0                                            
__________________________________________________________________________________________________
conv2d_94 (Conv2D)              (None, 74, 74, 32)   864         input_3[0][0]                    
__________________________________________________________________________________________________
batch_normalization_94 (BatchNo (None, 74, 74, 32)   96          conv2d_94[0][0]                  
__________________________________________________________________________________________________
activation_94 (Activation)      (None, 74, 74, 32)   0           batch_normalization_94[0][0]     
__________________________________________________________________________________________________
conv2d_95 (Conv2D)              (None, 72, 72, 32)   9216        activation_94[0][0]              



                                      중간 생략 ...


__________________________________________________________________________________________________
concatenate_3 (Concatenate)     (None, 3, 3, 768)    0           activation_185[0][0]             
                                                                 activation_186[0][0]             
__________________________________________________________________________________________________
activation_187 (Activation)     (None, 3, 3, 192)    0           batch_normalization_187[0][0]    
__________________________________________________________________________________________________
mixed10 (Concatenate)           (None, 3, 3, 2048)   0           activation_179[0][0]             
                                                                 mixed9_1[0][0]                   
                                                                 concatenate_3[0][0]              
                                                                 activation_187[0][0]             
__________________________________________________________________________________________________
global_average_pooling2d_1 (Glo (None, 2048)         0           mixed10[0][0]                    
__________________________________________________________________________________________________
sequential_1 (Sequential)       (None, 2)            4098        global_average_pooling2d_1[0][0] 
==================================================================================================
Total params: 21,806,882
Trainable params: 6,077,634
Non-trainable params: 15,729,248
__________________________________________________________________________________________________

모델의 구성을 잘 보시면 총 파라메터의 개수는 약 2,200만개의 파라메터를 가지고 있지만,
훈련이 가능한 파라메터의 수는 약 600만개 입니다.
나머지 1,500만개의 파라메터는 저희가 설정한대로 가중치 업데이트가 동결 되었습니다.

모델 컴파일

모델의 미세조정을 위해 러닝레이트를 줄여 학습을 진행합니다.

1
2
3
4
5
6
optimizer = Adam(lr=0.0001)
model.compile(
optimizer=optimizer,
loss='binary_crossentropy',
metrics=['acc'],
)

학습 시작

1
2
3
4
5
6
7
8
epochs = 32
history = model.fit_generator(
generator=trainGen,
epochs=epochs,
steps_per_epoch=trainGen.n / trainGen.batch_size,
validation_data=validationGen,
validation_steps=validationGen.n / validationGen.batch_size,
)
Epoch 1/32
226/225 [==============================] - 57s 253ms/step - loss: 0.2900 - acc: 0.8724 - val_loss: 0.3690 - val_acc: 0.9169
Epoch 2/32
226/225 [==============================] - 55s 245ms/step - loss: 0.2683 - acc: 0.8868 - val_loss: 0.4129 - val_acc: 0.9081
Epoch 3/32
226/225 [==============================] - 56s 246ms/step - loss: 0.2487 - acc: 0.8915 - val_loss: 0.4223 - val_acc: 0.9081
Epoch 4/32
226/225 [==============================] - 56s 246ms/step - loss: 0.2156 - acc: 0.9079 - val_loss: 0.4608 - val_acc: 0.9131
Epoch 5/32
226/225 [==============================] - 55s 244ms/step - loss: 0.2214 - acc: 0.9061 - val_loss: 0.3838 - val_acc: 0.9144
Epoch 6/32
226/225 [==============================] - 55s 243ms/step - loss: 0.1986 - acc: 0.9154 - val_loss: 0.4782 - val_acc: 0.9019
Epoch 7/32
226/225 [==============================] - 55s 245ms/step - loss: 0.1875 - acc: 0.9258 - val_loss: 0.3910 - val_acc: 0.9313
Epoch 8/32
226/225 [==============================] - 55s 243ms/step - loss: 0.1663 - acc: 0.9322 - val_loss: 0.4091 - val_acc: 0.9250
Epoch 9/32
226/225 [==============================] - 55s 241ms/step - loss: 0.1813 - acc: 0.9256 - val_loss: 0.5089 - val_acc: 0.9163
Epoch 10/32
226/225 [==============================] - 55s 244ms/step - loss: 0.1742 - acc: 0.9275 - val_loss: 0.4822 - val_acc: 0.9094
Epoch 11/32
226/225 [==============================] - 55s 242ms/step - loss: 0.1580 - acc: 0.9341 - val_loss: 0.4449 - val_acc: 0.9137
Epoch 12/32
226/225 [==============================] - 56s 247ms/step - loss: 0.1556 - acc: 0.9375 - val_loss: 0.5008 - val_acc: 0.9163
Epoch 13/32
226/225 [==============================] - 55s 243ms/step - loss: 0.1676 - acc: 0.9314 - val_loss: 0.4137 - val_acc: 0.9087
Epoch 14/32
226/225 [==============================] - 55s 244ms/step - loss: 0.1466 - acc: 0.9375 - val_loss: 0.6155 - val_acc: 0.8925
Epoch 15/32
226/225 [==============================] - 55s 244ms/step - loss: 0.1364 - acc: 0.9459 - val_loss: 0.5151 - val_acc: 0.9125
Epoch 16/32
226/225 [==============================] - 55s 243ms/step - loss: 0.1325 - acc: 0.9458 - val_loss: 0.5785 - val_acc: 0.9025
Epoch 17/32
226/225 [==============================] - 56s 246ms/step - loss: 0.1303 - acc: 0.9496 - val_loss: 0.5260 - val_acc: 0.9081
Epoch 18/32
226/225 [==============================] - 55s 242ms/step - loss: 0.1337 - acc: 0.9463 - val_loss: 0.5409 - val_acc: 0.9106
Epoch 19/32
226/225 [==============================] - 56s 247ms/step - loss: 0.1189 - acc: 0.9550 - val_loss: 0.4270 - val_acc: 0.9181
Epoch 20/32
226/225 [==============================] - 55s 245ms/step - loss: 0.1166 - acc: 0.9532 - val_loss: 0.5129 - val_acc: 0.9250
Epoch 21/32
226/225 [==============================] - 55s 244ms/step - loss: 0.1117 - acc: 0.9556 - val_loss: 0.7072 - val_acc: 0.8981
Epoch 22/32
226/225 [==============================] - 56s 247ms/step - loss: 0.1155 - acc: 0.9562 - val_loss: 0.4914 - val_acc: 0.9187
Epoch 23/32
226/225 [==============================] - 56s 247ms/step - loss: 0.1055 - acc: 0.9602 - val_loss: 0.6032 - val_acc: 0.9006
Epoch 24/32
226/225 [==============================] - 55s 246ms/step - loss: 0.1140 - acc: 0.9549 - val_loss: 0.4621 - val_acc: 0.9044
Epoch 25/32
226/225 [==============================] - 55s 244ms/step - loss: 0.1158 - acc: 0.9532 - val_loss: 0.5440 - val_acc: 0.9144
Epoch 26/32
226/225 [==============================] - 55s 241ms/step - loss: 0.1042 - acc: 0.9628 - val_loss: 0.5088 - val_acc: 0.9100
Epoch 27/32
226/225 [==============================] - 55s 245ms/step - loss: 0.0982 - acc: 0.9605 - val_loss: 0.5012 - val_acc: 0.9200
Epoch 28/32
226/225 [==============================] - 55s 242ms/step - loss: 0.1063 - acc: 0.9577 - val_loss: 0.5262 - val_acc: 0.9056
Epoch 29/32
226/225 [==============================] - 55s 241ms/step - loss: 0.0933 - acc: 0.9637 - val_loss: 0.4498 - val_acc: 0.9294
Epoch 30/32
226/225 [==============================] - 54s 241ms/step - loss: 0.0916 - acc: 0.9658 - val_loss: 0.5562 - val_acc: 0.9044
Epoch 31/32
226/225 [==============================] - 55s 244ms/step - loss: 0.0870 - acc: 0.9666 - val_loss: 0.4454 - val_acc: 0.9169
Epoch 32/32
226/225 [==============================] - 55s 245ms/step - loss: 0.0857 - acc: 0.9660 - val_loss: 0.5616 - val_acc: 0.9119

모델 평가 및 시각화

1
model.evaluate_generator(testGen)
[0.2855920347539756, 0.95551161632288]

테스트셋에 위의 분류기보다 조금 더 높은 정확도를 확인할 수 있습니다.
정학도가 높아질수록 정확도 1% 올리는게 더더욱 힘들어지죠

1
show_graph(history.history)

png

1
2
3
4
smooth_data = {}
for key, val in history.history.items():
smooth_data[key] = smooth_curve(val[:])
show_graph(smooth_data)

png

5~10에폭에서부터 과대적합이 시작되는것처럼 보이는데요.
이 예제에서는 분류기를 이전에 학습한걸 그대로 사용하였으나, 여러가지 방법으로 테스트를 진행해보세요.

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2019 코딩크루 All Rights Reserved.

UV : | PV :