뇌 MRI이미지로 알츠하이머와 경도인지장애를 진단하는 CNN 딥러닝 모델 개발과정 정리 및 회고.
(2) 모델링 과정
자유주제
(의료/헬스케어)데이터 직무 포지션
에서 풀고자하는 문제 정의데이터셋
선정 및 선정 이유딥러닝 파이프라인
구축 학습 및 검증
한계점
과 추후 발전 방향
(Part1) Intro & Metadata
서론
포지션설정, 기획의도
알츠하이머치매와 경도인지장애
연구의 필요성
목표 및 가설
데이터셋 및 메타데이터
데이터셋 소개
데이터 준비
메타데이터 분석
대시보드
(Part2) Modeling
모델링
개요 및 구조
데이터 로딩 및 분리
전처리 레이어
기본 CNN 모델
모델 성능 개선
전이학습
네거티브 전이
하이퍼파라미터 튜닝
최종 모델 학습 및 검증
(Part3) Prediction & Conclusion
예측 및 비교분석
테스트 데이터셋
예측 및 신뢰도
평가지표 및 혼동행렬
결론
요약
한계와 추후 발전방향
핵심과 소감
from google.colab import drive
drive.mount('/content/drive')
import os
base_dir = "/content/drive/MyDrive/AI/Alzheimer/"
data_dir = base_dir + "Axial"
os.listdir(data_dir)
'''
['AD', 'CN', 'MCI']
'''
import pandas as pd
# 변수 지정
CLASSES = ['CN', 'MCI', 'AD']
# 딕셔너리 형태로 이미지 갯수 저장
number_of_images = {}
for class_name in CLASSES:
number_of_images[class_name] = len(os.listdir(data_dir+"/"+class_name))
# 데이터프레임으로 만들기
image_count_df = pd.DataFrame(number_of_images.values(),
index=number_of_images.keys(),
columns=["Number of Images"])
# 결과 출력
display(image_count_df)
print("\nSum of Images: {}".format(image_count_df.sum()[0]))
Image Data 수 시각화 (소스코드는 이 글의 다음 과정인 파트3쪽에 있어서 ppt 이미지로 대체함)
from keras.utils import image_dataset_from_directory
# 변수 지정
CLASSES = ['CN', 'MCI', 'AD']
IMG_SIZE = 256
BATCH_SIZE = 32
SEED = 42
# 불러오기 (배치사이즈 32, 이미지크기 256)
dataset = image_dataset_from_directory(
data_dir,
shuffle=True,
class_names=CLASSES,
batch_size=BATCH_SIZE,
image_size=(IMG_SIZE,IMG_SIZE),
seed=SEED
)
'''
Found 5154 files belonging to 3 classes.
'''
# 레이블(타겟) 클래스 확인
print(dataset.class_names)
'''
['CN', 'MCI', 'AD']
'''
# 함수 지정
def dataset_split(ds, tr=0.8, val=0.1, test=0.1, shuffle=True, buf_size=10000, SEED=42):
ds_size = len(ds)
if shuffle:
ds = ds.shuffle(buf_size,seed=SEED)
train_size = int(ds_size*tr)
val_size = int(ds_size*test)
train = ds.take(train_size)
test0 = ds.skip(train_size)
val = test0.take(val_size)
test = test0.skip(val_size)
return train,val,test
# 분리 (Batch 단위로 분리가 진행됨)
train_ds, val_ds, test_ds = dataset_split(dataset)
print("Split전 Batched data 개수")
print(f"Dataset : {len(dataset)}")
print("\nSplit후 Batched data 개수")
print(f"Train : {len(train_ds)}")
print(f"Validation : {len(val_ds)}")
print(f"Test : {len(test_ds)}")
'''
Split전 Batched data 개수
Dataset : 162
Split후 Batched data 개수
Train : 129
Validation : 16
Test : 17
'''
train_ds=train_ds.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
val_ds=val_ds.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
test_ds=test_ds.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
# 변수정보
IMG_SIZE = 256
SEED = 42
# 정규화 layer 정의
resize_rescale = Sequential([
Resizing(IMG_SIZE, IMG_SIZE),
Rescaling(1.0/255)
])
# 데이터 증강(Augmentation) layer 정의
data_augmentation = Sequential([
RandomFlip("horizontal_and_vertical", seed=SEED),
RandomRotation(0.1, seed=SEED)
])
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False
import seaborn as sns
import tensorflow as tf
from keras.utils import image_dataset_from_directory
from keras.models import Sequential
from keras.layers import Resizing, Rescaling, RandomFlip, RandomRotation
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
class_list = ['CN', 'MCI', 'AD']
IMG_SIZE = 256
BATCH_SIZE = 32
CHANNELS = 3
EPOCHS = 10
SEED = 42
input_shape = (BATCH_SIZE, IMG_SIZE, IMG_SIZE, CHANNELS)
model1 = Sequential([
resize_rescale,
data_augmentation,
Conv2D(32, (3,3), padding='same', activation='relu', input_shape=input_shape),
MaxPooling2D((2,2)),
Conv2D(64, (3,3), padding='same', activation='relu'),
MaxPooling2D((2,2)),
Flatten(),
Dense(64, activation='relu'),
Dense(3, activation='softmax')
])
model1.build(input_shape)
model1.summary()
'''
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
sequential (Sequential) (32, 256, 256, 3) 0
sequential_1 (Sequential) (32, 256, 256, 3) 0
conv2d (Conv2D) (32, 256, 256, 32) 896
max_pooling2d (MaxPooling2D (32, 128, 128, 32) 0
)
conv2d_1 (Conv2D) (32, 128, 128, 64) 18496
max_pooling2d_1 (MaxPooling (32, 64, 64, 64) 0
2D)
flatten (Flatten) (32, 262144) 0
dense (Dense) (32, 64) 16777280
dense_1 (Dense) (32, 3) 195
=================================================================
Total params: 16,796,867
Trainable params: 16,796,867
Non-trainable params: 0
_________________________________________________________________
'''
model1.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['acc'])
history1 = model1.fit(
train_ds,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_data=val_ds
)
loss1 = history1.history['loss']
acc1 = history1.history['acc']
val_loss1 = history1.history['val_loss']
val_acc1 = history1.history['val_acc']
plt.figure(figsize=(15,5))
plt.subplot(121)
plt.plot(loss1, label="train_loss")
plt.plot(val_loss1, label="validation_loss")
plt.title("Loss of Model\nSparse Categorical Entropy")
plt.legend()
plt.subplot(122)
plt.plot(acc1, label="train_accuracy")
plt.plot(val_acc1, label="valiadation_accuracy")
plt.title("Metrics of Model\nAccuracy")
plt.legend()
plt.show()
model1.save(filepath="/content/drive/MyDrive/AI/Alzheimer/model1.hdf5")
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False
import seaborn as sns
import tensorflow as tf
from keras.utils import image_dataset_from_directory
from keras.models import Sequential
from keras.layers import Resizing, Rescaling, RandomFlip, RandomRotation
from keras.layers import Dense, GlobalAveragePooling2D
from keras.applications import ResNet50V2
CLASSES = ['CN', 'MCI', 'AD']
IMG_SIZE = 256
BATCH_SIZE = 32
CHANNELS = 3
EPOCHS = 10
SEED = 42
# ResNet50V2의 Feature Extractor 부분만을 import
resnet = ResNet50V2(weights='imagenet', include_top=False)
# 가중치가 학습되지 않도록 설정
for layer in resnet.layers:
layer.trainable = False
input_shape = (BATCH_SIZE, IMG_SIZE, IMG_SIZE, CHANNELS)
model2 = Sequential([
resize_rescale,
data_augmentation,
resnet,
GlobalAveragePooling2D(),
Dense(1024, activation='relu'),
Dense(3, activation='softmax')
])
model2.build(input_shape)
model2.summary()
'''
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
sequential (Sequential) (None, 256, 256, 3) 0
sequential_1 (Sequential) (None, 256, 256, 3) 0
resnet50v2 (Functional) (None, None, None, 2048) 23564800
global_average_pooling2d_1 (32, 2048) 0
(GlobalAveragePooling2D)
dense_2 (Dense) (32, 1024) 2098176
dense_3 (Dense) (32, 3) 3075
=================================================================
Total params: 25,666,051
Trainable params: 2,101,251
Non-trainable params: 23,564,800
_________________________________________________________________
'''
model2.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['acc'])
history2 = model2.fit(
train_ds,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_data=val_ds
)
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False
import seaborn as sns
import tensorflow as tf
from keras.utils import image_dataset_from_directory
from keras.models import Sequential
from keras.layers import Resizing, Rescaling, RandomFlip, RandomRotation
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from keras.callbacks import Callback
import IPython
!pip install -U keras-tuner
import keras_tuner as kt
CLASSES = ['CN', 'MCI', 'AD']
IMG_SIZE = 256
BATCH_SIZE = 32
CHANNELS = 3
EPOCHS = 10
SEED = 42
INPUT_SHAPE = (BATCH_SIZE, IMG_SIZE, IMG_SIZE, CHANNELS)
def model_builder(hp):
model = Sequential()
# 전처리 layer
model.add(resize_rescale)
model.add(data_augmentation)
# 튜닝 파라미터1
hp_conv2d = hp.Choice('filters', values = [16, 32, 64])
model.add(Conv2D(filters=hp_conv2d, kernel_size=(3,3), padding='same', activation='relu', input_shape=INPUT_SHAPE))
model.add(MaxPooling2D((2,2)))
model.add(Conv2D(filters=hp_conv2d, kernel_size=(3,3), padding='same', activation='relu'))
model.add(MaxPooling2D((2,2)))
model.add(Flatten())
# 튜닝 파라미터2
hp_units = hp.Int('units', min_value = 64, max_value = 512, step = 64)
model.add(Dense(units=hp_units, activation='relu'))
model.add(Dense(3, activation='softmax'))
# 모델 compile
model.compile(optimizer='adam',
loss = 'sparse_categorical_crossentropy',
metrics=['acc'])
return model
tuner = kt.Hyperband(model_builder,
objective='val_acc',
max_epochs=10,
factor=3,
directory=base_dir,
project_name='kt_hyperband')
class ClearTrainingOutput(Callback):
def on_train_end(*args, **kwargs):
IPython.display.clear_output(wait=True)
tuner.search(train_ds, epochs=10, validation_data = val_ds, callbacks=[ClearTrainingOutput()])
best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]
'''
Trial 29 Complete [00h 02m 41s]
val_acc: 0.912109375
Best val_acc So Far: 0.98046875
Total elapsed time: 00h 47m 34s
'''
print(f"""
하이퍼 파라미터 검색 완료.
최적화된 Conv2D 필터 수 : {best_hps.get('filters')}
최적화된 은닉층 Dense 노드 수 : {best_hps.get('units')}
""")
'''
하이퍼 파라미터 검색 완료.
최적화된 Conv2D 필터 수 : 16
최적화된 은닉층 Dense 노드 수 : 320
'''
model = tuner.hypermodel.build(best_hps)
model.build(INPUT_SHAPE)
model.summary()
'''
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
sequential (Sequential) (None, 256, 256, 3) 0
sequential_1 (Sequential) (None, 256, 256, 3) 0
conv2d_4 (Conv2D) (32, 256, 256, 16) 448
max_pooling2d_4 (MaxPooling (32, 128, 128, 16) 0
2D)
conv2d_5 (Conv2D) (32, 128, 128, 16) 2320
max_pooling2d_5 (MaxPooling (32, 64, 64, 16) 0
2D)
flatten_2 (Flatten) (32, 65536) 0
dense_4 (Dense) (32, 320) 20971840
dense_5 (Dense) (32, 3) 963
=================================================================
Total params: 20,975,571
Trainable params: 20,975,571
Non-trainable params: 0
_________________________________________________________________
'''
model.save(filepath="/content/drive/MyDrive/AI/Alzheimer/model_tuned.hdf5")
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False
import seaborn as sns
import tensorflow as tf
from tensorflow.keras.utils import image_dataset_from_directory
from tensorflow.keras.models import load_model
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
base_dir = "./imagedata/"
data_dir = base_dir + "Axial"
os.listdir(data_dir)
'''
['AD', 'CN', 'MCI']
'''
CLASSES = ['CN', 'MCI', 'AD']
IMG_SIZE = 256
BATCH_SIZE = 32
CHANNELS = 3
EPOCHS = 10
SEED = 42
dataset = image_dataset_from_directory(
data_dir,
shuffle=True,
class_names=CLASSES,
batch_size=BATCH_SIZE,
image_size=(IMG_SIZE, IMG_SIZE),
seed=SEED
)
print(dataset.class_names)
'''
Found 5154 files belonging to 3 classes.
['CN', 'MCI', 'AD']
'''
def dataset_split(ds, tr=0.8, val=0.1, test=0.1, shuffle=True, buf_size=10000, SEED=42):
ds_size = len(ds)
if shuffle:
ds = ds.shuffle(buf_size,seed=SEED)
train_size = int(ds_size*tr)
val_size = int(ds_size*test)
train = ds.take(train_size)
test0 = ds.skip(train_size)
val = test0.take(val_size)
test = test0.skip(val_size)
return train,val,test
train_ds, val_ds, test_ds = dataset_split(dataset)
print("Split전 Batched data 개수")
print(f"Dataset : {len(dataset)}")
print("\nSplit후 Batched data 개수")
print(f"Train : {len(train_ds)}")
print(f"Validation : {len(val_ds)}")
print(f"Test : {len(test_ds)}")
'''
Split전 Batched data 개수
Dataset : 162
Split후 Batched data 개수
Train : 129
Validation : 16
Test : 17
'''
train_ds=train_ds.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
val_ds=val_ds.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
test_ds=test_ds.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
model_path = base_dir + "model_tuned.hdf5"
model = load_model(filepath=model_path)
model.summary()
'''
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
sequential (Sequential) (None, 256, 256, 3) 0
sequential_1 (Sequential) (None, 256, 256, 3) 0
conv2d_4 (Conv2D) (32, 256, 256, 16) 448
max_pooling2d_4 (MaxPooling (32, 128, 128, 16) 0
2D)
conv2d_5 (Conv2D) (32, 128, 128, 16) 2320
max_pooling2d_5 (MaxPooling (32, 64, 64, 16) 0
2D)
flatten_2 (Flatten) (32, 65536) 0
dense_4 (Dense) (32, 320) 20971840
dense_5 (Dense) (32, 3) 963
=================================================================
Total params: 20,975,571
Trainable params: 20,975,571
Non-trainable params: 0
_________________________________________________________________
'''
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['acc'])
early_stop = EarlyStopping(monitor='val_loss', patience=5)
model_save_path = base_dir + "/best_model.hdf5"
checkpointer = ModelCheckpoint(filepath=model_save_path, monitor='val_loss',
verbose=0, save_best_only=True)
history1 = model.fit(
train_ds,
epochs = EPOCHS*10,
batch_size=BATCH_SIZE,
validation_data=val_ds,
verbose=1,
callbacks=[early_stop, checkpointer]
)
마지막 epoch
저장된 epoch
학습 곡선
인사이트
model_best = load_model(filepath=model_save_path)
test_loss, test_acc = model_best.evaluate(test_ds, verbose=1)
print(f"test loss : {test_loss}")
print(f"test accuracy : {test_acc}")
'''
17/17 [==============================] - 1s 65ms/step - loss: 0.0502 - acc: 0.9853
test loss : 0.05021626874804497
test accuracy : 0.9852941036224365
'''
# val_loss 최소값, 인덱스 확인
print(np.min(val_loss1))
print(np.argmin(val_loss1))
print(np.argmin(val_loss1)-len(val_loss1))
'''
0.025903187692165375
16
-6
'''
# 인덱스 16 혹은 -6이 최적화된 값이므로 이를 dataframe으로 만들기
train_l_m = [loss1[-6], acc1[-6]]
val_l_m = [val_loss1[-6], val_acc1[-6]]
test_l_m = [test_loss, test_acc]
loss_acc_dict = dict(zip(['train', 'validation', 'test'], [train_l_m, val_l_m, test_l_m]))
loss_acc_df = pd.DataFrame(loss_acc_dict, index=['loss','accuracy'])
loss_acc_df
loss_dict = dict(zip(['Train','Validation','Test'], [loss1[-6], val_loss1[-6], test_loss]))
loss_df = pd.DataFrame(loss_dict, index=['loss'])
loss_bar = sns.barplot(data=loss_df.round(4), palette=['green', 'blue', 'red'], alpha=0.5)
loss_bar.bar_label(loss_bar.containers[0])
plt.title('"Loss" of CNN Classifier\nloss : Sparse Categorical Crossentropy')
plt.ylim(0.0, 0.5)
plt.show()
acc_dict = dict(zip(['Train','Validation','Test'], [acc1[-6], val_acc1[-6], test_acc]))
acc_df = pd.DataFrame(acc_dict, index=['acc'])
acc_bar = sns.barplot(data=acc_df.round(4), palette=['green', 'blue', 'red'], alpha=0.5)
acc_bar.bar_label(acc_bar.containers[0])
plt.title('"Metrics" of CNN Classifier\nmetrics : Accuracy')
plt.ylim(0, 1.2)
plt.show()