본문 바로가기

Computer Science/AI Exploration

[E-09] Pneumonia

728x90

들어가며

최근 딥러닝 기술이 산업적으로 명확한 용도를 입증한 도메인 중 하나로 의료 분야를 들 수 있다. 영상분석 인력의 개인적 편차, 주관적 판단, 피로에 의한 오진 등의 부정확성을 극복할 수 있는 좋은 대안으로 인정받고 있다.

하지만, 의료 영상 분석은 일반 이미지 처리와는 다른 독특한 특징을 가지고 있다.

  • 의료 영상 이미지는 개인 정보 보호 등의 이슈로 인해 데이터를 구하는 것이 쉽지 않습니다.
  • 라벨링 작업 자체가 전문적 지식을 요하므로 데이터셋 구축 비용이 비쌉니다.
  • 희귀질병을 다루는 경우 데이터를 입수하는 것 자체가 드문 일입니다.
  • 음성/양성 데이터 간 imbalance가 심합니다. 학습에 주의가 필요합니다.
  • 이미지만으로 진단이 쉽지 않아 다른 데이터와 결합해서 해석해야 할 수도 있습니다.

따라서 의료 영상 처리를 위해서는 딥러닝 영상처리 기술뿐 아니라, 의료 도메인 지식 및 의료 영상에 대한 명확한 이해가 필요하다.

이번 시간엔 의료 영상 기초 상식에 대해 알아보고, 실제로 의료 영상 데이터를 처리하는 실습을 해보자.

데이터는 여기서 준비한다.

Chest X-Ray Images (Pneumonia)

$ mkdir -p ~/aiffel/chest_xray
$ ln -s ~/data/ ~/aiffel/chest_xray

X-RAY 이미지

이번 시간엔 X-RAY 이미지를 기반으로 하는 폐렴 진단 딥러닝 모델을 제작하자. 먼저, X-RAY 영상을 촬영하기 위해 사용되는 자세 분류체계를 이해해야 한다.

1) 의료영상 자세 분류

https://ko.wikipedia.org/wiki/%ED%95%B4%EB%B6%80%ED%95%99_%EC%9A%A9%EC%96%B4

위의 이미지에 따르면 의료 영상 촬영은 인체를 세 방향의 단면으로 나누어 진행된다.

  • Sagittal plane : 시상면. 사람을 왼쪽과 오른쪽을 나누는 면.

  • Coronal plane : 관상면. 인체를 앞뒤로 나누는 면.

  • Transverse plane : 횡단면(수평면). 인체를 상하로 나누는 면.

예시 사진을 보자.

이와 같이 오늘 사용할 데이터는 모두 Coronal Plane 이미지로 되어있다.

https://ko.wikipedia.org/wiki/%ED%95%B4%EB%B6%80%ED%95%99_%EC%9A%A9%EC%96%B4

영상을 볼 떄의 해부학적 위치에 대해 알아보자. 우리가 영상을 보게 되면 위의 이미지와 같이 보게 된다. 가장 많이들 헷갈려하는 게 오른쪽/왼쪽이다.

이제 X-RAY에 대해 배워보자.

2) X-RAY 특성

X-RAY는 전자기파가 몸을 통과한 결과를 이미지화 시킨 것이다. 즉, 통과하고 남은 전자기파의 결과이기 때문에 색상이 아니라 흑백 명암으로 나오게 된다.

http://m.etnews.com/20181102000172?obj=Tzo4OiJzdGRDbGFzcyI6Mjp7czo3OiJyZWZlcmVyIjtOO3M6NzoiZm9yd2FyZCI7czoxMzoid2ViIHRvIG1vYmlsZSI7fQ%3D%3D

손 영상이다.

  • 뼈 : 하얀색
  • 근육 및 지방 : 연한 회색
  • 공기 : 검은색

http://health.cdc.go.kr/health/Resource/Module/Content/Printok.do?idx=2110&subIdx=4

흉부 영상이다.

  • 갈비뼈 : 하얀색
  • 폐 : 검은색
  • 어깨 쪽의 지방 및 근육 : 연한 회색

폐는 공기가 많이 차 있어서 검은색이 나오게 된다.

폐렴을 진단해보자(1)

