딥러닝으로 개와 고양이를 분류하는 모델을 만들어보자 - ResNet50

사진으로 개 고양이 분류하기 - Ver. ResNet

안녕하세요.
저번 포스팅에서는 일반적인 CNN을 이용하여 네트워크를 구성하고, 개와 고양이 사진을 분류시키는 모델을 만들어 봤는데요.
이번 시간에는 대회에서 사람보다 적은 에러율을 보였던 ResNet을 이용하여 모델을 만들어 보겠습니다.

토이 프로젝트의 튜토리얼이지만 모델 자체가 굉장히 무겁다보니 CPU로는 극악의 시간이 소요됩니다.
꼭 GPU 환경을 구축하시고 진행하세요.

데스크톱으로 GPU 학습환경 구성이 어려우시다면, AWS로 GPU기반의 딥러닝 학습환경 구축하기 포스팅을 참고하세요.

ResNet

ResNet 모델을 사용하기 전에 간단하게만 알아봅시다.
참고한 논문은 여기에서 확인하실 수 있고, 가장 흥미로운 부분은 Residual Network 입니다.

기존의 CNN 학습 방법으로 VGG의 구현을 살짝 보겠습니다.
VGG-19
위 이미지에서 보면 좌측의 네트워크는 저희가 지금까지 만든 각 레이어를 쭉 일자로 연결한 형태이고,
우측의 Residual을 보면 이전 레이어에서 나온 데이터를 한단계를 건너뛰어 재사용하는걸 볼 수 있습니다.
좌측의 평평한 레이어에서 하나의 지름길을 만들어 한단계를 넘어 다음 레이어에 연결시킨 부분이 인상적입니다.
이러한 부분으로 인해 VGG모델에 비해 깊은 레이어를 가지고 있지만, 실제 사용시에는 굉장히 빠른 속도를 보여줍니다.
다만 학습시간이 오래걸리는건 당연하겠죠 ?

어떤 강의에서는 이 Residual network를 Fast network라고 표현한답니다.

좀 더 자세한 설명은 해당 논문을 참고하시기 바랍니다.

Dataset

데이터셋은 이전 포스팅과 마찬가지로 동일한 데이터셋인 kaggle에서 제공하는 데이터셋을 이용할 예정입니다.

Download

직접 데이터셋을 배포하진 않고 있습니다.
데이터셋은 여기에서 직접 다운로드 받으실 수 있고, kaggle api를 통하여 받으실 수 있습니다.

저희는 저번 포스팅과 동일하게 datasets이란 폴더에 데이터셋을 넣어두고 사용하도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
datasets
└── cat-and-dog
├── test_set
│   ├── cats
│   │   └── datas...
│   └── dogs
│   └── datas...
└── training_set
├── cats
│   └── datas...
└── dogs
└── datas...

이와 같은 형태의 구성이 되어 있다고 가정합니다.

데이터 로드

마찬가지로 저번 포스팅과 동일하게 데이터 증식을 위한 제너레이터로 데이터를 읽어옵니다.
관련된 설명은 이전 포스팅을 참고해주세요.

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

# 위노그라드 알고리즘 설정
os.environ['TF_ENABLE_WINOGRAD_NONFUSED'] = '1'

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

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=.1
)

trainGen = imageGenerator.flow_from_directory(
os.path.join(rootPath, 'training_set'),
target_size=(64, 64),
subset='training'
)

validationGen = imageGenerator.flow_from_directory(
os.path.join(rootPath, 'training_set'),
target_size=(64, 64),
subset='validation'
)
Found 7205 images belonging to 2 classes.
Found 800 images belonging to 2 classes.

모델 구성

텐서플로 케라스에서 ResNet 모델을 제공해주고 있습니다.
저희는 해당 모델의 구성을 그대로 가져다 사용하도록 하겠습니다.

1
2
3
4
5
6
7
8
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers

model = Sequential()
model.add(ResNet50(include_top=True, weights=None, input_shape=(64, 64, 3), classes=2))

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
resnet50 (Model)             (None, 2)                 23591810  
=================================================================
Total params: 23,591,810
Trainable params: 23,538,690
Non-trainable params: 53,120
_________________________________________________________________

include_top
모델의 최상위 Fully Connected 레이어를 추가할것인지 추가하지 않을것인지에 대한 토글입니다.

weights
기존 학습된 가중치 데이터를 읽어들일 가중치 경로입니다. imagenet이라 입력하면 imagenet 데이터셋을 기반으로 기존 학습된 가중치 데이터를 불러옵니다.

input_shape
입력값의 형태를 지정합니다. 3D 텐서를 입력해야합니다.

classes
모델의 마지막 출력 차원을 지정합니다.

저희는 ResNet50 모델을 이용하여 개와 고양이를 학습하는 모델을 학습시켜보도록 하겠습니다.

