Keras & VGG16을 이용한 blood cell classification 본문

ML & DL/이것저것..

Keras & VGG16을 이용한 blood cell classification

eremo2002 2019. 1. 3. 13:24

이 글을 쓰는 이유는 Keras를 통해 CNN을 직접 구현해보고 이미지 classification에서 자주 사용되는 데이터셋이 아닌 다른 데이터셋을 사용하여 classification을 해보는 것이 주 목적이다.


딥러닝 라이브러리를 통해 데이터셋을 바로 불러오는 게 아니라 직접 로컬 데이터를 불러와보고 전처리도 해보고 네트워크를 설계하고 하이퍼 파라미터를 조절해보며 겪게 될 시행착오를 남기고자 한다.


상대적으로 구조가 간단한 vgg16으로 시작하여 네트워크를 만들어보고 직접 트레이닝할 것이다. 

이후 Imagenet dataset으로 pre-train된 네트워크와 비교해보고 다른 네트워크 역시 만들어볼 것이다.





Dataset

데이터셋은 캐글에 올라온 Blood cell dataset을 사용하였다. (문제시 삭제함)

https://www.kaggle.com/paultimothymooney/blood-cells


나는 classification만 할 것이기 때문에 annotation data는 당장은 사용하지 않는다.


Class는 EOSINOPHIL, LYMPHOCYTE, MONOCYTE, NEUTROPHIL 4개가 존재한다.





Train data는 각 class당 약 2500개 

Validation data는 각 class당 200개(Test data에서 일부를 나눔)

Test data 각 class당 약 420개













Model

모델은 VGG16을 사용한다.

케라스에선 이미 사전 훈련된 여러 네트워크들을 제공한다.

아래는 VGG16을 가져와서 summary한 결과이며 이를 통해 네트워크 구조를 확인할 수 있다.


1
2
3
4
5
6
7
from keras import models
from keras.applications import VGG16
 
 
 
pre_model = VGG16(weights='imagenet', include_top=True)
pre_model.summary()
cs











나는 4개의 클래스만 분류하면 되기 때문에 classifier의 unit 수를 4로 수정하였다.

또한 이미지넷은 1000개 클래스를 예측하며 F.C layer의 unit을 4096으로 하여 마지막에 1000개로 이어줬다. 

이에 반해 나는 사전학습된 모델을 사용하지 않고 4개의 클래스를 예측하기 때문에 4096에서 4로 연결하는 것은 feature정보의 특성을 많이 잃어버릴 수 있다고 판단하여 2048, 1024개의 unit을 가지는 F.C layer를 더 연결하였다.

네트워크 구조는 다음과 같다. input의 width, height를 220, 200으로 resize하여 넣었다.









categorical_crossentropy

RMSprop

he_normal initializer

L2 regularization

batch_size 16








에폭을 300까지 돌렸을 때

Loss가 줄어들다가 얼마 안 가 10 언저리에서 더 떨어지지 않았다. 

accuracy 역시 30%대를 겨우 넘는 결과를 보여주었다.


트레이닝을 계속 하면서도 training acc와 validation acc모두 25~30에서만 왔다갔다하고 더 올라가지 않았다.

처음에는 training acc와 loss는 아주 조금씩 변했는데 validationacc와 loss는 고정되어서 아예 똑같은 숫자만 찍혔다.










우선 일차적으로 학습을 진행하는 동안 Local minimum에 빠져 더 이상 성능이 올라기지 않는다고 생각하였다.

그래서 learning rate를 높여서 트레이닝 하였다. 그럼에도 불구하고 계속 똑같은 현상이 발생하였다. 

아직도 빠져나오지 못 했다고 판단하여 learning rate를 더 올려가며 시도해봤지만 결과는 달라지지 않았다.


일단 overfitting이라도 되면 그 이후에 overfitting을 방지하는 테크닉을 적용해보겠지만 이건 처음부터 정확도가 특정 구간에 갇혀 아무리 학습이 진행되어도 올라가지 않았다. 


사실 처음부터 initializer나 regularizer를 적용하진 않았다. 여러 테크닉을 적용해가며 성능이 얼마나 달라지는지 비교하고 싶었는데 이건 학습이 진행되질 않으니 미칠 노릇이었다. 그래서 웨이트 초기화도 해보고 regularizer도 적용해보고 optimizer도 바꿔보고 step이나 epoch도 바꿔봤는데 같은 문제가 여전히 발생했다. 나는 원인을 찾지 못 해 하이퍼 파라미터만 조금씩 바꿔서 트레이닝 돌려놓고 다른 공부를 하였다.