의료 인공지능은 질병의 정확한 분류, 환자의 중증도를 확인하는 용도 등 다양하게 사용된다. 이 중 가장 핫한 분야는 질병을 탐지해내는 인공지능이다. 폐렴 딥러닝 모델을 만들어보자.

데이터셋

이번 노드에서 사용할 데이터는 캐글의 Chest X-Ray Images이다.

데이터 구성은 train, test, val로 구성되며, 각 이미지 카테고리(폐렴, 정상)에 대한 하위 폴더를 포함한다. 5,856 개의 X-Ray 이미지 (JPEG)와 2 개의 범주 (폐렴 / 정상)가 있다. 전체 데이터의 크기는 총 1.2GB 가량 된다.

해당 이미지는 중국 광저우에 있는 광저우 여성 및 어린이 병원의 1~5 세 소아 환자의 흉부 X선 영상이다.

폐렴이란?

폐렴(pneumonia 뉴모니아)은 폐에 염증이 생긴 상태로 중증의 호흡기 감염병이다. 세균을 통한 감염이 가장 많으며, 바이러스, 균류, 또는 기타 미생물도 원인이 될 수가 있다. 드물게는 알레르기 반응이나 자극적인 화학 물질을 흡입해 발생하기도 한다. 노인이나 어린아이, 혹은 전체적으로 상태가 안 좋은 환자들이나 기침 반사가 약한 사람들에게는 흡인성 폐렴이 발생한다. 그리고 세균이 원인인 경우는 항생제로 치료를 할 수 있다. 항생제가 생기기 전에는 50~90%가 사망할 정도로 위험한 질환이었으나, 현재는 거의 사망하지 않는다. 1940년대에 항생제가 개발되기 전까지는 폐렴 환자의 1/3 정도가 사망하였다. 오늘날에는 적절한 의학적 치료로 폐렴 환자의 95% 이상이 회복된다. 그러나 일부 저개발국(개발 도상국)에서는 폐렴이 여전히 주요 사망 원인 중 하나이다.
출처 : https://ko.wikipedia.org/wiki/폐렴

염증은 유해한 자극에 대한 생체반응 중 하나로 면역세포, 혈관, 염증 매개체들이 관여하는 보호반응이다. 염증의 목적은 세포의 손상을 초기 단계에서 억제하고, 상처 부분의 파괴된 조직 및 괴사된 세포를 제거하며, 동시에 조직을 재생하는 것이다.
출처 : https://ko.wikipedia.org/wiki/염증

폐렴이 걸렸을 떄 X-RAY상에서 어떻게 나오는지 보자.

폐렴 구별법

폐렴의 구별법은 언뜻 예상외로 간단하다.
X-RAY 사진상, 다양한 양상의 음영(폐 부위에 희미한 그림자) 증가가 관찰된다.
구별 방법은 간단하지만 실제로 영상을 보면 희미한 경우가 많이 있어 저게 실제로 폐렴으로 인한 것인지 아니면 다른 이유 때문인지 파악하기 어렵다.

정상적인 흉부 X-RAY(왼쪽)는 이미지에서 비정상적인 음영 영역이 없는 깨끗한 폐를 보여준다. 세균성 폐렴 (가운데)은 일반적으로 오른쪽 상부 엽 (흰색 화살표)에 있는 나타내는 반면, 바이러스성 폐렴 (오른쪽)은 양쪽 폐에서보다 확산된 "interstitial(조직 사이에 있는)" 패턴으로 나타난다.

(참고) 아래 제공되는 실습코드는 이 캐글 노트북을 기반으로 작성된 것임을 밝힙니다.

1) Set-up

library import

import os, re
import random, math
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import warnings 
warnings.filterwarnings(action='ignore')
# 필요한 변수들을 생성한다.

# 데이터 로드할 때 빠르게 로드할 수 있도록하는 설정 변수
AUTOTUNE = tf.data.experimental.AUTOTUNE
# X-RAY 이미지 사이즈 변수
IMAGE_SIZE = [180, 180]

# 데이터 경로 변수
ROOT_PATH = os.path.join(os.getenv('HOME'), 'aiffel')
TRAIN_PATH = ROOT_PATH + '/chest_xray/data/train/*/*' # *은 모든 디렉토리와 파일을 의미합니다.
VAL_PATH = ROOT_PATH + '/chest_xray/data/val/*/*'
TEST_PATH = ROOT_PATH + '/chest_xray/data/test/*/*'

