Glomerulus Classification with Convolutional Neural Networks
# 1. 세션 클리어
clear_session()
# 2. 레이어 연결
il = Input(shape=(280, 280, 3))
# 280 -> 45
hl = ZeroPadding2D(padding=(3,3))(il)
hl = Conv2D(filters=96,
kernel_size=13,
strides=(3,3),
activation='relu')(hl)
hl = MaxPool2D(pool_size=(3, 3),
strides=(2, 2))(hl)
# 45 -> 22
hl = Conv2D(filters=256,
kernel_size=5,
strides=(1,1),
padding='same',
activation='relu')(hl)
hl = MaxPool2D(pool_size=(3, 3),
strides=(2, 2))(hl)
# 22 -> 11
hl = Conv2D(filters=384,
kernel_size=3,
strides=(1,1),
padding='same',
activation='relu')(hl)
hl = Conv2D(filters=384,
kernel_size=3,
strides=(1,1),
padding='same',
activation='relu')(hl)
hl = Conv2D(filters=256,
kernel_size=3,
strides=(1,1),
padding='same',
activation='relu')(hl)
hl = MaxPool2D(pool_size=(2, 2))(hl)
hl = Flatten()(hl)
hl = Dense(128, activation='relu')(hl)
hl = Dense(128, activation='relu')(hl)
ol = Dense(1, activation='sigmoid')(hl)
# 3. 모델 선언
model = Model(il, ol)
# 4. 컴파일
model.compile(loss=keras.losses.binary_crossentropy,
metrics=['accuracy'],
optimizer='adam')
# 요약
model.summary()
"""
Total params: 7,742,721
Trainable params: 7,742,721
Non-trainable params: 0
"""
1x1, 3x3, 5x5 convolution, 3x3 max pooling으로 구성된 inception 모듈을 이용하였다. 서로 다른 크기의 필터를 통해 다른 scale의 필터를 추출하기 위함이다.
일반적으로 3x3, 5x5 필터는 모델의 연산량을 크게 늘리지만, 1x1 Convolution으로 채널을 축소함으로써 연산량을 대폭 감소시켰다는 것이 특징이다.
마지막으로 Auxiliary classifier인데, Back propagation 시 중간에 합류하여 원래의 결과를 변환시켜, 미분값이 0으로 수렴하는 문제를 막아주는 역할을 한다.
# Inception 하나의 구조
def inception(hl, filters_1x1, filters_3x3_red, filters_3x3, filters_5x5_red, filters_5x5, filters_pool):
hl_1 = Conv2D(filters=filters_1x1,
kernel_size=(1, 1),
padding='same',
strides=(1, 1),
activation='relu')(hl)
hl_2 = Conv2D(filters=filters_3x3_red,
kernel_size=(1, 1),
padding='same',
strides=(1, 1),
activation='relu')(hl)
hl_2 = Conv2D(filters=filters_3x3,
kernel_size=(3, 3),
padding='same',
strides=(1, 1),
activation='relu')(hl_2)
hl_3 = Conv2D(filters=filters_5x5_red,
kernel_size=(1, 1),
padding='same',
strides=(1, 1),
activation='relu')(hl)
hl_3 = Conv2D(filters=filters_5x5,
kernel_size=(5, 5),
padding='same',
strides=(1, 1),
activation='relu')(hl_3)
hl_4 = MaxPool2D(pool_size=(3, 3),
strides=(1, 1),
padding='same')(hl)
hl_4 = Conv2D(filters=filters_pool,
kernel_size=(1, 1),
padding='same',
strides=(1, 1),
activation='relu')(hl_4)
return Concatenate()([hl_1, hl_2, hl_3, hl_4])
"""
Total params: 11,560,835
Trainable params: 11,560,835
Non-trainable params: 0
"""
inception(hl, 64, 96, 128, 16, 32, 32)
로 선언할 수 있다.이번 실습은 4번에 해당하는 케이스로, Input과 가까운 일부 레이어를 제외하고 나머지 레이어에 대한 학습을 진행했다.
레이어를 정교한 방법으로 탐색하고, 나누어야할 것 같지만 우선은 이런 방법도 가능하다는 것 정도를 익히기 위해 임의의 레이어를 기준으로 나누었다.
현재 imagenet 데이터 벤치마크 성능에서 우수한 스코어를 기록한 davit 모델을 이용하였다.
!pip install -U keras-cv-attention-models
from keras_cv_attention_models import davit
# Will download and load pretrained imagenet weights.
base_model = davit.DaViT_T(pretrained="imagenet", input_shape=(280, 280, 3), num_classes=0)
from tensorflow.keras.layers import GlobalAveragePooling2D
base_model.trainable = True
# 0. 세션 클리어
clear_session()
# 1. 레이어 연결
il = Input(shape=(280, 280, 3))
hl = base_model(il)
hl = GlobalAveragePooling2D()(hl)
hl = Dropout(0.3)(hl)
ol = Dense(1, activation='sigmoid')(hl)
# 2. 모델 선언
model_vit = Model(il, ol)
# 3. 컴파일
model_vit.compile(loss=keras.losses.binary_crossentropy,
metrics=['accuracy'],
optimizer='adam')
# 요약
model_vit.summary()
"""
=================================================================
Total params: 27,591,937
Trainable params: 27,591,937
Non-trainable params: 0
_________________________________________________________________
"""
len(model_vit.trainable_variables)
>>> 212
for idx, layer in enumerate(base_model.layers) :
if idx < 80 :
layer.trainable = False
else :
layer.trainable = True
len(model_vit.trainable_variables)
>>> 168
x_train_vit = base_model.preprocess_input(x_train)
x_val_vit = base_model.preprocess_input(x_val)
x_test_vit = base_model.preprocess_input(x_test)
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
es = EarlyStopping(monitor='val_loss',
min_delta=0,
patience=7,
verbose=1,
restore_best_weights=True)
lr = ReduceLROnPlateau(monitor='val_loss',
factor=0.5,
patience=3,
verbose=1,
min_delta=0,
min_lr=0.00001)
history = model_vit.fit(x_train_vit, y_train, validation_data=(x_val_vit, y_val), verbose=1, epochs=1000, callbacks=[es, lr])
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model Loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train','test'], loc = 'upper left')
plt.show()
그림으로 쓱 훑고 이해도 잘 안되었던 모델구조를 직접 재현해보면서 모델 구조를 보는 능력이 어느정도 생긴 것 같다.
점점 예측 성능이 개선되는 것을 확인할 수 있었다.
좀 더 복잡하고 세부적인 기능을 이용하는 코드는 torch를 사용해야하는 경우가 많았는데, 아직 torch는 전혀 다루지 못하는 것이 아쉬웠다.
VIT, EffiNet과 같은 모델에 대한 지식이 없어 되는대로 가져다 사용한 것에 무언가 죄책감을 느꼈다.