File size: 13,786 Bytes
0fac3d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
from typing import Iterable
import torch

from losses import DistillationLoss
import utils

from sklearn.metrics import average_precision_score, roc_auc_score, f1_score
from sklearn.metrics import hamming_loss
from sklearn.metrics import accuracy_score

from sklearn.metrics import recall_score
from sklearn.metrics import precision_score

import numpy as np

from evaluate_model import load_weights, compute_challenge_metric, multilabel_specificity_score
import sys


def normalize_model_outputs(model_outputs):
    """
    Normalize model outputs to the range [0, 1].
    
    Parameters:
    model_outputs (numpy.ndarray): The raw output of the deep learning model.
    
    Returns:
    numpy.ndarray: The normalized model outputs.
    """
    a = model_outputs.min()
    b = model_outputs.max()
    return (model_outputs - a) / (b - a)


sinus_rhythm_ID = set(['426783006'])

weights_file = "./weights.csv"

classes, weights = load_weights(weights_file)

def train_one_epoch(model: torch.nn.Module, criterion: DistillationLoss,
                    data_loader: Iterable, optimizer: torch.optim.Optimizer,
                    device: torch.device, epoch: int,
                    set_training_mode=True):
    
    model.train(set_training_mode)
    metric_logger = utils.MetricLogger(delimiter="  ")
    metric_logger.add_meter('lr', utils.SmoothedValue(window_size=1, fmt='{value:.6f}'))
    header = 'Epoch: [{}]'.format(epoch)
    print_freq = 600
    
    output_list = []
    target_list = []    

    loss_value_after_each_epcoh = 0
    batch_num = 0

    for samples, targets in metric_logger.log_every(data_loader, print_freq, header):        
        batch_num += 1

        samples = samples.to(device, non_blocking=True)
        targets = targets.to(device, non_blocking=True)
        samples = samples.unsqueeze(1)

        outputs = model(samples.float())
        loss = criterion(outputs, targets.float())
            
        loss_value = loss.item()
        
        optimizer.zero_grad()
        
        loss.backward() # Backward pass: Compute gradient of the loss with respect to model parameters
            
        # Update parameters/using the Noam
        optimizer.step() 

        torch.cuda.synchronize()
        
        metric_logger.update(loss=loss_value)
        metric_logger.update(lr=optimizer.param_groups[0]["lr"])
        
        target_list.append(targets.data.cpu().numpy())
        output_list.append(outputs.data.cpu().numpy())
        loss_value_after_each_epcoh += loss_value
    
    # gather the stats from all processes
    metric_logger.synchronize_between_processes()
    print("Averaged stats:", metric_logger)

    # below code is for record the AUPRC of training
    targets_all = np.concatenate(target_list, axis=0)
    outputs_all = np.concatenate(output_list, axis=0)
    outputs_all = normalize_model_outputs(outputs_all)

    threshold = 0.5
    targets_all[targets_all >= threshold] = 1
    targets_all[targets_all < threshold] = 0

    # When using the function below, y_true must be a binarized value.
    train_auprc = average_precision_score(y_true = targets_all, y_score = outputs_all)
    print("This is the training AUPRC:", train_auprc)

    ### The code below is for obtaining the thresholds
    scores_challengeScore = []

    scores_F1 = []
    Macro_scores_F1 = []

    scores_SubsetAccuracy = []
    scores_HammingLoss = []

    for thr in np.arange(0., 1., 0.02):
        outputs_dyn = np.array([[(1 if prob > thr else 0) for prob in probs] for probs in np.array(outputs_all)])

        challenge_value = compute_challenge_metric(weights, targets_all, outputs_dyn, classes, sinus_rhythm_ID)
        scores_challengeScore.append(challenge_value)

        f1 = f1_score(targets_all, outputs_dyn, average='weighted')
        scores_F1.append(f1)

        f1_macro = f1_score(targets_all, outputs_dyn, average='macro')
        Macro_scores_F1.append(f1_macro)

        subset_accuracy = accuracy_score(targets_all, outputs_dyn)
        scores_SubsetAccuracy.append(subset_accuracy)

        hamming = hamming_loss(targets_all, outputs_dyn)
        scores_HammingLoss.append(hamming)

    scores_challengeScore = np.array(scores_challengeScore)
    scores_F1 = np.array(scores_F1)
    ## Macro F1-score
    Macro_scores_F1 = np.array(Macro_scores_F1)

    scores_SubsetAccuracy = np.array(scores_SubsetAccuracy)
    scores_HammingLoss = np.array(scores_HammingLoss)

    # print("This is the challenge score list from training set:\n", scores)

    # Best thrs for challenge score
    thrs_CHALL = np.array([np.argmax(scores_challengeScore, axis=0)*0.02])
    print("This is the best threshold for the challenge score from training set", thrs_CHALL)
    outputs_best_CHALL = np.array([[(1 if prob > thrs_CHALL else 0) for prob in probs] for probs in np.array(outputs_all)])
    challenge_value = compute_challenge_metric(weights, targets_all, outputs_best_CHALL, classes, sinus_rhythm_ID)
    print("This is the challenge score from training set:", challenge_value)

    # Best thrs for F1
    scores_F1 = np.array([np.argmax(scores_F1, axis=0)*0.02])
    print("This is the best threshold for the F1 from training set", scores_F1)
    outputs_best_f1 = np.array([[(1 if prob > scores_F1 else 0) for prob in probs] for probs in np.array(outputs_all)])
    f1 = f1_score(targets_all, outputs_best_f1, average='weighted')
    print("This is the f1 score from training set:", challenge_value)

    # Best thrs for subset accuracy
    scores_SubsetAccuracy = np.array([np.argmax(scores_SubsetAccuracy, axis=0)*0.02])
    print("This is the best threshold for the Subset Accuracy from training set", scores_SubsetAccuracy)
    outputs_best_SubsetAccuracy = np.array([[(1 if prob > scores_SubsetAccuracy else 0) for prob in probs] for probs in np.array(outputs_all)])
    subset_accuracy = accuracy_score(targets_all, outputs_best_SubsetAccuracy)
    print("This is the subset accuracy from training set:", subset_accuracy)

    # Best thrs for hamming loss, here is the loss value from output, we need to find the minimum value.
    # Determine the optimal threshold for Hamming loss. The loss values are obtained from the output, and the goal is to find the minimum value.
    scores_HammingLoss = np.array([np.argmin(scores_HammingLoss, axis=0)*0.02])
    print("This is the best threshold for the Subset Accuracy from training set", scores_HammingLoss)
    outputs_best_HammingLoss = np.array([[(1 if prob > scores_HammingLoss else 0) for prob in probs] for probs in np.array(outputs_all)])
    hamming = hamming_loss(targets_all, outputs_best_HammingLoss)
    print("This is the Hamming from training set:", hamming)

    # Best thrs for Macro F1-score
    Macro_scores_F1 = np.array([np.argmax(Macro_scores_F1, axis=0)*0.02])
    print("This is the best threshold for the Macro F1 from training set", Macro_scores_F1)

    loss_value_after_each_epcoh /= batch_num
    return train_auprc, loss_value_after_each_epcoh, thrs_CHALL, scores_F1, scores_SubsetAccuracy, scores_HammingLoss, Macro_scores_F1