# 프로젝트를 진행할 때 아래 두 변수를 변경해보세요
BATCH_SIZE = 16
EPOCHS = 25

print(ROOT_PATH)
/aiffel/aiffel

2) 데이터 가져오기

데이터를 열어보면 이미 train, val(validation), test 로 나누어져 있다. 각 데이터의 개수를 세 보자.

train_filenames = tf.io.gfile.glob(TRAIN_PATH)
test_filenames = tf.io.gfile.glob(TEST_PATH)
val_filenames = tf.io.gfile.glob(VAL_PATH)

print(len(train_filenames))
print(len(test_filenames))
print(len(val_filenames))
5216
624
16
# val 수가 너무 적기 때문에, train:val을 8:2로 재조정

# train 데이터와 validation 데이터를 모두 filenames에 담습니다
filenames = tf.io.gfile.glob(TRAIN_PATH)
filenames.extend(tf.io.gfile.glob(VAL_PATH))

# 모아진 filenames를 8:2로 나눕니다
train_size = math.floor(len(filenames)*0.8)
random.seed(8)
random.shuffle(filenames)
train_filenames = filenames[:train_size]
val_filenames = filenames[train_size:]

print(len(train_filenames))
print(len(val_filenames))
4185
1047
# train data 내에 정상 이미지 수/ 폐렴 이미지 수 check
## 파일 경로에 "normal","pneumonia"가 있는지 확인하면 폐렴이미지도 셀 수 있다

COUNT_NORMAL = len([filename for filename in train_filenames if "NORMAL" in filename])
print(f"Normal images count in training set: {COUNT_NORMAL}")


COUNT_PNEUMONIA = len([filename for filename in train_filenames if "PNEUMONIA" in filename])
print(f"Pneumonia images count in training set: {COUNT_PNEUMONIA}")
Normal images count in training set: 1072
Pneumonia images count in training set: 3113

우리가 사용할 CNN 모델의 경우 데이터가 클래스별 balance가 좋을수록 training을 잘 한다. imbalance한 것은 차후에 조정하자. test와 val 데이터셋은 평가를 위해 사용되기 때문에 학습과 관련이 없다. 따라서 imbalance 하더라도 문제가 없다.

이제 tf.data 인서튼서를 만들어보자. tf.data는 tensorflow에서 학습시킬 때, mini-batch로 작업할 수 있도록 해준다.

mini-batch는 왜 사용하는가?

train_list_ds = tf.data.Dataset.from_tensor_slices(train_filenames)
val_list_ds = tf.data.Dataset.from_tensor_slices(val_filenames)
TRAIN_IMG_COUNT = tf.data.experimental.cardinality(train_list_ds).numpy()
print(f"Training images count: {TRAIN_IMG_COUNT}")

VAL_IMG_COUNT = tf.data.experimental.cardinality(val_list_ds).numpy()
print(f"Validating images count: {VAL_IMG_COUNT}")
Training images count: 4185
Validating images count: 1047

현재 이미지에는 라벨 데이터가 없다. 따라서, 파일 경로를 참고하여 라벨 데이터를 만들어주자.

# 파일 경로의 끝에서 두번째 부분을 확인하면 양성과 음성을 구분할 수 있습니다
def get_label(file_path):
    parts = tf.strings.split(file_path, os.path.sep)
    return parts[-2] == "PNEUMONIA"   # 폐렴이면 양성(True), 노말이면 음성(False)|

이미지 데이터는 사이즈가 제각각일 가능성이 높다. 이미지의 사이즈를 통일시키고 GPU 메모리를 효율적으로 사용하기 위해 이미지 사이즈를 줄이자.

decode_img, process_path 함수를 만들자.

process_path 함수에서 decode_img 함수를 이용해서 이미지의 데이터 타입을 float으로 바꾸고 사이즈를 변경한다. 이후 get_label을 이용해서 라벨 값을 가져온다.

# 이미지를 알맞은 형식으로 바꿉니다.
def decode_img(img):
    img = tf.image.decode_jpeg(img, channels=3) # 이미지를 uint8 tensor로 수정
    img = tf.image.convert_image_dtype(img, tf.float32) # float32 타입으로 수정
    img = tf.image.resize(img, IMAGE_SIZE) # 이미지 사이즈를 IMAGE_SIZE로 수정
    return img