모델 컴파일

모델의 구성이 완료된 이후에는 항상 컴파일이 필요합니다.

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

학습 시작

ResNet 모델의 학습이 시작됩니다.

1
2
3
4
5
6
7
8
epochs = 32
history = model.fit_generator(
trainGen,
epochs=epochs,
steps_per_epoch=trainGen.samples / epochs,
validation_data=validationGen,
validation_steps=trainGen.samples / epochs,
)
Epoch 1/32
226/225 [==============================] - 100s 441ms/step - loss: 1.0603 - acc: 0.5530 - val_loss: 0.9258 - val_acc: 0.5022
Epoch 2/32
226/225 [==============================] - 80s 355ms/step - loss: 0.9658 - acc: 0.6102 - val_loss: 0.8094 - val_acc: 0.5333
Epoch 3/32
226/225 [==============================] - 80s 354ms/step - loss: 0.9366 - acc: 0.6113 - val_loss: 4.5736 - val_acc: 0.5488
Epoch 4/32
226/225 [==============================] - 80s 354ms/step - loss: 0.9635 - acc: 0.5666 - val_loss: 4.1998 - val_acc: 0.5292
Epoch 5/32
226/225 [==============================] - 80s 354ms/step - loss: 0.8955 - acc: 0.6076 - val_loss: 2.3943 - val_acc: 0.5353
Epoch 6/32
226/225 [==============================] - 80s 353ms/step - loss: 0.8526 - acc: 0.6103 - val_loss: 1.5856 - val_acc: 0.6340
Epoch 7/32
226/225 [==============================] - 80s 354ms/step - loss: 0.8663 - acc: 0.6193 - val_loss: 0.6723 - val_acc: 0.6033
Epoch 8/32
226/225 [==============================] - 80s 353ms/step - loss: 0.8656 - acc: 0.5956 - val_loss: 0.9622 - val_acc: 0.6312
Epoch 9/32
226/225 [==============================] - 80s 354ms/step - loss: 0.7594 - acc: 0.6465 - val_loss: 0.6115 - val_acc: 0.6655
Epoch 10/32
226/225 [==============================] - 80s 354ms/step - loss: 0.8273 - acc: 0.6457 - val_loss: 5.6990 - val_acc: 0.5010
Epoch 11/32
226/225 [==============================] - 80s 355ms/step - loss: 0.8979 - acc: 0.5210 - val_loss: 0.7881 - val_acc: 0.5429
Epoch 12/32
226/225 [==============================] - 80s 355ms/step - loss: 0.7378 - acc: 0.5575 - val_loss: 0.7642 - val_acc: 0.5741
Epoch 13/32
226/225 [==============================] - 80s 354ms/step - loss: 0.6924 - acc: 0.5781 - val_loss: 0.7743 - val_acc: 0.5873
Epoch 14/32
226/225 [==============================] - 80s 355ms/step - loss: 0.6797 - acc: 0.5925 - val_loss: 0.6813 - val_acc: 0.5864
Epoch 15/32
226/225 [==============================] - 80s 353ms/step - loss: 0.6758 - acc: 0.6071 - val_loss: 0.6564 - val_acc: 0.6355
Epoch 16/32
226/225 [==============================] - 80s 352ms/step - loss: 0.6618 - acc: 0.6306 - val_loss: 0.6620 - val_acc: 0.6398
Epoch 17/32
226/225 [==============================] - 80s 354ms/step - loss: 0.6850 - acc: 0.5840 - val_loss: 0.6861 - val_acc: 0.5525
Epoch 18/32
226/225 [==============================] - 80s 355ms/step - loss: 0.6746 - acc: 0.5918 - val_loss: 0.6706 - val_acc: 0.5904
Epoch 19/32
226/225 [==============================] - 81s 357ms/step - loss: 0.6513 - acc: 0.6269 - val_loss: 0.6519 - val_acc: 0.6323
Epoch 20/32
226/225 [==============================] - 80s 353ms/step - loss: 0.6425 - acc: 0.6363 - val_loss: 0.6568 - val_acc: 0.6289
Epoch 21/32
226/225 [==============================] - 80s 353ms/step - loss: 0.6306 - acc: 0.6528 - val_loss: 0.6282 - val_acc: 0.6532
Epoch 22/32
226/225 [==============================] - 80s 356ms/step - loss: 0.6204 - acc: 0.6574 - val_loss: 0.6147 - val_acc: 0.6574
Epoch 23/32
226/225 [==============================] - 80s 352ms/step - loss: 0.5968 - acc: 0.6823 - val_loss: 0.6732 - val_acc: 0.6366
Epoch 24/32
226/225 [==============================] - 80s 353ms/step - loss: 0.5842 - acc: 0.6910 - val_loss: 0.5618 - val_acc: 0.7125
Epoch 25/32
226/225 [==============================] - 80s 352ms/step - loss: 0.5762 - acc: 0.6996 - val_loss: 0.9354 - val_acc: 0.5436
Epoch 26/32
226/225 [==============================] - 80s 353ms/step - loss: 0.5724 - acc: 0.7059 - val_loss: 4.0694 - val_acc: 0.4996
Epoch 27/32
226/225 [==============================] - 80s 354ms/step - loss: 0.5702 - acc: 0.7041 - val_loss: 0.5508 - val_acc: 0.7060
Epoch 28/32
226/225 [==============================] - 80s 353ms/step - loss: 0.5459 - acc: 0.7235 - val_loss: 0.5939 - val_acc: 0.6792
Epoch 29/32
226/225 [==============================] - 80s 352ms/step - loss: 0.5353 - acc: 0.7347 - val_loss: 0.6381 - val_acc: 0.6692
Epoch 30/32
226/225 [==============================] - 80s 354ms/step - loss: 0.5242 - acc: 0.7404 - val_loss: 0.7704 - val_acc: 0.6610
Epoch 31/32
226/225 [==============================] - 80s 355ms/step - loss: 0.5159 - acc: 0.7481 - val_loss: 1.4030 - val_acc: 0.5737
Epoch 32/32
226/225 [==============================] - 80s 352ms/step - loss: 0.4922 - acc: 0.7576 - val_loss: 0.5993 - val_acc: 0.7081