그러다가 learning rate를 굉장히 낮게 주고 돌렸다. 그리고 다른 일을 하다가 잠깐 확인을 해보니 학습이 제대로 진행되고 있었다.

문제의 원인은 다른 게 아니라 learning rate이 너무 높은 게 원인이었다. 


나는 learning rate이 생각보다 낮아서 local minima에 빠졌구나라고 생각하고 learning rate을 올렸던 것인데 실제론 내가 적용했던 learning rate들이 다 너무 높았던 것이다. 그래서 learning rate을 확 낮췄을 때 제대로 학습이 진행되었다.



아직 epoch도 굉장히 적고 dropout이나 skip connection같은 좋은 방법들을 적용한 건 아니지만 

우선 간단한 구조로 90% 가량의 정확도를 보여주고 학습이 잘 진행된다는 것에 만족하였다.













책이나 논문, 강의 등을 통해 들었던 이론들을 실제로 구현해보고 적용해보는 것이 생각보다 쉽지 않았다. 학습이 잘 진행될 여지가 충분히 남아있다는 점을 고려하여 다른 네트워크들 역시 구현해보며 성능을 개선해나갈 것이다. 또한 케라스를 통해 모델을 구현해보면서 케라스가 정말정말 직관적이고 잘 짜여진 좋은 딥러닝 프레임워크라는 것을 너무 많이 느끼게 되었다.












구현한 코드는 다음과 같다.


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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
from keras import layers, models
from keras.applications import VGG16
from keras import Input
from keras.models import Model
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers, initializers, regularizers, metrics
from keras.callbacks import ModelCheckpoint
import os
from glob import glob
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
 
 
 
 
# data_list = glob('blood-cells/dataset2-master/images/TRAIN/**/*.jpeg')
# print(data_list[-1])
 
train_datagen = ImageDataGenerator(rescale=1./255)
val_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
 
train_dir = os.path.join('./dataset/1/images/train')
val_dir = os.path.join('./dataset/1/images/val')
test_dir = os.path.join('./dataset/1/images/test')
 
# train_dir = os.path.join('./dataset/cats_and_dogs_small/train')
# val_dir = os.path.join('./dataset/cats_and_dogs_small/val')
# test_dir = os.path.join('./dataset/cats_and_dogs_small/test')
 
train_generator = train_datagen.flow_from_directory(train_dir, batch_size=16, target_size=(220200), color_mode='rgb')
val_generator = val_datagen.flow_from_directory(val_dir, batch_size=16, target_size=(220200), color_mode='rgb')
 
 
 
input_tensor = Input(shape=(2202003), dtype='float32', name='input')
 
= layers.Conv2D(64, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(input_tensor)
= layers.Conv2D(64, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.MaxPooling2D((2,2))(x)
 
= layers.Conv2D(128, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.Conv2D(128, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.MaxPooling2D((2,2))(x)
 
= layers.Conv2D(256, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.Conv2D(256, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.Conv2D(256, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.MaxPooling2D((2,2))(x)
 
= layers.Conv2D(512, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.Conv2D(512, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.Conv2D(512, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.MaxPooling2D((2,2))(x)
 
= layers.Conv2D(512, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.Conv2D(512, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.Conv2D(512, (33), activation='relu', padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(0.01))(x)
= layers.MaxPooling2D((2,2))(x)
 
= layers.Flatten()(x)
= layers.Dense(4096, kernel_initializer='he_normal')(x)
= layers.Dense(2048, kernel_initializer='he_normal')(x)
= layers.Dense(1024, kernel_initializer='he_normal')(x)
output_tensor = layers.Dense(4, activation='softmax')(x)
 
myvgg = Model(input_tensor, output_tensor)
myvgg.summary()
 
 
# checkpoint = ModelCheckpoint(filepath='My_VGG_{epoch:03d}_{val_loss:.7f}.hdf5',monitor='loss', mode='min', save_best_only=True)
checkpoint = ModelCheckpoint(filepath='My_VGG_weight.hdf5'
            monitor='loss'
            mode='min'
            save_best_only=True)
 
myvgg.compile(loss='categorical_crossentropy', optimizer=optimizers.RMSprop(lr=2e-5), metrics=['acc'])
 
 
history = myvgg.fit_generator(train_generator, 
            steps_per_epoch=25
            epochs=300
            validation_data=val_generator, 
            validation_steps=16
            callbacks=[checkpoint])
 
 
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
 
epochs = range(1len(acc) + 1)
 
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Accuracy')
plt.legend()
plt.figure()
 
plt.plot(epochs, loss, 'ro', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title('Loss')
plt.legend()
 
plt.show()
cs





Comments