# 이미지 파일의 경로를 입력하면 이미지와 라벨을 읽어옵니다.
def process_path(file_path):
    label = get_label(file_path) # 라벨 검출
    img = tf.io.read_file(file_path) # 이미지 읽기
    img = decode_img(img) # 이미지를 알맞은 형식으로 수정
    return img, label

train dataset, val dataset을 만든다. num_parallel_calls parameter에 위에서 할당한 AUTOTUNE변수를 이용하면 데이터를 더욱 빠르게 처리할 수 있다.

# make train, validation dataset

train_ds = train_list_ds.map(process_path, num_parallel_calls=AUTOTUNE)
val_ds = val_list_ds.map(process_path, num_parallel_calls=AUTOTUNE)
# check about image resize, labeling

for image, label in train_ds.take(1): # train_ds.take(1)은 하나의 데이터만 가져온다는 의미다.
    print("Image shape: ", image.numpy().shape)
    print("Label: ", label.numpy())
Image shape:  (180, 180, 3)
Label:  False
# make test dataset

test_list_ds = tf.data.Dataset.list_files(TEST_PATH)
TEST_IMAGE_COUNT = tf.data.experimental.cardinality(test_list_ds).numpy()
test_ds = test_list_ds.map(process_path, num_parallel_calls=AUTOTUNE)
test_ds = test_ds.batch(BATCH_SIZE)

print(TEST_IMAGE_COUNT)
624

Tensorflow에서는 tf.data 파이프라인을 사용해서 학습 데이터를 효율적으로 사용할 수 있도록 해준다. tf.data 파이프라인을 이용하여 prepare_for_training() 함수를 정의해서 데이터를 변환시켜주자.

  • shuffle()을 사용해 고정 크기 버퍼를 유지하고, 해당 버터에서 무작위로 균일하게 다음 요소를 선택한다.

  • repeat()을 사용하면 epoch을 진행하면서 여러 번 데이터셋을 불러오게 되는데, 이때 repeat()을 사용한 테이터셋의 경우 여러번 데이터셋을 활용할 수 있게 해준다.

  • batch()를 사용하면 BATCH_SIZE에서 정한 만큼의 배치로 주어진다.

  • prefetch()를 사용하면 학습데이터를 나눠서 읽어오게 해준다. 따라서 첫 번째 데이터를 GPU에서 학습하는 동안 두 번째 데이터를 CPU에서 준비할 수 있어 리소스의 유휴 상태를 줄일 수 있다.

def prepare_for_training(ds, shuffle_buffer_size=1000):
    ds = ds.shuffle(buffer_size=shuffle_buffer_size)
    ds = ds.repeat()
    ds = ds.batch(BATCH_SIZE)
    ds = ds.prefetch(buffer_size=AUTOTUNE)
    return ds

train_ds = prepare_for_training(train_ds)
val_ds = prepare_for_training(val_ds)

3) 데이터 시각화

데이터를 보기 위해 먼저, train에 있는 batch 중 첫 번째 배치를 추출한다. 추출된 배치를 image와 label 데이터셋으로 나눈다. 추출된 배치를 image와 label dataset으로 나눈다. 이후 show_batch() 함수를 이용해 결과 사진을 보자.

# 이미지 배치를 입력하면 여러장의 이미지를 보여줍니다.
def show_batch(image_batch, label_batch):
    plt.figure(figsize=(10,10))
    for n in range(BATCH_SIZE):
        ax = plt.subplot(4,math.ceil(BATCH_SIZE/4),n+1)
        plt.imshow(image_batch[n])
        if label_batch[n]:
            plt.title("PNEUMONIA")
        else:
            plt.title("NORMAL")
        plt.axis("off")


image_batch, label_batch = next(iter(train_ds))
show_batch(image_batch.numpy(), label_batch.numpy())

4) CNN 모델링

CNN 요약

CNN을 통해 모델링을 하고 결과를 보자.

먼저, Convolution block을 만들어야 한다. conv_block()의 구성은 Convolution을 두 번 진행하고 Batch Normalization을 통해서 Gradient Vanishing, Gradient Exploding을 해결한다. 이후 Max Pooling을 진행한다.

