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

개와 고양이 분류하기

이번에는 CNN(Convolutional Neural Network)를 이용하여 개와 고양이 사진을 분류하는 모델을 만들어 봅시다.

사람은 개와 고양이를 분류하기가 굉장히 쉽죠. 딱 보면 아니까요.
하지만 컴퓨터 공학에서는 이걸 분류하는 문제가 해결하기 굉장히 어려운 문제였습니다.
컴퓨터는 일일히 룰을 지정해가며 개와 고양이를 분류하도록 만들어야 하니까요.

그래서 최근에는 딥러닝이 이러한 문제들을 해결하면서 최근 비전 관련 문제들을 딥러닝으로 풀어내려는 시도가 많습니다.
오늘 소개할 CNN은 이미지 인식 등에서 기존의 전통적인 비전 프로세싱에 비해 높은 성능을 보여줍니다.

오늘 시도하는 튜토리얼은 CPU로 학습시키기엔 꽤 오랜 시간이 걸리므로 인내심을 가지고 학습을 진행하시거나,
AWS로 GPU 딥러닝 환경 구축하기 포스팅을 참고하여 환경을 세팅한 뒤 진행해보세요.

Dataset

이번 데이터셋은 kaggle에서 제공하는 데이터셋을 이용할 예정입니다.
MNIST 데이터셋은 흑백의 이미지였지만, 이번에 사용할 이미지는 컬러를 가지고 있습니다.

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...

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

데이터 증식

데이터 증식(Data argumentaion)을 위해 케라스에서 제공하는 이미지 제너레이터를 사용합니다.
이미지의 위치를 조금 옮긴다거나, 회전, 좌우반전등을 했을 때 컴퓨터가 받아들이는 이미지는 전혀 다른것이 됩니다.
이러한 변형을 줌으로써 학습 데이터를 늘리고, 이러한 변조에 강하게 모델을 학습시킬 수 있습니다.

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

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.

rescale은 이미지의 nomalization을 위해 사용합니다. 각 이미지별로 255로 나눈 값으로 데이터가 변형됩니다.
rotation_range는 이미지의 최대 회전각을 지정합니다. 최대 20도까지 회전합니다.
width,height shift_range는 이미지의 이동을 말합니다. 좌우, 위아래로 이미지의 이동하는 백분율을 지정합니다. (0.1은 10%)
brightness_range는 이미지 밝기에 대한 내용입니다.
horizontal_flip은 이미지의 수평 반전을 시켜줍니다. 이 옵션의 경우 데이터셋의 이해가 필요합니다. 예를 들면 MNIST 데이터셋의 경우 손글씨 데이터이기 때문에 수평 반전이 일어나면 안됩니다.
validation_split은 검증세트의 비율을 지정해줍니다.

설정한 이미지 제너레이터를 통해 특정 디렉터리의 데이터들을 손쉽게 불러올 수 있습니다.
현재 데이터셋 구성에 맞춰 폴더이름은 레이블명, 폴더안의 데이터는 해당 레이블의 데이터셋이 됩니다.
target_size는 이미지를 해당 형태로 변형시켜주는데, 저희는 64x64 사이즈로 데이터로 읽어오겠습니다.

모델 구성

간단한 CNN 모델을 구성하여 학습을 진행해봅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tensorflow.keras.models import Sequential
from tensorflow.keras import layers

model = Sequential()

