딥러닝 이야기 / Multi-label Diagnosis Prediction using Chest X-ray and ResNet / 1. 흉부 X-ray와 ResNet을 이용한 환자 상태 예측

작성일: 2022.10.16
이번에는 MIMIC-CXR-JPG의 환자의 전면 흉부 X-ray 이미지 데이터를 이용하여 ResNet 모델을 통해 환자의 multi-label 진단을 예측하는 프로젝트에 대해 설명해보겠습니다.
프로젝트에 대해 설명하기 앞서, MIMIC-CXR-JPG는 환자의 민감한 정보를 수반하고 있습니다. 따라서 인가 받은 사람들만 접근할 수 있기 때문에, 데이터를 따로 보여주거나 GitHub에 공개하지 않겠습니다.
MIMIC-CXR-JPG에 대해 자세한 데이터 설명을 확인하고 싶다면 아래 링크를 참고하시기 바랍니다.
그리고 ResNet 모델을 통해 multi-label 진단을 예측하는 코드 또한 GitHub에 공개되어있으니 링크 달아두겠습니다.
오늘의 컨텐츠입니다.
- 데이터 설명
- 프로젝트의 조건
- 학습 전략
- ResNet multi-label 진단 예측 모델
Multi-label Diagnosis Prediction using Chest X-ray and ResNet
”
먼저 프로젝트에 사용한 데이터 종류를 소개하겠습니다. 이번 프로젝트를 진행하기 위해 사용한 데이터는 MIMIC-CXR-JPG는 3가지 종류의 데이터입니다. 이중 위의 2개는 인가받으면 직접 .csv로 내려받을 수 있으며, 맨 아래 이미지 데이터는 그 용량이 크기 때문에 wget으로 인가된 아이디와 비밀번호를 입력하여 다운로드 받을 수 있습니다.
- mimic-cxr-2.0.0-negbio
- mimic-cxr-2.0.0-metadata
- Chest X-ray
1. mimic-cxr-2.0.0-negbio
먼저 이 데이터는 subject_id라고 하는 환자 고유 번호와 study_id라는 연구 고유 번호가 있습니다. 즉 한 명의 환자의 4번의 연구를 위해 각각에 해당하는 4번의 X-ray를 찍었다면, 한 명의 subject_id에 4개의 study_id가 존재하는 것입니다. 즉 subject_id는 중복이 일어나며, 이 데이터의 코유 key는 study_id가 됩니다.
그리고 각각의 study_id에 해당하는 X-ray 사진에 대해 무기폐, 심장 비대, 폐경화 등에 해당하는 총 14개의 label로 이루어진 진단이 있습니다. 이때 각각의 목록에 대해 labeling이 되어있는데 그 의미는 아래와 같습니다.
- 1: 그 증세가 있음.
- 0: 그 증세가 없음.
- -1: 불확실함.
- 비어있는 경우: 의사가 언급하지 않음.
2. mimic-cxr-2.0.0-metadata
이 데이터는 각 study_id에 해당하는 X-ray 이미지들의 이름, 종류 등의 자세한 정보를 보여줍니다. 먼저 하나의 study_id에 여러 X-ray 이미지가 있을 수 있습니다. 하나의 study_id에 AP, PA, Lateral 이렇게 3 종류의 X-ray를 찍었다면 3가지의 사진에 대한 정보가 이 데이터에 담겨있습니다.
3. Chest X-ray
이제 각 X-ray 이미지에 대한 정보도 알았으니 실제 이미지가 필요합니다. X-ray 이미지는 다른 이미지 데이터에 비해 고해상도이며, 데이터 수도 37만개가 넘어 용량이 매우 큽니다(참고로 이미지는 흑백 이미지입니다). 실제로 모든 이미지의 용량을 다 합치면 500 GB 이상이나 됩니다. 따라서 이미지는 wget을 통해 아이디와 비밀번호를 이용하여 받을 수 있으며, 다운로드 시간도 상당히 오래 걸립니다.
”
이제 프로젝트의 조건을 설명하겠습니다.
- 하나의 study_id만 선택 먼저 프로젝트의 조건은 하나의 subject_id에 대해 하나의 study_id만 사용하는 것입니다. 앞서 하나의 subject_id에 여러 study_id가 있을 수 있다고 언급했기 때문에 하나의 study만 선택하기 위해서 lowest alphanumeric study_id를 선택하였습니다.
- AP view에 해당하는 X-ray 이미지만 사용 AP view는 X-ray가 몸의 앞쪽에서 뒤로 통과하여 찍은 사진을 의미합니다. 따라서 위에서 하나의 선택된 study_id에 대해 AP view X-ray 사진을 사용합니다. 만약 AP 사진이 여러개라면 jpg 파일 이름에 대해 lowest alphanumeric 파일을 사용합니다.
- Labeling 위에서 설명한 negbio에는 총 14가지 종류의 multi-label이 있으며, 이 프로젝트에서는 1을 제외한 나머지 label을 모두 0으로 취급합니다. 이때 label이 비어있는 경우도 0으로 취급합니다.
- Training/Test Split 위의 조건으로 필터링된 33,501개의 데이터 중 study_id가 8, 9로 끝나는 경우를 test set으로 두고 나머지를 training set로 정하였습니다(train: 26,807, test: 6,694).
Chest X-ray AP and PA views,
출처: Quora, What is the difference between an AP and a PA view of an X-ray?
”
이제 간단한 학습 전략을 살펴보겠습니다. 아래에서 성능이 더 좋았다고 언급하는 것은 test set에 대한 기준입니다.
- 이미지 전처리 이미지의 전처리는 training data의 이미지의 평균, 표준편차를 구해 정규화 합니다. 이미지를 [-1, 1] 사이의 범위로 정규화하여 실험도 해보았지만 성능은 평균과 표준편차를 사용하여 정규화 한 이미지를 사용한 학습이 더 좋게 나왔습니다.
- 모델 모델은 PyTorch의 pre-trained ResNet101 모델을 backbone으로 사용하였습니다. ResNet101 대신 ResNet50 모델을 통해 실험도 해보았지만, ResNet101의 성능이 더 좋았습니다.
- Optimizer Optimizer는 Adam, learning rate는 1e-4를 사용했습니다. 그리고 추가 실험으로 weight_decay를 1e-4를 두고 실험을 진행해보았지만, 적용하지 않은 경우가 결과가 더 좋게 나왔습니다.
- Best Model 선택 기준 Best model은 accuracy로 정하였습니다. Sigmoid로 나온 결과를 0.5 이상이면 1, 미만이면 0으로 만들어주고 accuracy를 ground truth와 비교하여 모델을 저장하였습니다. 이는 loss 기준으로 선택한 모델보다 성능이 더 좋았습니다.
- pos_weight 이번 실험에서는 PyTorch의 BCEWithLogitsLoss를 사용하는데 여기에 argument 옵션으로 pos_weight라는 것이 있습니다. 이것은 label의 0, 1의 개수가 unbalance 할 때 적용할 수 있는 방법입니다. 실제로 이번 실험에서 사용한 데이터의 0의 개수는 410,175, 1의 개수는 58839로 1보다 0의 비율이 훨씬 많습니다. 따라서 1의 개수를 동일하게 고려할 수 있도록 positive loss에 가중치를 곱해줄 수 있는데 이때 사용하는 옵션이 pos_weight인 것입니다. 하지만 pos_weight를 사용했을 때 성능이 약간 더 안 좋게 나왔는데 사용 유무에 따른 결과는 아래에서 보여드리도록 하겠습니다.
”
여기서는 ResNet 이용한 사망 예측 모델을 살펴보겠습니다. 코드는 PyTorch로 작성 되었으며, 데이터 전처리 및 훈련 코드는 GitHub 코드를 참고하시기 바랍니다.
class ResNet(nn.Module):
def __init__(self, img_size, label):
super(ResNet, self).__init__()
self.feature_size = img_size // 32
self.label = label
base_model = resnet101(pretrained=True, progress=False)
base_model = list(base_model.children())[:-2]
self.resnet = nn.Sequential(*base_model)
self.layer1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=3, kernel_size=1, stride=1, padding=0, bias=False),
nn.ReLU()
)
self.fc = nn.Sequential(
nn.Linear(2048*self.feature_size*self.feature_size, self.label)
)
def forward(self, x):
batch_size = x.size(0)
x = self.layer1(x)
x = self.resnet(x) # output size: B x 2048 x H/32 x W/32
x = x.view(batch_size, -1)
x = self.fc(x)
return x
ResNet
- 4번째 줄: ResNet을 거치고 나온 feature size(1/32배가 됨).
- 5번째 줄: Label 종류 개수(14개).
- 6 ~ 7번째 줄: Backbone 모델 및 마지막 fully connected 및 pooling 레이어 제외.
- 9 ~ 12번째 줄: ResNet은 칼라 이미지이기 때문에 흑백 X-ray 이미지의 가로, 세로 크기는 유지하되 channel 수만 3으로 증가시켜주는 레이어.
- 13 ~ 15번째 줄: Fully-connected 레이어.
- 18 ~ 24번째 줄: 이미지 데이터가 학습 시 거치는 부분.
실험 결과
Test set에 대해 실험 결과를 내었습니다. Test set은 위에서 언급하였듯이, 필터링 된 데이터 중 study_id의 끝 번호가 8, 9인 데이터로 구성하였습니다. 평가 지표는 AUROC (Area Under the Receiver Operating Characteristic)과 AUPRC (Area Under the Precision-Recall Curve)로 측정하였습니다. 또한 BCEWithLogitsLoss의 pos_weight 옵션 선택의 유무를 바탕으로 실험 결과를 비교해보겠습니다.
- AUROC: 1에 가까울수록 좋은 성능, 이진 분류에 있어서 0.5면 random guessing을 의미.
- AUPRC: 1에 가까울수록 좋은 성능, AUROC에 비해 좋은 결과를 내기 힘든 지표.
결과
- pos_weight (X)
- AUROC (macro): 0.7191
- AUROC (micro): 0.8360
- AUPRC (macro): 0.2804
- AUPRC (micro): 0.4764
- pos_weight (O)
- AUROC (macro): 0.7594
- AUROC (micro): 0.8649
- AUPRC (macro): 0.3091
- AUPRC (micro): 0.5313
MIMIC-CXR-JPG chest X-ray 이미지를 이용한 multi-label 진단 프로젝트에 대해 살펴보았습니다.
학습 과정과 전처리에 대한 자세한 코드는 GitHub에 있으니 참고하시면 될 것 같습니다.