# making conv_block
def conv_block(filters):
    block = tf.keras.Sequential([
        tf.keras.layers.SeparableConv2D(filters, 3, activation='relu', padding='same'),
        tf.keras.layers.SeparableConv2D(filters, 3, activation='relu', padding='same'),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.MaxPool2D()
    ])

    return block
# making Dense Block
def dense_block(units, dropout_rate):
    block = tf.keras.Sequential([
        tf.keras.layers.Dense(units, activation='relu'),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Dropout(dropout_rate)
    ])

    return block

방금 만든 모델에는 BatchNormalization과 Dropout이라는 두 가지 regularization 기법이 동시에 사용되고 있다. 하지만 일반적으로는 이런 방법은 잘 사용하지 않는다. 대표적인 게 아래 논문의 사례이다.

Understanding the Disharmony between Dropout and Batch Normalization by Variance Shift

위 논문에서는 variance shift를 억제하는 Batch Normalization과 이를 유발하는 Dropout을 동시에 사용하는 것이 어울리지 않는다고 밝혔다.

하지만, 실용적으론는 두 방법을 동시에 사용하는 것이 낫다고 보는 견해도 있다. 그 이유는 동시에 사용하는 것이 성능 향상에 예외적으로 도움을 주는 경우도 있기 때문이다.

Rethinking the Usage of Batch Normalization and Dropout in the Training of Deep Neural Networks

def build_model():
    model = tf.keras.Sequential([
        tf.keras.Input(shape=(IMAGE_SIZE[0], IMAGE_SIZE[1], 3)),

        tf.keras.layers.Conv2D(16, 3, activation='relu', padding='same'),
        tf.keras.layers.Conv2D(16, 3, activation='relu', padding='same'),
        tf.keras.layers.MaxPool2D(),

        conv_block(32),
        conv_block(64),

        conv_block(128),
        tf.keras.layers.Dropout(0.2),

        conv_block(256),
        tf.keras.layers.Dropout(0.2),

        tf.keras.layers.Flatten(),
        dense_block(512, 0.7),
        dense_block(128, 0.5),
        dense_block(64, 0.3),

        tf.keras.layers.Dense(1, activation='sigmoid')
    ])

    return model

5) data imbalance 처리

imbalance를 해결하기 위해 Weight balancing이라는 테크닉이 사용된다. 이는 training set의 각 데이터에서 loss를 계산할 때 특정 클래스의 데이터에 더 큰 loss 값을 갖도록 가중치를 부여하는 방법이다. Keras는 model.fit()을 호출할 때 파라미터로 넘기는 class_weight에 이러한 클래스별 가중치를 세팅할 수 있도록 지원하고 있다.

참고: 딥러닝에서 클래스 불균형을 다루는 방법

아래 코드에서 weight_for_0은 Normal image에 사용할 weight를, weight_for_1은 Pneumonia image에 사용할 weight를 세팅한다. 이 weight들은 Normal과 Pneumonia 전체 데이터 건수에 반비례하도록 설정된다.

weight_for_0 = (1 / COUNT_NORMAL)*(TRAIN_IMG_COUNT)/2.0 
weight_for_1 = (1 / COUNT_PNEUMONIA)*(TRAIN_IMG_COUNT)/2.0

class_weight = {0: weight_for_0, 1: weight_for_1}

print('Weight for NORMAL: {:.2f}'.format(weight_for_0))
print('Weight for PNEUMONIA: {:.2f}'.format(weight_for_1))
Weight for NORMAL: 1.95
Weight for PNEUMONIA: 0.67

6) 모델 훈련

먼저 GPU를 선택한다. 모델은 위에서 만들었던 모델 함수인 build_model()을 model에 선언한다.

  • loss: binary_crossentropy
  • optimizer: adam
  • metrics: accuracy, precision, recall
with tf.device('/GPU:0'):
    model = build_model()

    METRICS = [
        'accuracy',
        tf.keras.metrics.Precision(name='precision'),
        tf.keras.metrics.Recall(name='recall')
    ]

    model.compile(
        optimizer='adam',
        loss='binary_crossentropy',
        metrics=METRICS
    )
# 모델을 fit하자.각 파라미터에 위에서 선언했던 변수, 데이터셋을 가져와서 각각에 맞게 넣어주자

