안녕하세요! M_AI 입니다!
이전 글에서는 데이터셋 준비법을 알아보았습니다.
이전글 : [당뇨망막병증(Diabetic Retinopathy)에서 Grad-CAM으로 병변 찾기] 1. Data Preprocessing
https://yhu0409.tistory.com/10
하지만 결과를 미리 말씀드리자면, 5개의 클래스로 학습하면 Validataion Set에 대해 대략 75 ~ 80% 성능이 나옵니다.
이는 생각보다 좋지 않은 성능이었습니다.
아무래도 병의 중증도는 단계별로 명확하게 나눌 수 있는 discrete한 것이 아니기에,
구분이 어렵다고 제 스스로 판단하여
다음 글에서는 5개의 분류가 아닌, 방향을 바꾸어 단순히 병의 유무만 예측하는 이진 분류(binary classification)으로 진행하고자 합니다.
이 글을 읽으면 좋을 것 같은 사람들!
- 기본적인 분류(classfication) 모델을 Tensorflow 2.x 버전으로 구현할 줄 아는 사람
- Clssification을 넘어서 Object detection을 하길 원하는데 오픈 소스를 봐도 이해가 가질 않는 사람.
- 오픈 소스를 이해하더라도 입맛대로 수정하는 방법을 모르는 사람.
본 게시글의 목적
※ 본 게시글은 딥러닝 모델의 상세한 이론적 설명보다는 모델 구조를 바탕으로 코드 분석하고 이해하는 데에 목적에 있습니다!
※ 상세한 이론을 원하면 다른 곳에서 찾아보시길 바랍니다!
본 게시글의 특징
① Framework : Tensorflow 2.x
② 학습 환경
- Google Colab
③ Batch size = 1
④ Kaggle에서 APTOS 2019 Blindness Detection 데이터셋과 Diabetic Retinopathy Detection 데이터셋을 사용하여 병의 중증도를 분류(Classification) 및 예측 목적.
( 데이터셋 출처 : https://www.kaggle.com/benjaminwarner/resized-2015-2019-blindness-detection-images)
https://www.kaggle.com/c/aptos2019-blindness-detection
⑤ 게시글에서는 반말
2. Model Training
2.1. Training Method
본 게시글에서 학습할 모델은 VGG16과 EfficientNet B0이다. 해당 모델은 모두 ImageNet으로 사전 학습된(pre-trained) 모델을 사용하여 전이학습(transfer learning)과 미세 조정(fine-tuning)을 할 것이다.
ImageNet은 1,000개 클래스를 가지고 있기에, 사전 학습된 모델들의 출력층 노드 개수는 1,000개이다. 본 게시글에서 해당 데이터셋의 클래스는 5이기에 이에 맞춰 수정하기 위해서 각 모델을 커스텀(custom)해야한다.
모델 설정
- VGG16
VGG16 모델은 컨볼루션 계층이 끝나면 1차원 배열로 변하는 계층과 4096개 노드가 있는데 2개의 계층 이후 출력 계층이 존재한다. 하지만 본 게시글에서는 4096개 노드로 설정한다면 (1024, 1024, 3) 크기의 입력 영상에 따라 모델이 차지하는 메모리가 매우 커져 out-of-memory 발생 또는 느린 학습으로 인해 이 값을 각각 512, 256으로 수정해주었다.
학습 속도 : 0.0001 / 0.00001
- EfficientNet B0
해당 모델은 출력 계층 전에 dropout이 존재하는데, 어느 정도의 dropout을 해줘야할지 몰라 임의로 0.3으로 설정하였다.
학습 속도 : 0.0001 / 0.00001
학습 시에는 두 모델에 대해서 optimizer은 Adam으로 동일하게 설정한다.
학습 방법
- 조기종료 (Earlystopping)
validation loss에 대해서 4번 동안 성능이 좋아지지 않으면 조기 종료하도록 하였다.
- 전이학습 및 미세 조정
전이학습은 백본(VGG16, EfficientNet)의 가중치(weights)를 고정(freeze)한 후, 나머지 layers만(custome한 layers) 학습하여 초기화한다. 이후에는 미세 조정을 하기 위해 첫번째 convolution block만 고정하고 나머지 가중치는 풀어준다.(unfreeze)
미세 조정 때 학습 속도는 전이 학습 때의 1/10로 설정하여 학습시키도록 한다.
아래 def MODEL 함수는 각 모델을 커스텀하는 함수이다.
freeze 인수는 백본을 freeze한 상태로 나머지 layer를 초기화하는 전이학습
inference_mode인수는 학습된 weights를 불러 inference 할 때 사용하기 위한 인수이다.
def MODEL(model_name,
freeze = True,
inference_mode = False,
model_dir = BASE_DIR,
width = WIDTH,
height = HEIGHT,
num_classes = NUM_CLASSES
):
if model_name == "VGG16":
base_model = keras.applications.VGG16(include_top=False,
input_shape=(width, height, 3),
weights = 'imagenet')
if freeze:
base_model.trainable = False
else:
base_model.trainable = True
model = keras.Sequential([
base_model,
keras.layers.Flatten(name = "Flatten"),
keras.layers.Dense(512, activation='relu', name = "Dense_1"),
keras.layers.Dense(256, activation='relu', name = "Dense_2"),
keras.layers.Dense(num_classes, activation='softmax', name = "output_layer")
])
if inference_mode:
model.load_weights(f"{model_dir}{model_name}_fine-tuning.h5")
model.summary()
return model
elif model_name == "EfficientNetB0":
base_model = keras.applications.EfficientNetB0(include_top=False,
input_shape=(width, height, 3),
weights = 'imagenet')
if freeze:
base_model.trainable = False
else:
base_model.trainable = True
model = keras.Sequential([
base_model,
keras.layers.GlobalAveragePooling2D(name = "avg_pool"),
keras.layers.Dropout(0.3, name = "top_dropout"),
keras.layers.Dense(num_classes, activation='softmax', name = "output_layer")
])
if inference_mode:
model.load_weights(f"{model_dir}{model_name}_fine-tuning.h5")
model.summary()
return model
else:
return False
def Transfer_Learning 함수는 모델을 ImageNet의 weights를 제외한 layers를 초기화 훈련시키기 위한 함수이며, 백본을 고정한다.
def Transfer_Learning(model_name,
model_dir = BASE_DIR,
epochs = EPOCHS,
batch_size = BATCH_SIZE,
width = WIDTH,
height = HEIGHT,
num_classes = NUM_CLASSES):
model = MODEL(model_name, freeze = True)
if model == False:
print("Only model_name : 'VGG16' or 'EfficientNetB0'")
else:
dataset = Dataset_Generator()
train_dataset = tf.data.Dataset.from_generator(
dataset.train_generator,
(tf.float32, tf.int32),
(tf.TensorShape([1, HEIGHT, WIDTH, 3]), tf.TensorShape([1, 1])),
)
valid_dataset = tf.data.Dataset.from_generator(
dataset.valid_generator,
(tf.float32, tf.int32),
(tf.TensorShape([1, HEIGHT, WIDTH, 3]), tf.TensorShape([1, 1])),
)
print("Mdoel Complie....")
loss_fn = FocalLoss()
callbacks_list = [tf.keras.callbacks.ModelCheckpoint(filepath = os.path.join(f"{model_dir}{model_name}.h5"),
monitor = "val_loss",
save_best_only = True,
save_weights_only = True,
verbose = 1
),
tf.keras.callbacks.EarlyStopping(monitor = 'val_accuracy',
patience = 4,
min_delta = 0.01,
)
]
if model_name == "VGG16":
LearningRate = 0.0001
else:
LearningRate = 0.0001
model.compile(loss = loss_fn,
optimizer = tf.keras.optimizers.Adam(learning_rate = LearningRate),
metrics=['accuracy'],
)
print("Start Transfer Training....")
history = model.fit(train_dataset,
validation_data = valid_dataset,
callbacks = callbacks_list,
epochs = epochs,
batch_size = batch_size,
verbose=1,
shuffle = True
)
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(len(loss))
plt.figure(0, figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
print("End of Transfer training !!!!!!!!")
def Tuning 함수는 백본의 나머지 layers를 초기화한 후에, 고정한 백본 layer를 미세조정하는 함수
def Tuning(model_name,
model_dir = BASE_DIR,
epochs = EPOCHS,
batch_size = BATCH_SIZE,
width = WIDTH,
height = HEIGHT,
num_classes = NUM_CLASSES):
model = MODEL(model_name, freeze = False, inferece_mode = True)
if model == False:
print("Only model_name : 'VGG16' or 'EfficientNetB0'")
else:
dataset = Dataset_Generator()
train_dataset = tf.data.Dataset.from_generator(
dataset.train_generator,
(tf.float32, tf.int32),
(tf.TensorShape([1, HEIGHT, WIDTH, 3]), tf.TensorShape([1, 1])),
)
valid_dataset = tf.data.Dataset.from_generator(
dataset.valid_generator,
(tf.float32, tf.int32),
(tf.TensorShape([1, HEIGHT, WIDTH, 3]), tf.TensorShape([1, 1])),
)
model.load_weights(f"{model_dir}{model_name}.h5")
print("Mdoel Complie....")
callbacks_list = [tf.keras.callbacks.ModelCheckpoint(filepath = os.path.join(f"{model_dir}{model_name}"+"_fine-tuning.h5"),
monitor = "val_loss",
save_best_only = False,
save_weights_only = True,
verbose = 1
),
tf.keras.callbacks.EarlyStopping(monitor = 'val_accuracy',
patience = 4,
min_delta = 0.0001,
)
]
loss_fn = FocalLoss()
if model_name == "VGG16":
LearningRate = 0.00001
else:
LearningRate = 0.00001
model.compile(loss = loss_fn,
optimizer = tf.keras.optimizers.Adam(learning_rate = LearningRate),
metrics=['accuracy'],
)
print("Start Fine-Tuning....")
fine_history = model.fit(train_dataset,
validation_data = valid_dataset,
callbacks = callbacks_list,
epochs = epochs,
batch_size = batch_size,
verbose=1,
shuffle = True
)
acc = fine_history.history['accuracy']
val_acc = fine_history.history['val_accuracy']
loss = fine_history.history['loss']
val_loss = fine_history.history['val_loss']
epochs_range = range(len(loss))
plt.figure(1, figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
del dataset
print("End of Fine-Tuning!!!!!!!!")
아래는 학습 코드
# 1. VGG16
Transfer_Learning(model_name = "VGG16")
Tuning(model_name = "VGG16")
# 2. EfficinetNet B0
Transfer_Learning(model_name = "EfficientNetB0")
Tuning(model_name = "EfficientNetB0")
이를 토대로 각각 학습시키도록 한다.
2.2. Result and Test
학습한 두 모델을 테스트 데이터셋에 테스트해보고 싶지만, 이미 validation set에서 낮은 성능을 보였기에, 의미없는 테스트라고 판단하여 생략하도록 한다.
VGG16 : 80%
EfficientNet B0 : 74%
이는 데이터 클래스의 비율이 class 0 에 극단적으로 몰려있음을 감안하더라도, class 0 에 대한 데이터마저도 잘못 예측한다는 의미이므로, 잘못된 모델이라고 볼 수가 있다.
아래 Figure 5는 kaggle의 2015, 2019 competition leaderboard를 가져온 것이다.
2015년도 competition은 84.957%가 1위, 2019년도 competition은 93.6129%가 1위이다.
사실 validation set은 2015년도 데이터셋으로 80% 성능을 보여도 11위에 빗댈 수 있지만, 개인적으로 마음에 들지 않는 성능이다.
해당 게시글은 병의 유무를 명확하게 구분하고, 어떤 병변으로 인해 병으로 예측하는지를 찾는 것이 목적이기 때문에 사전에 데이터를 명확하게 구분 가능케하는 모델이 필요하다.
그러므로 방향을 돌려서, 이진 분류로 병의 유무를 판단하는 방법으로 진행하도록 한다.
끝
'1. 모델 분석 > Classification' 카테고리의 다른 글
[당뇨망막병증(Diabetic Retinopathy)에서 Grad-CAM으로 병변 찾기] 1. Data Preprocessing (0) | 2021.05.31 |
---|