model.add(layers.InputLayer(input_shape=(64, 64, 3)))
model.add(layers.Conv2D(16, (3, 3), (1, 1), 'same', activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(rate=0.3))

model.add(layers.Conv2D(32, (3, 3), (1, 1), 'same', activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(rate=0.3))

model.add(layers.Conv2D(64, (3, 3), (1, 1), 'same', activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(rate=0.3))

model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(2, activation='sigmoid'))

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_3 (Conv2D)            (None, 64, 64, 16)        448       
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 32, 32, 16)        0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 32, 32, 16)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 32, 32, 32)        4640      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 16, 16, 32)        0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 16, 16, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 16, 16, 64)        18496     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 8, 8, 64)          0         
_________________________________________________________________
dropout_5 (Dropout)          (None, 8, 8, 64)          0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 4096)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 512)               2097664   
_________________________________________________________________
dense_4 (Dense)              (None, 256)               131328    
_________________________________________________________________
dense_5 (Dense)              (None, 2)                 514       
=================================================================
Total params: 2,253,090
Trainable params: 2,253,090
Non-trainable params: 0
_________________________________________________________________
1
trainGen.samples
7205
1
2
3
4
5
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['acc'],
)
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 [==============================] - 37s 164ms/step - loss: 0.6921 - acc: 0.5143 - val_loss: 0.6901 - val_acc: 0.5551
Epoch 2/32
226/225 [==============================] - 36s 159ms/step - loss: 0.6824 - acc: 0.5672 - val_loss: 0.6680 - val_acc: 0.5807
Epoch 3/32
226/225 [==============================] - 36s 158ms/step - loss: 0.6727 - acc: 0.5781 - val_loss: 0.6682 - val_acc: 0.5857
Epoch 4/32
226/225 [==============================] - 36s 159ms/step - loss: 0.6614 - acc: 0.6063 - val_loss: 0.6381 - val_acc: 0.6231
Epoch 5/32
226/225 [==============================] - 36s 159ms/step - loss: 0.6451 - acc: 0.6323 - val_loss: 0.6168 - val_acc: 0.6573
Epoch 6/32
226/225 [==============================] - 36s 159ms/step - loss: 0.6329 - acc: 0.6482 - val_loss: 0.6262 - val_acc: 0.6653
Epoch 7/32
226/225 [==============================] - 36s 158ms/step - loss: 0.6093 - acc: 0.6664 - val_loss: 0.5814 - val_acc: 0.6937
Epoch 8/32
226/225 [==============================] - 37s 165ms/step - loss: 0.5997 - acc: 0.6765 - val_loss: 0.6226 - val_acc: 0.6498
Epoch 9/32
226/225 [==============================] - 36s 158ms/step - loss: 0.6055 - acc: 0.6683 - val_loss: 0.5609 - val_acc: 0.7178
Epoch 10/32
226/225 [==============================] - 36s 159ms/step - loss: 0.5880 - acc: 0.6837 - val_loss: 0.5585 - val_acc: 0.7047
Epoch 11/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5769 - acc: 0.6972 - val_loss: 0.5701 - val_acc: 0.7006
Epoch 12/32
226/225 [==============================] - 36s 159ms/step - loss: 0.5727 - acc: 0.7003 - val_loss: 0.5507 - val_acc: 0.7168
Epoch 13/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5673 - acc: 0.7059 - val_loss: 0.5697 - val_acc: 0.7028
Epoch 14/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5588 - acc: 0.7132 - val_loss: 0.5845 - val_acc: 0.6786
Epoch 15/32
226/225 [==============================] - 36s 160ms/step - loss: 0.5522 - acc: 0.7187 - val_loss: 0.5251 - val_acc: 0.7267
Epoch 16/32
226/225 [==============================] - 37s 163ms/step - loss: 0.5570 - acc: 0.7141 - val_loss: 0.5353 - val_acc: 0.7317
Epoch 17/32
226/225 [==============================] - 37s 162ms/step - loss: 0.5441 - acc: 0.7182 - val_loss: 0.5237 - val_acc: 0.7369
Epoch 18/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5399 - acc: 0.7227 - val_loss: 0.5142 - val_acc: 0.7402
Epoch 19/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5343 - acc: 0.7278 - val_loss: 0.5283 - val_acc: 0.7217
Epoch 20/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5352 - acc: 0.7262 - val_loss: 0.5031 - val_acc: 0.7476
Epoch 21/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5278 - acc: 0.7312 - val_loss: 0.5222 - val_acc: 0.7347
Epoch 22/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5251 - acc: 0.7374 - val_loss: 0.4975 - val_acc: 0.7667
Epoch 23/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5186 - acc: 0.7448 - val_loss: 0.4979 - val_acc: 0.7543
Epoch 24/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5091 - acc: 0.7504 - val_loss: 0.5470 - val_acc: 0.7277
Epoch 25/32
226/225 [==============================] - 38s 168ms/step - loss: 0.5108 - acc: 0.7416 - val_loss: 0.5053 - val_acc: 0.7491
Epoch 26/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5028 - acc: 0.7507 - val_loss: 0.5248 - val_acc: 0.7302
Epoch 27/32
226/225 [==============================] - 36s 159ms/step - loss: 0.5008 - acc: 0.7523 - val_loss: 0.4948 - val_acc: 0.7599
Epoch 28/32
226/225 [==============================] - 36s 158ms/step - loss: 0.5063 - acc: 0.7456 - val_loss: 0.5401 - val_acc: 0.7167
Epoch 29/32
226/225 [==============================] - 36s 157ms/step - loss: 0.4870 - acc: 0.7671 - val_loss: 0.5423 - val_acc: 0.7310
Epoch 30/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4976 - acc: 0.7625 - val_loss: 0.5083 - val_acc: 0.7450
Epoch 31/32
226/225 [==============================] - 36s 160ms/step - loss: 0.4917 - acc: 0.7613 - val_loss: 0.5162 - val_acc: 0.7344
Epoch 32/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4840 - acc: 0.7648 - val_loss: 0.5015 - val_acc: 0.7436