with tf.device('/GPU:0'):
    history = model.fit(
        train_ds,
        steps_per_epoch=TRAIN_IMG_COUNT // BATCH_SIZE,
        epochs=EPOCHS,
        validation_data=val_ds,
        validation_steps=VAL_IMG_COUNT // BATCH_SIZE,
        class_weight=class_weight,

    )
Epoch 1/25
261/261 [==============================] - 86s 193ms/step - loss: 0.4201 - accuracy: 0.7974 - precision: 0.9460 - recall: 0.7718 - val_loss: 0.8511 - val_accuracy: 0.7346 - val_precision: 0.7346 - val_recall: 1.0000
Epoch 2/25
261/261 [==============================] - 49s 188ms/step - loss: 0.2528 - accuracy: 0.9040 - precision: 0.9685 - recall: 0.9002 - val_loss: 1.6538 - val_accuracy: 0.7346 - val_precision: 0.7346 - val_recall: 1.0000
Epoch 3/25
261/261 [==============================] - 49s 190ms/step - loss: 0.2057 - accuracy: 0.9205 - precision: 0.9737 - recall: 0.9178 - val_loss: 0.1585 - val_accuracy: 0.9308 - val_precision: 0.9189 - val_recall: 0.9935
Epoch 4/25
261/261 [==============================] - 50s 191ms/step - loss: 0.1844 - accuracy: 0.9286 - precision: 0.9753 - recall: 0.9276 - val_loss: 0.0897 - val_accuracy: 0.9712 - val_precision: 0.9855 - val_recall: 0.9752
Epoch 5/25
261/261 [==============================] - 51s 196ms/step - loss: 0.1950 - accuracy: 0.9253 - precision: 0.9759 - recall: 0.9226 - val_loss: 0.1605 - val_accuracy: 0.9365 - val_precision: 0.9943 - val_recall: 0.9190
Epoch 6/25
261/261 [==============================] - 54s 208ms/step - loss: 0.1629 - accuracy: 0.9387 - precision: 0.9814 - recall: 0.9351 - val_loss: 0.0976 - val_accuracy: 0.9654 - val_precision: 0.9946 - val_recall: 0.9581
Epoch 7/25
261/261 [==============================] - 47s 181ms/step - loss: 0.1543 - accuracy: 0.9456 - precision: 0.9845 - recall: 0.9418 - val_loss: 0.0758 - val_accuracy: 0.9721 - val_precision: 0.9973 - val_recall: 0.9647
Epoch 8/25
261/261 [==============================] - 47s 182ms/step - loss: 0.1467 - accuracy: 0.9490 - precision: 0.9846 - recall: 0.9461 - val_loss: 0.3459 - val_accuracy: 0.8885 - val_precision: 0.9985 - val_recall: 0.8493
Epoch 9/25
261/261 [==============================] - 60s 230ms/step - loss: 0.1414 - accuracy: 0.9504 - precision: 0.9840 - recall: 0.9489 - val_loss: 0.0714 - val_accuracy: 0.9702 - val_precision: 0.9829 - val_recall: 0.9764
Epoch 10/25
261/261 [==============================] - 46s 177ms/step - loss: 0.1200 - accuracy: 0.9564 - precision: 0.9873 - recall: 0.9536 - val_loss: 0.0498 - val_accuracy: 0.9827 - val_precision: 0.9869 - val_recall: 0.9895
Epoch 11/25
261/261 [==============================] - 45s 174ms/step - loss: 0.1116 - accuracy: 0.9586 - precision: 0.9894 - recall: 0.9547 - val_loss: 0.0703 - val_accuracy: 0.9712 - val_precision: 0.9933 - val_recall: 0.9674
Epoch 12/25
261/261 [==============================] - 45s 174ms/step - loss: 0.1221 - accuracy: 0.9569 - precision: 0.9883 - recall: 0.9533 - val_loss: 0.4596 - val_accuracy: 0.8548 - val_precision: 1.0000 - val_recall: 0.8026
Epoch 13/25
261/261 [==============================] - 46s 175ms/step - loss: 0.1105 - accuracy: 0.9576 - precision: 0.9896 - recall: 0.9529 - val_loss: 0.0681 - val_accuracy: 0.9760 - val_precision: 0.9755 - val_recall: 0.9921
Epoch 14/25
261/261 [==============================] - 45s 174ms/step - loss: 0.1266 - accuracy: 0.9526 - precision: 0.9866 - recall: 0.9492 - val_loss: 0.1873 - val_accuracy: 0.9308 - val_precision: 0.9971 - val_recall: 0.9083
Epoch 15/25
261/261 [==============================] - 46s 175ms/step - loss: 0.1545 - accuracy: 0.9380 - precision: 0.9827 - recall: 0.9330 - val_loss: 0.2284 - val_accuracy: 0.9058 - val_precision: 0.9970 - val_recall: 0.8743
Epoch 16/25
261/261 [==============================] - 45s 174ms/step - loss: 0.1126 - accuracy: 0.9567 - precision: 0.9899 - recall: 0.9514 - val_loss: 0.0555 - val_accuracy: 0.9788 - val_precision: 0.9947 - val_recall: 0.9765
Epoch 17/25
261/261 [==============================] - 45s 173ms/step - loss: 0.0869 - accuracy: 0.9682 - precision: 0.9921 - recall: 0.9650 - val_loss: 0.0548 - val_accuracy: 0.9827 - val_precision: 0.9908 - val_recall: 0.9857
Epoch 18/25
261/261 [==============================] - 45s 174ms/step - loss: 0.1096 - accuracy: 0.9612 - precision: 0.9877 - recall: 0.9597 - val_loss: 0.0871 - val_accuracy: 0.9625 - val_precision: 0.9973 - val_recall: 0.9515
Epoch 19/25
157/261 [=================>............] - ETA: 6s - loss: 0.0705 - accuracy: 0.9749 - precision: 0.9939 - recall: 0.9720

