본문 바로가기
ML,DL

[머신러닝/딥러닝] 흉부 엑스선 기반 폐렴 진단: 분석정리 및 시각화

by 공부기 2023. 2. 12.

 

드디어! 마지막 장으로 이번에는 경진대회에 참여하지 않고 캐글러가 공유한 데이터셋으로 모델링 연습을 실습해보았다

 

이번 데이터셋에서는 흉부 엑스선 이미지가 나오는데, 이 이미지들을 보고 정상인의 엑스선인지, 폐렴에 걸린 사람의 엑스선인지 판별하는 모델을 만들어보며 훈련과 예측 단계를 함수로 묶어 활용하는 방법을 학습한다.

 

경지대회가 아니기 때문에 결과를 제출할 수 없고 정해진 평가 지표도 없다. 그래서 원하는 지표를 사용하면 되는데, 이번에는 정확도, F1 점수를 사용한다. 두 평가지표를 구하려면 예측을 확률이 아닌 이산값(양성:1 또는 음성:0) 기본적으로 예측하는 확률을 이용해 타깃 예측값을 이산값으로 바꿀 수 있다.

 

 

 

데이터셋은 다음과 같이 제공되어있다.

▶ train: 훈련 데이터

       - NORMAL: 정상 엑스선 이미지

       - PNEUMONIA: 폐렴 엑스선 이미지

▶ val: 검증 데이터

       - NORMAL: 정상 엑스선 이미지

       - PNEUMONIA: 폐렴 엑스선 이미지

▶ test: 테스트 데이터

       - NORMAL: 정상 엑스선 이미지

       - PNEUMONIA: 폐렴 엑스선 이미지

 

 

 

<데이터셋>

https://www.kaggle.com/datasets/paultimothymooney/chest-xray-pneumonia

 

Chest X-Ray Images (Pneumonia)

5,863 images, 2 categories

www.kaggle.com

 

 

<참고 코드>

https://www.kaggle.com/code/dhruvmak/eda-with-bokeh-efficientnet-92/notebook

 

EDA with Bokeh, EfficientNet 92%

Explore and run machine learning code with Kaggle Notebooks | Using data from Chest X-Ray Images (Pneumonia)

www.kaggle.com

 

 

 

 

 

데이터 둘러보기

간단하게 훈련, 검증, 테스트 데이터 개수와 타깃값별 이미지 개수를 살펴본다.

 

# 데이터 경로
data_path = '/kaggle/input/chest-xray-pneumonia/chest_xray/'

# 훈련, 검증, 테스트 데이터 경로 설정
train_path = data_path + 'train/'
valid_path = data_path + 'val/'
test_path = data_path + 'test/'

 

이번에는 한 디렉터리에 있는 것이 아니라서 경로를 따로 설정해주고, 이전까지 해왔던 거처럼 csv 파일이 아니라서 판다스의 read_csv()를 사용하지 않고 경로를 설정해주었다.

 

 

from glob import glob

print(f'훈련 데이터 개수 : {len(glob(train_path + "*/*"))}')
print(f'검증 데이터 개수 : {len(glob(valid_path + "*/*"))}')
print(f'테스트 데이터 개수 : {len(glob(test_path + "*/*"))}')

 

 

glob 함수로 경로에 해당하는 파일들을 리스트로 반환한 값에 len을 사용하면 개수를 구할 수 있다. 훈련 데이터 개수는 5216, 검증 데이터 16개, 테스트 데이터 개수 624개다. 검증 데이터가 16개로 너무 적어서 검증 데이터 성능 점수는 크게 신뢰할 필요는 없다.

 

 

 

다음은 타깃값별 개수를 구하는 코드다. (정상 이미지, 폐렴 이미지)

 

all_normal_imgs = []    # 모든 정상 이미지를 담을 리스트 초기화
all_pneumonia_imgs = [] # 모든 폐렴 이미지를 담을 리스트 초기화

for cat in ['train/', 'val/', 'test/']:
    data_cat_path = data_path + cat
    # 정상, 폐렴 이미지 경로
    normal_imgs = glob(data_cat_path + 'NORMAL/*')
    pneumonia_imgs = glob(data_cat_path + 'PNEUMONIA/*')
    # 정상, 폐렴 이미지 경로를 리스트에 추가
    all_normal_imgs.extend(normal_imgs)
    all_pneumonia_imgs.extend(pneumonia_imgs)

