Chest X-Ray Medical Diagnosis with Deep Learning - ③ Model Development

start

BioinformaticsAndMe

 

 


Chest X-Ray Medical Diagnosis with Deep Learning

     ① Import Packages and Function
     ② Load the Datasets
     ③ Model Development
     ④ Training
    ⑤ Prediction and Evaluation 

 

 

 

 

③ Model Development


: 이제 Model 교육 및 개발을 수행

: 하지만 실제로 신경망을 훈련하기 전에, class 불균형을 해소해야 함

plt.xticks(rotation=90)
plt.bar(x=labels, height=np.mean(train_generator.labels, axis=0))
plt.title("Frequency of Each Class")
plt.show()

: 위 그림에서 여러 병리에 따라 양성 사례의 유병률이 크게 다르다는 것을 알 수 있음

  →이러한 경향은 전체 데이터셋의 경향도 반영함

: 이상적으로는 균형 잡힌 데이터셋으로 모델을 훈련시켜, positive 및 negative 사례가 손실에 동등하게 기여하도록 함

 

 

 

Exercise 2 - Computing Class Frequencies


: 아래 함수를 통해, 각 데이터셋의 라벨에 대한 빈도를 계산

def compute_class_freqs(labels):
    """
    Compute positive and negative frequences for each class.

    Args:
        labels (np.array): matrix of labels, size (num_examples, num_classes)
    Returns:
        positive_frequencies (np.array): array of positive frequences for each
                                         class, size (num_classes)
        negative_frequencies (np.array): array of negative frequences for each
                                         class, size (num_classes)
    """    
    
    # total number of patients (rows)
    N = labels.shape[0]
    
    positive_frequencies = np.sum(labels, axis=0) / labels.shape[0]
    negative_frequencies = 1 - positive_frequencies

    return positive_frequencies, negative_frequencies

: 예제 테스트

labels_matrix = np.array(
    [[1, 0, 0],
     [0, 1, 1],
     [1, 0, 1],
     [1, 1, 1],
     [1, 0, 1]]
)
print("labels:")
print(labels_matrix)

test_pos_freqs, test_neg_freqs = compute_class_freqs(labels_matrix)

print(f"pos freqs: {test_pos_freqs}")

print(f"neg freqs: {test_neg_freqs}")

: 결과 출력

# Expected output
labels:
[[1 0 0]
 [0 1 1]
 [1 0 1]
 [1 1 1]
 [1 0 1]]
pos freqs: [0.8 0.4 0.8]
neg freqs: [0.2 0.6 0.2]

: 이제 training 데이터셋에 대한 빈도를 계산

freq_pos, freq_neg = compute_class_freqs(train_generator.labels)
freq_pos

# Expected output
array([0.02 , 0.013, 0.128, 0.002, 0.175, 0.045, 0.054, 0.106, 0.038,
       0.021, 0.01 , 0.014, 0.016, 0.033])

: 각 병리에 대하여, positive 또는 negative의 기여 비율을 나란히 시각화

data = pd.DataFrame({"Class": labels, "Label": "Positive", "Value": freq_pos})
data = data.append([{"Class": labels[l], "Label": "Negative", "Value": v} for l,v in enumerate(freq_neg)], ignore_index=True)
plt.xticks(rotation=90)
f = sns.barplot(x="Class", y="Value", hue="Label" ,data=data)

: 위 그림에서 보듯이, 양성 사례의 기여도는 음성 사례의 기여도보다 현저히 낮음

: 기여도가 동일하도록 수행하는 방법은 각 클래스별 가중치인 Wpos 및 Wneg를 곱하여, 전체 기여도가 동일하게함

pos_weights = freq_neg
neg_weights = freq_pos
pos_contribution = freq_pos * pos_weights 
neg_contribution = freq_neg * neg_weights

 

: positive, negative 기여도를 다시 그래프로 확인

data = pd.DataFrame({"Class": labels, "Label": "Positive", "Value": pos_contribution})
data = data.append([{"Class": labels[l], "Label": "Negative", "Value": v} 
                        for l,v in enumerate(neg_contribution)], ignore_index=True)
plt.xticks(rotation=90)
sns.barplot(x="Class", y="Value", hue="Label" ,data=data);

: 위 그림에서처럼 가중치를 적용되어, 각 클래스의 양수/음수 레이블이 손실 함수에 대해 동일한 기여도를 갖게됨

 

 

 

Exercise 3 - Weighted Loss


: 아래 weighted_loss 함수를 작성하여 각 배치의 가중 손실을 계산하는 손실 함수를 반환

: 다중 클래스 손실의 경우 각 개별 클래스의 평균 손실을 합산