학습결과 시각화

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

그래프의 변동이 심하니 지수 이동 평균값을 구하여 그래프를 다시 그립니다.

1
2
3
4
5
6
7
8
9
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
2
3
4
smooth_data = {}
for key, val in history.history.items():
smooth_data[key] = smooth_curve(val)
show_graph(smooth_data)

png

현재 Tesla K80으로 약 40분을 훈련시켰는데, 그래프로만 봐서는 과소적합일 가능성이 보입니다.
조금 더 학습을 시켜보면 알수 있겠네요.

모델의 평가를 해본 이후에 32에폭을 더 돌려보도록 하겠습니다.

모델 중간 평가

현재 모델을 기준으로 테스트셋의 정확도와 손실율을 구해보겠습니다.
테스트셋의 경우 rescale만 적용하여 원본 이미지 그대로 사용합니다.

1
2
3
4
5
6
7
8
9
10
testGenerator = ImageDataGenerator(
rescale=1./255
)

testGen = imageGenerator.flow_from_directory(
os.path.join(rootPath, 'test_set'),
target_size=(64, 64),
)

model.evaluate_generator(testGen)
Found 2023 images belonging to 2 classes.





[0.6065594666792624, 0.7162629758080102]

모델 추가 학습

이전의 그래프로는 과소적합일 가능성이 있기 때문에 32에폭만 추가적으로 더 학습을 진행해보겠습니다.