안 돌려봤는데 추후 참고용

✔ Question: monitor에 여러 값을 넣는 방법은?

# Earlystopping추가해보기
es = EarlyStopping(monitor=['precision','recall'], patience=2, verbose=1)


with tf.device('/GPU:0'):
    history = model.fit(
        train_ds,
        steps_per_epoch=TRAIN_IMG_COUNT // BATCH_SIZE,
        epochs=EPOCHS,
        validation_data=val_ds,
        validation_steps=VAL_IMG_COUNT // BATCH_SIZE,
        class_weight=class_weight,
        callbacks=[es]
    )

7. 결과 확인

그래프로 Epoch마다 모델의 precision, recall, accuracy, loss의 변화를 살펴보자.

fig, ax = plt.subplots(1, 4, figsize=(20, 3))
ax = ax.ravel()

for i, met in enumerate(['precision', 'recall', 'accuracy', 'loss']):
    ax[i].plot(history.history[met])
    ax[i].plot(history.history['val_' + met])
    ax[i].set_title('Model {}'.format(met))
    ax[i].set_xlabel('epochs')
    ax[i].set_ylabel(met)
    ax[i].legend(['train', 'val'])
# 테스트 데이터로 모델 평가를 해보자.

loss, accuracy, precision, recall = model.evaluate(test_ds)
print(f'Loss: {loss},\nAccuracy: {accuracy},\nPrecision: {precision},\nRecall: {recall}')

좀 더 성능을 개선할 방법은 없을까?? 오늘 다룬 폐렴 데이터를 기반으로 한 연구도 많이 찾아볼 수 있다.

의료 데이터의 경우 구하기가 어렵고, 구하더라도 데이터가 작은 경우가 많이 있다. 이런 문제를 해결하기 위해서는 Data Augmentation 방법을 많이 시도한다.

Data Augmentation은 각 데이터에 최적화된 방법을 찾기가 어렵고 제약사항이 많을 때 사용한다. 그래서 의료분야에서는 기본적인 Data augmentation 방법들을 많이 사용한다. 이미지 회전, 가우시안 노이즈 추가 방법 등을 사용한다. 특히 장기의 크기는 사람마다 바뀌지 않기 때문에, 노이즈 추가 방법을 통해 이런 특성을 활용한다.

전통적인 Data augmentation 외에도 GAN을 이용해서 Data augmentation을 시도하는 연구도 있으니 참고해보자.

728x90

'Computer Science > AI Exploration' 카테고리의 다른 글

[E-11] Stock prediction  (0) 2022.02.22
[E-10] Generative Modeling  (0) 2022.02.22
[E-08] Project  (0) 2022.02.22
[E-08] Text Summarization  (0) 2022.02.22
[E-07] Image Segmentation  (0) 2022.02.22