학습결과 시각화 및 평가

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

32 에폭의 학습을 마친 그래프입니다.
GPU를 GTX 750TI 모델을 사용중인데 에폭당 36초씩 약 20분 가량 학습 시간이 소요되었습니다.
그래프를 보면 과소적합 된것처럼 보입니다.

모델 중간 평가

현재 모델을 기준으로 테스트셋의 정확도와 손실율을 구해보겠습니다.
테스트셋의 경우 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.5174207690176962, 0.7412259022139466]

74.1% 의 정확도를 보여줍니다.
과소적합이 의심되니 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 [==============================] - 37s 162ms/step - loss: 0.4809 - acc: 0.7647 - val_loss: 0.4936 - val_acc: 0.7552
Epoch 2/32
226/225 [==============================] - 38s 167ms/step - loss: 0.4797 - acc: 0.7714 - val_loss: 0.5129 - val_acc: 0.7468
Epoch 3/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4706 - acc: 0.7766 - val_loss: 0.4703 - val_acc: 0.7754
Epoch 4/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4771 - acc: 0.7682 - val_loss: 0.4822 - val_acc: 0.7671
Epoch 5/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4721 - acc: 0.7637 - val_loss: 0.4953 - val_acc: 0.7539
Epoch 6/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4714 - acc: 0.7736 - val_loss: 0.4742 - val_acc: 0.7727
Epoch 7/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4698 - acc: 0.7747 - val_loss: 0.5087 - val_acc: 0.7595
Epoch 8/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4577 - acc: 0.7805 - val_loss: 0.4736 - val_acc: 0.7674
Epoch 9/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4615 - acc: 0.7771 - val_loss: 0.5172 - val_acc: 0.7386
Epoch 10/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4628 - acc: 0.7766 - val_loss: 0.5027 - val_acc: 0.7499
Epoch 11/32
226/225 [==============================] - 37s 165ms/step - loss: 0.4598 - acc: 0.7785 - val_loss: 0.4804 - val_acc: 0.7623
Epoch 12/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4515 - acc: 0.7859 - val_loss: 0.4893 - val_acc: 0.7549
Epoch 13/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4569 - acc: 0.7862 - val_loss: 0.4973 - val_acc: 0.7631
Epoch 14/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4350 - acc: 0.7974 - val_loss: 0.4770 - val_acc: 0.7730
Epoch 15/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4376 - acc: 0.8010 - val_loss: 0.5099 - val_acc: 0.7562
Epoch 16/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4416 - acc: 0.7891 - val_loss: 0.4770 - val_acc: 0.7665
Epoch 17/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4342 - acc: 0.7915 - val_loss: 0.4721 - val_acc: 0.7779
Epoch 18/32
226/225 [==============================] - 36s 159ms/step - loss: 0.4388 - acc: 0.7926 - val_loss: 0.4733 - val_acc: 0.7666
Epoch 19/32
226/225 [==============================] - 37s 163ms/step - loss: 0.4189 - acc: 0.7991 - val_loss: 0.4757 - val_acc: 0.7711
Epoch 20/32
226/225 [==============================] - 36s 161ms/step - loss: 0.4194 - acc: 0.7991 - val_loss: 0.4706 - val_acc: 0.7801
Epoch 21/32
226/225 [==============================] - 36s 159ms/step - loss: 0.4334 - acc: 0.7942 - val_loss: 0.4563 - val_acc: 0.7807
Epoch 22/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4205 - acc: 0.8017 - val_loss: 0.4695 - val_acc: 0.7667
Epoch 23/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4246 - acc: 0.8006 - val_loss: 0.4741 - val_acc: 0.7678
Epoch 24/32
226/225 [==============================] - 36s 159ms/step - loss: 0.4097 - acc: 0.8103 - val_loss: 0.5746 - val_acc: 0.7368
Epoch 25/32
226/225 [==============================] - 36s 158ms/step - loss: 0.4137 - acc: 0.8051 - val_loss: 0.4951 - val_acc: 0.7663
Epoch 26/32
226/225 [==============================] - 39s 173ms/step - loss: 0.4066 - acc: 0.8077 - val_loss: 0.4999 - val_acc: 0.7667
Epoch 27/32
226/225 [==============================] - 36s 159ms/step - loss: 0.4121 - acc: 0.8110 - val_loss: 0.4716 - val_acc: 0.7789
Epoch 28/32
226/225 [==============================] - 38s 167ms/step - loss: 0.3985 - acc: 0.8153 - val_loss: 0.5083 - val_acc: 0.7613
Epoch 29/32
226/225 [==============================] - 36s 158ms/step - loss: 0.3980 - acc: 0.8153 - val_loss: 0.4996 - val_acc: 0.7672
Epoch 30/32
226/225 [==============================] - 36s 159ms/step - loss: 0.4105 - acc: 0.8083 - val_loss: 0.4759 - val_acc: 0.7745
Epoch 31/32
226/225 [==============================] - 36s 159ms/step - loss: 0.4027 - acc: 0.8112 - val_loss: 0.4851 - val_acc: 0.7696
Epoch 32/32
226/225 [==============================] - 36s 158ms/step - loss: 0.3988 - acc: 0.8205 - val_loss: 0.4962 - val_acc: 0.7692
1
show_graph(history.history)