1
2
3
4
5
6
7
8
epochs = 32
history = model.fit_generator(
trainGen,
epochs=epochs,
steps_per_epoch=trainGen.samples / epochs,
validation_data=validationGen,
validation_steps=trainGen.samples / epochs,
)
Epoch 1/32
226/225 [==============================] - 80s 355ms/step - loss: 0.4832 - acc: 0.7692 - val_loss: 0.5355 - val_acc: 0.7609
Epoch 2/32
226/225 [==============================] - 80s 353ms/step - loss: 0.4713 - acc: 0.7720 - val_loss: 0.4927 - val_acc: 0.7584
Epoch 3/32
226/225 [==============================] - 80s 356ms/step - loss: 0.4615 - acc: 0.7808 - val_loss: 0.5980 - val_acc: 0.7175
Epoch 4/32
226/225 [==============================] - 80s 352ms/step - loss: 0.4611 - acc: 0.7821 - val_loss: 1.0948 - val_acc: 0.5885
Epoch 5/32
226/225 [==============================] - 80s 352ms/step - loss: 0.4445 - acc: 0.7929 - val_loss: 0.5592 - val_acc: 0.7476
Epoch 6/32
226/225 [==============================] - 80s 353ms/step - loss: 0.4443 - acc: 0.7986 - val_loss: 0.5090 - val_acc: 0.7360
Epoch 7/32
226/225 [==============================] - 80s 355ms/step - loss: 0.4169 - acc: 0.8060 - val_loss: 0.5854 - val_acc: 0.7501
Epoch 8/32
226/225 [==============================] - 80s 352ms/step - loss: 0.4104 - acc: 0.8143 - val_loss: 0.9060 - val_acc: 0.7382
Epoch 9/32
226/225 [==============================] - 80s 352ms/step - loss: 0.4023 - acc: 0.8198 - val_loss: 0.4205 - val_acc: 0.8031
Epoch 10/32
226/225 [==============================] - 80s 352ms/step - loss: 0.3985 - acc: 0.8197 - val_loss: 1.0545 - val_acc: 0.6121
Epoch 11/32
226/225 [==============================] - 81s 356ms/step - loss: 0.3883 - acc: 0.8228 - val_loss: 0.4979 - val_acc: 0.7987
Epoch 12/32
226/225 [==============================] - 80s 352ms/step - loss: 0.3755 - acc: 0.8326 - val_loss: 0.6414 - val_acc: 0.6928
Epoch 13/32
226/225 [==============================] - 80s 352ms/step - loss: 0.3692 - acc: 0.8364 - val_loss: 0.5251 - val_acc: 0.7304
Epoch 14/32
226/225 [==============================] - 80s 352ms/step - loss: 0.3950 - acc: 0.8204 - val_loss: 3.7220 - val_acc: 0.5317
Epoch 15/32
226/225 [==============================] - 80s 354ms/step - loss: 0.3995 - acc: 0.8192 - val_loss: 0.4228 - val_acc: 0.7976
Epoch 16/32
226/225 [==============================] - 80s 353ms/step - loss: 0.3514 - acc: 0.8469 - val_loss: 0.5189 - val_acc: 0.7638
Epoch 17/32
226/225 [==============================] - 80s 352ms/step - loss: 0.3363 - acc: 0.8533 - val_loss: 0.3654 - val_acc: 0.8330
Epoch 18/32
226/225 [==============================] - 80s 355ms/step - loss: 0.3307 - acc: 0.8577 - val_loss: 0.4184 - val_acc: 0.8128
Epoch 19/32
226/225 [==============================] - 80s 354ms/step - loss: 0.3644 - acc: 0.8398 - val_loss: 0.5159 - val_acc: 0.7530
Epoch 20/32
226/225 [==============================] - 80s 353ms/step - loss: 0.3657 - acc: 0.8429 - val_loss: 0.5468 - val_acc: 0.7775
Epoch 21/32
226/225 [==============================] - 80s 355ms/step - loss: 0.3313 - acc: 0.8565 - val_loss: 1.4114 - val_acc: 0.5933
Epoch 22/32
226/225 [==============================] - 80s 353ms/step - loss: 0.3172 - acc: 0.8613 - val_loss: 1.1401 - val_acc: 0.6374
Epoch 23/32
226/225 [==============================] - 80s 353ms/step - loss: 0.3278 - acc: 0.8590 - val_loss: 0.5699 - val_acc: 0.7566
Epoch 24/32
226/225 [==============================] - 80s 354ms/step - loss: 0.3031 - acc: 0.8703 - val_loss: 0.3731 - val_acc: 0.8324
Epoch 25/32
226/225 [==============================] - 80s 353ms/step - loss: 0.2799 - acc: 0.8809 - val_loss: 0.4147 - val_acc: 0.8131
Epoch 26/32
226/225 [==============================] - 80s 355ms/step - loss: 0.2879 - acc: 0.8765 - val_loss: 0.3705 - val_acc: 0.8410
Epoch 27/32
226/225 [==============================] - 80s 354ms/step - loss: 0.2953 - acc: 0.8763 - val_loss: 0.4153 - val_acc: 0.8234
Epoch 28/32
226/225 [==============================] - 80s 352ms/step - loss: 0.2705 - acc: 0.8829 - val_loss: 0.3329 - val_acc: 0.8583
Epoch 29/32
226/225 [==============================] - 80s 355ms/step - loss: 0.2615 - acc: 0.8906 - val_loss: 0.3774 - val_acc: 0.8317
Epoch 30/32
226/225 [==============================] - 81s 356ms/step - loss: 0.2550 - acc: 0.8910 - val_loss: 0.4223 - val_acc: 0.8317
Epoch 31/32
226/225 [==============================] - 79s 351ms/step - loss: 0.2552 - acc: 0.8906 - val_loss: 0.4189 - val_acc: 0.8092
Epoch 32/32
226/225 [==============================] - 80s 352ms/step - loss: 0.2475 - acc: 0.8917 - val_loss: 0.3132 - val_acc: 0.8566

학습결과 시각화

마찬가지로 지수이동평균을 적용하여 그래프를 그려보겠습니다.

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_generator(testGen)
[0.33355356405939846, 0.8581314879482004]

여전히 과소적합인 것 같네요.
테스트셋에 대한 정확도가 85%까지 올라왔는데 아마 학습을 더 진행하게 된다면 더 높은 성능을 내줄거 같습니다.

아무래도 모델을 처음부터 모델을 학습시키다보니 시간이 오래 걸리네요.
대회에 나갈때에는 모델을 2~3주동안 학습시켰답니다.
여러분도 학습을 추가적으로 진행시켜보세요 !

Powered by Hexo and Hexo-theme-hiker

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

UV : | PV :