@torch.no_grad()
def evaluate(data_loader, model, thrs_chall, thrs_weighted_F1, thrs_accuracy, thrs_hammingLoss, thrs_Macro_scores_F1, device):
    # criterion = torch.nn.CrossEntropyLoss()
    criterion = torch.nn.BCEWithLogitsLoss()

    metric_logger = utils.MetricLogger(delimiter="  ")
    header = 'Test:'

    targets = []
    outputs = []
    loss_value_after_each_epcoh = 0
    batch_num = 0
    # switch to evaluation mode
    model.eval()

    for images, target in metric_logger.log_every(data_loader, 100, header):
        
        batch_num += 1
        images = images.to(device, non_blocking=True)
        target = target.to(device, non_blocking=True)

        images = images.unsqueeze(1)

        output = model(images.float())

        loss = criterion(output, target.float())
        
        metric_logger.update(loss=loss.item())

        targets.append(target.data.cpu().numpy())
        outputs.append(output.data.cpu().numpy())
    
        loss_value_after_each_epcoh += loss.item()

    # below code is for record the AUPRC of validation
    targets = np.concatenate(targets, axis=0)
    outputs = np.concatenate(outputs, axis=0)
    
    outputs = normalize_model_outputs(outputs)

    auprc = average_precision_score(y_true=targets, y_score=outputs)
    auroc = roc_auc_score(targets, outputs)

    # print("This is the top 3 row:", outputs[0:3])
    outputs_F1 = np.array([[(1 if prob > thrs_weighted_F1 else 0) for prob in probs] for probs in outputs])
    weighted_f1_Tt = f1_score(targets, outputs_F1, average='weighted')

    outputs_hammingLoss = np.array([[(1 if prob > thrs_hammingLoss else 0) for prob in probs] for probs in outputs])
    hamming_Tt = hamming_loss(targets, outputs_hammingLoss)

    outputs_accuracy = np.array([[(1 if prob > thrs_accuracy else 0) for prob in probs] for probs in outputs])
    subset_accuracy_Tt = accuracy_score(targets, outputs_accuracy)

    # print("This is the best threshold for the challenge score, obtained from the training test, without any testing data leakage:", thrs_chall)
    outputs_best = np.array([[(1 if prob > thrs_chall else 0) for prob in probs] for probs in outputs])
    challenge_value_Tt = compute_challenge_metric(weights, targets, outputs_best, classes, sinus_rhythm_ID)
    # print("This is the challenge score:", challenge_value)

    # Marco F-1 score
    outputs_Macro_scores_F1 = np.array([[(1 if prob > thrs_Macro_scores_F1 else 0) for prob in probs] for probs in outputs])
    Macro_scores_F1_Tt = f1_score(targets, outputs_Macro_scores_F1, average='macro')

    # Macro specificity, this threshold is based on the Macro F1-score
    outputs_Macro_specificity = np.array([[(1 if prob > thrs_Macro_scores_F1 else 0) for prob in probs] for probs in outputs])
    Macro_specificity_Tt,_ = multilabel_specificity_score(targets, outputs_Macro_specificity)

    # Weighted specificity, this threshold is based on the Weighted F1-score
    outputs_Weighted_specificity = np.array([[(1 if prob > thrs_weighted_F1 else 0) for prob in probs] for probs in outputs])
    _, Weighted_specificity_Tt= multilabel_specificity_score(targets, outputs_Weighted_specificity)

    # Macro sensitivity, this threshold is based on the Macro F1-score
    outputs_Macro_sensitivity = np.array([[(1 if prob > thrs_Macro_scores_F1 else 0) for prob in probs] for probs in outputs])
    Macro_sensitivity_Tt = recall_score(targets, outputs_Macro_sensitivity, average='macro')

    # Weighted sensitivity, this threshold is based on the Weighted F1-score
    outputs_Weighted_sensitivity = np.array([[(1 if prob > thrs_weighted_F1 else 0) for prob in probs] for probs in outputs])
    Weighted_sensitivity_Tt = recall_score(targets, outputs_Weighted_sensitivity, average='weighted')

    # thrs_Macro_Precision, this threshold is based on the Macro F1-score
    outputs_Macro_Precision = np.array([[(1 if prob > thrs_Macro_scores_F1 else 0) for prob in probs] for probs in outputs])
    Macro_Precision_Tt = precision_score(targets, outputs_Macro_Precision, average='macro', zero_division=1.0)

    # thrs_Weighted_Precision, this threshold is based on the Weighted F1-score
    outputs_Weighted_Precision = np.array([[(1 if prob > thrs_weighted_F1 else 0) for prob in probs] for probs in outputs])
    Weighted_Precision_Tt = precision_score(targets, outputs_Weighted_Precision, average='weighted', zero_division=1.0)

    ### The 5 Best-Value Lists Based on the Six Evaluation Metrics
    scores_challengeScore_list = []

    scores_F1_list = []
    Macro_scores_F1_list = []


    scores_SubsetAccuracy_list = []
    scores_HammingLoss_list = []
    
    #### This is the best value achieved for the threshold-dependent evaluation metrics.
    for thr_b in np.arange(0., 1., 0.02):
        outputs_dyn = np.array([[(1 if prob > thr_b else 0) for prob in probs] for probs in outputs])

        challenge_value = compute_challenge_metric(weights, targets, outputs_dyn, classes, sinus_rhythm_ID)
        scores_challengeScore_list.append(challenge_value)

        ##### for the F1-score(weighted and Macro)
        f1 = f1_score(targets, outputs_dyn, average='weighted')
        scores_F1_list.append(f1)
        f1_macro = f1_score(targets, outputs_dyn, average='macro')
        Macro_scores_F1_list.append(f1_macro)

        subset_accuracy = accuracy_score(targets, outputs_dyn)
        scores_SubsetAccuracy_list.append(subset_accuracy)

        hamming = hamming_loss(targets, outputs_dyn)
        scores_HammingLoss_list.append(hamming)
    
    scores_challengeScore_list = np.array(scores_challengeScore_list)
    best_scores_challengeScore = np.max(scores_challengeScore_list)

    scores_F1_list = np.array(scores_F1_list)
    best_scores_F1 = np.max(scores_F1_list)

    Macro_scores_F1_list = np.array(Macro_scores_F1_list)
    best_Macro_scores_F1 = np.max(Macro_scores_F1_list)

    scores_SubsetAccuracy_list = np.array(scores_SubsetAccuracy_list)
    best_scores_SubsetAccuracy = np.max(scores_SubsetAccuracy_list)
    
    scores_HammingLoss_list = np.array(scores_HammingLoss_list)
    best_scores_HammingLoss = np.min(scores_HammingLoss_list)

    # gather the stats from all processes
    metric_logger.synchronize_between_processes()
    loss_value_after_each_epcoh /= batch_num

    return (auprc, auroc, weighted_f1_Tt, hamming_Tt, subset_accuracy_Tt, challenge_value_Tt, Macro_scores_F1_Tt,
            Macro_specificity_Tt, Weighted_specificity_Tt, Macro_sensitivity_Tt, Weighted_sensitivity_Tt, Macro_Precision_Tt,
            Weighted_Precision_Tt, best_scores_challengeScore, best_scores_F1, best_Macro_scores_F1, best_scores_SubsetAccuracy,
            best_scores_HammingLoss, loss_value_after_each_epcoh)