def get_weighted_loss(pos_weights, neg_weights, epsilon=1e-7):
    """
    Return weighted loss function given negative weights and positive weights.

    Args:
      pos_weights (np.array): array of positive weights for each class, size (num_classes)
      neg_weights (np.array): array of negative weights for each class, size (num_classes)
    
    Returns:
      weighted_loss (function): weighted loss function
    """
    def weighted_loss(y_true, y_pred):
        """
        Return weighted loss value. 

        Args:
            y_true (Tensor): Tensor of true labels, size is (num_examples, num_classes)
            y_pred (Tensor): Tensor of predicted labels, size is (num_examples, num_classes)
        Returns:
            loss (Tensor): overall scalar loss summed across all classes
        """
        # initialize loss to zero
        loss = 0.0
                
        for i in range(len(pos_weights)):
            # for each class, add average weighted loss for that class 
            loss += -(K.mean( pos_weights[i] * y_true[:,i] * K.log(y_pred[:,i] + epsilon) + \
                                neg_weights[i] * (1 - y_true[:,i]) * K.log(1 - y_pred[:,i] + epsilon), axis = 0))
        return loss
    
       
    return weighted_loss

: 예제 테스트

sess = K.get_session()
with sess.as_default() as sess:
    print("Test example:\n")
    y_true = K.constant(np.array(
        [[1, 1, 1],
         [1, 1, 0],
         [0, 1, 0],
         [1, 0, 1]]
    ))
    print("y_true:\n")
    print(y_true.eval())

    w_p = np.array([0.25, 0.25, 0.5])
    w_n = np.array([0.75, 0.75, 0.5])
    print("\nw_p:\n")
    print(w_p)

    print("\nw_n:\n")
    print(w_n)

    y_pred_1 = K.constant(0.7*np.ones(y_true.shape))
    print("\ny_pred_1:\n")
    print(y_pred_1.eval())

    y_pred_2 = K.constant(0.3*np.ones(y_true.shape))
    print("\ny_pred_2:\n")
    print(y_pred_2.eval())

    # test with a large epsilon in order to catch errors
    L = get_weighted_loss(w_p, w_n, epsilon=1)

    print("\nIf we weighted them correctly, we expect the two losses to be the same.")
    L1 = L(y_true, y_pred_1).eval()
    L2 = L(y_true, y_pred_2).eval()
    print(f"\nL(y_pred_1)= {L1:.4f}, L(y_pred_2)= {L2:.4f}")
    print(f"Difference is L1 - L2 = {L1 - L2:.4f}")
# Expected output
Test example:

y_true:

[[1. 1. 1.]
 [1. 1. 0.]
 [0. 1. 0.]
 [1. 0. 1.]]

w_p:

[0.25 0.25 0.5 ]

w_n:

[0.75 0.75 0.5 ]

y_pred_1:

[[0.7 0.7 0.7]
 [0.7 0.7 0.7]
 [0.7 0.7 0.7]
 [0.7 0.7 0.7]]

y_pred_2:

[[0.3 0.3 0.3]
 [0.3 0.3 0.3]
 [0.3 0.3 0.3]
 [0.3 0.3 0.3]]

If we weighted them correctly, we expect the two losses to be the same.

L(y_pred_1)= -0.4956, L(y_pred_2)= -0.4956
Difference is L1 - L2 = 0.0000

 

 

 

3.3 DenseNet121


: 다음으로 Keras에서 로드한 후, 두 개의 레이어를 추가 할 수 있는 DenseNet121 모델을 사용

  *GlobalAveragePooling2D - DenseNet121에서 마지막 convolution 레이어의 평균을 얻음

  *Dense - 각 클래스에 대한 예측 logit을 얻기 위한 sigmoid 기능이 있는 layer

: compile 함수에서 loss 매개 변수를 지정하여, 모델에 대한 사용자 정의 손실 함수를 설정할 수 있음

# create the base pre-trained model
base_model = DenseNet121(weights='drive/MyDrive/nih/densenet.hdf5', include_top=False)

x = base_model.output

# add a global spatial average pooling layer
x = GlobalAveragePooling2D()(x)

# and a logistic layer
predictions = Dense(len(labels), activation="sigmoid")(x)

model = Model(inputs=base_model.input, outputs=predictions)
model.compile(optimizer='adam', loss=get_weighted_loss(pos_weights, neg_weights))

 

 

 

 

 

다음 분석

Chest X-Ray Medical Diagnosis with Deep Learning

    ④ Training

 

 

 

#Reference

1) www.coursera.org/learn/ai-for-medical-diagnosis

2) github.com/vishrutskaushik/Chest-X-Ray-Medical-Diagnosis

 

 

 

Chest X-Ray Medical Diagnosis with Deep Learning - ③ Model Development

end

BioinformaticsAndMe

 

 

 

+ Recent posts