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

흉부 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에 공개되어있으니 링크 달아두겠습니다.




오늘의 컨텐츠입니다.

  1. 데이터 설명
  2. 프로젝트의 조건
  3. 학습 전략
  4. 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을 통해 아이디와 비밀번호를 이용하여 받을 수 있으며, 다운로드 시간도 상당히 오래 걸립니다.

프로젝트의 조건

이제 프로젝트의 조건을 설명하겠습니다.

  1. 하나의 study_id만 선택
  2. 먼저 프로젝트의 조건은 하나의 subject_id에 대해 하나의 study_id만 사용하는 것입니다. 앞서 하나의 subject_id에 여러 study_id가 있을 수 있다고 언급했기 때문에 하나의 study만 선택하기 위해서 lowest alphanumeric study_id를 선택하였습니다.

  3. AP view에 해당하는 X-ray 이미지만 사용
  4. AP view는 X-ray가 몸의 앞쪽에서 뒤로 통과하여 찍은 사진을 의미합니다. 따라서 위에서 하나의 선택된 study_id에 대해 AP view X-ray 사진을 사용합니다. 만약 AP 사진이 여러개라면 jpg 파일 이름에 대해 lowest alphanumeric 파일을 사용합니다.

  5. Labeling
  6. 위에서 설명한 negbio에는 총 14가지 종류의 multi-label이 있으며, 이 프로젝트에서는 1을 제외한 나머지 label을 모두 0으로 취급합니다. 이때 label이 비어있는 경우도 0으로 취급합니다.

  7. Training/Test Split
  8. 위의 조건으로 필터링된 33,501개의 데이터 중 study_id가 8, 9로 끝나는 경우를 test set으로 두고 나머지를 training set로 정하였습니다(train: 26,807, test: 6,694).

위의 조건을 적용시켜 37만개가 넘는 데이터 중 필터링되어 남은 총 33,501(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에 대한 기준입니다.

  1. 이미지 전처리
  2. 이미지의 전처리는 training data의 이미지의 평균, 표준편차를 구해 정규화 합니다. 이미지를 [-1, 1] 사이의 범위로 정규화하여 실험도 해보았지만 성능은 평균과 표준편차를 사용하여 정규화 한 이미지를 사용한 학습이 더 좋게 나왔습니다.

  3. 모델
  4. 모델은 PyTorch의 pre-trained ResNet101 모델을 backbone으로 사용하였습니다. ResNet101 대신 ResNet50 모델을 통해 실험도 해보았지만, ResNet101의 성능이 더 좋았습니다.

  5. Optimizer
  6. Optimizer는 Adam, learning rate는 1e-4를 사용했습니다. 그리고 추가 실험으로 weight_decay를 1e-4를 두고 실험을 진행해보았지만, 적용하지 않은 경우가 결과가 더 좋게 나왔습니다.

  7. Best Model 선택 기준
  8. Best model은 accuracy로 정하였습니다. Sigmoid로 나온 결과를 0.5 이상이면 1, 미만이면 0으로 만들어주고 accuracy를 ground truth와 비교하여 모델을 저장하였습니다. 이는 loss 기준으로 선택한 모델보다 성능이 더 좋았습니다.

  9. pos_weight
  10. 이번 실험에서는 PyTorch의 BCEWithLogitsLoss를 사용하는데 여기에 argument 옵션으로 pos_weight라는 것이 있습니다. 이것은 label의 0, 1의 개수가 unbalance 할 때 적용할 수 있는 방법입니다. 실제로 이번 실험에서 사용한 데이터의 0의 개수는 410,175, 1의 개수는 58839로 1보다 0의 비율이 훨씬 많습니다. 따라서 1의 개수를 동일하게 고려할 수 있도록 positive loss에 가중치를 곱해줄 수 있는데 이때 사용하는 옵션이 pos_weight인 것입니다. 하지만 pos_weight를 사용했을 때 성능이 약간 더 안 좋게 나왔는데 사용 유무에 따른 결과는 아래에서 보여드리도록 하겠습니다.

ResNet multi-label 진단 예측 모델

여기서는 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에 있으니 참고하시면 될 것 같습니다.

태그 #MIMIC-CXR-JPG #ResNet #multi-label