png

그래프를 보니 과대적합이 되어가고 있는것처럼 보입니다.
모델이 문제에 비해 간단한 모델인 것 같네요.

1
model.evaluate_generator(testGen)
[0.5502711092547738, 0.736529906139006]

테스트셋의 정확도를 봤을 때 73%로 아까보다 오히려 떨어진 결과를 보여줍니다.
예상대로 과대적합이 되어가고 있던 모양입니다.

모델 예측

모델 학습시켰으니 개와 고양이를 예측시키는걸 한번 해보도록 하겠습니다.

1
2
from tensorflow.keras.preprocessing.image import array_to_img
import numpy as np
1
2
3
4
5
6
7
8
9
cls_index = ['고양이', '개']

imgs = testGen.next()
arr = imgs[0][0]
img = array_to_img(arr).resize((128, 128))
plt.imshow(img)
result = model.predict_classes(arr.reshape(1, 64, 64, 3))
print('예측: {}'.format(cls_index[result[0]]))
print('정답: {}'.format(cls_index[np.argmax(imgs[1][0])]))
예측: 고양이
정답: 고양이

png

사이즈를 줄인 이미지를 강제로 키웠더니 이미지 품질이 많이 떨어졌네요.
모델이 예측한 답과 원본 답이 일치하는걸 볼 수 있습니다.
물론 정확도가 많이 낮아 여러번 하면 틀린답도 자주 나옵니다.

다음에는 이미지넷에서 큰 성과를 이뤘던 모델들을 가지고 정확도를 더 높일 수 있도록 학습을 진행해보겠습니다.

Powered by Hexo and Hexo-theme-hiker

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

UV : | PV :