print(f'정상 흉부 이미지 개수 : {len(all_normal_imgs)}')
print(f'폐렴 흉부 이미지 개수 : {len(all_pneumonia_imgs)}')

 

정상, 폐렴 이미지 경로를 각 리스트를 만들어 추가해고 개수를 세어보았다. 정상 이미지 1583개, 폐럼 이미지 4273개로 폐렴 흉부 이미지 개수가 정상 흉부 이미지 개수보다 많았다.

 

 

 

 

 

 

 

데이터 시각화

타깃값 분포와 실제 엑스선 이미지를 출력해보자.

 

 

타깃값 분포

바로 위에서 구했던 타깃값별 개수를 이용해 파이그래프로 비율을 한눈에 확인한다.

 

import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline

mpl.rc('font', size=15)
plt.figure(figsize=(7, 7))

label = ['Normal', 'Pneumonia'] # 타깃값 레이블
# 타깃값 분포 파이 그래프
plt.pie([len(all_normal_imgs), len(all_pneumonia_imgs)], 
        labels = label, 
        autopct = '%.1f%%');

타깃값 분포 파이그래프

 

타깃값은 정상 27% vs 폐렴 73%로 폐렴이 많았다.

 

 

 

 

 

이미지 출력

 

이미지도 출력해서 확인해볼 건데, 저번 장에서는 이미지 ID를 입력받아왔지만 이번에는 경로를 전달하여 출력하게 된다.

 

import matplotlib.gridspec as gridspec
import cv2

def show_image(img_paths, rows=2, cols=3): 
    assert len(img_paths) <= rows*cols # 이미지가 행/열 개수보다 많으면 오류 발생
    
    mpl.rc('font', size=8)
    plt.figure(figsize=(15, 8)) 
    grid = gridspec.GridSpec(rows, cols) # 서브플롯 배치

    # 이미지 출력
    for idx, img_path in enumerate(img_paths):
        image = cv2.imread(img_path) # 이미지 파일 읽기
        ax = plt.subplot(grid[idx])
        ax.imshow(image) # 이미지 출력

 

이번에도 2행 3열로 총 6장의 이미지를 출력하는 함수를 만들었다.

 

# 정상 엑스선 이미지 경로(마지막 6장)
num_of_imgs = 6
normal_img_paths = all_normal_imgs[-num_of_imgs:]

# 이미지 출력
show_image(normal_img_paths)

 

 
 

정상 엑스선 이미지

 

앞서 만들었던 각 타깃값 이미지 경로가 담긴 리스트를 이용해서 정상 이미지를 출력해보았다. 이미지 가로 세로 크기가 동일하지 않고 제각각이라서 변환기를 이용해 이미지 크기를 일치시킬 것이다.

 

 

 

마지막으로 폐렴인 엑스선 이미지를 출력해보았다.

 

# 폐렴 엑스선 이미지 경로(마지막 6장)
pneumonia_img_paths = all_pneumonia_imgs[-num_of_imgs:]

# 이미지 출력
show_image(pneumonia_img_paths)

 

폐렴 엑스선 이미지

 

의학적 지식이 없어서 잘은 모르겠지만 정상 엑스선 이미지가 더 또렷해 보인다. 다음 글에 이어서 이 이미지들을 식별하는 딥러닝 모델을 만들어볼 것이다.

 

 

 

 

 


분석 정리

1. 검증 데이터와 훈련 데이터가 각 다른 디렉터리에 별도로 제공하여 훈련 데이터에서 따로 나누지 않아도 된다.

2. 주어진 파일이 csv가 아니라서 앞 장에서 했던 것과 방식이 조금 다르다.

3. 검증 데이터가 16개로 너무 적어서 검증 데이터 성능 점수를 크게 신뢰할 필요 없다.

4. 타깃값이 같은 이미지끼리 디렉터리로 구분되어 제공된다.

5. 제공된 이미지들의 크기가 제각각이다. 이런 경우 이미지 크기를 일정하게 조절해주는 것이 좋으므로 변환기를 사용할 것이다.

 

 

 

참고 교재

 

머신러닝·딥러닝 문제해결 전략 |  신백균

 

[머신러닝·딥러닝 문제해결 전략 - chapter13]