-
Notifications
You must be signed in to change notification settings - Fork 33
/
covid19_ai_diagnoser_optimal_model_architecture.py
312 lines (203 loc) · 10.9 KB
/
covid19_ai_diagnoser_optimal_model_architecture.py
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
#Original code: https://www.kaggle.com/gbellport/pneumonia-detection-96-recall-91-accuracy/output
#Code modified by Jordan Bennett , and converted from .pynb to .py using https://jupyter.org/try
######################
# Jordan's modifications :
#1. Pneumonia model training components removed. (Saved weight loading takes place instead)
#2. Covid19 training components added, atop pretrained model in (1).
#3. Replace pyplot -> plt.imread, with gray scale cv2.imread(path,0), so that ui's image load process doesn't throw a pyimage10 error
#4. Other pyplot related code removed.
#5. Confusion Matrix Format added, as quick explanation of Confusion Matrix that was already included.
#6. Specificty calculation and relevant label added.
#7. Many other modifications
# General libraries
import os
import numpy as np
import random
import cv2
# Deep learning libraries
import keras.backend as K
from keras.models import Model, Sequential
from keras.layers import Input, Dense, Flatten, Dropout, BatchNormalization
from keras.layers import Conv2D, SeparableConv2D, MaxPool2D, LeakyReLU, Activation
from keras.optimizers import Adam
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
import tensorflow as tf
#################################
#GLOBAL UTILITY
##Serves both Section B: "regular pneumonia..." and Section D: "covid19 pneumonia..." accuracy report sections.
# Hyperparameters
img_dims = 150
batch_size = 32
###########
#Util Component 1: Confusion matrix report/Accuracy measures
from sklearn.metrics import accuracy_score, confusion_matrix
#For graphical confusion metric
import matplotlib.pyplot as plt
import seaborn as sns
def renderConfusionMetrics ( ___model, _testData, _testLabels, enableTraining, ___train_gen, ___test_gen, __batch_size, __epochs, hdf5_testSaveFileName ):
preds = ___model.predict(_testData)
acc = accuracy_score(_testLabels, np.round(preds))*100
cm = confusion_matrix(_testLabels, np.round(preds))
tn, fp, fn, tp = cm.ravel()
plt.style.use("grayscale")
sns.heatmap(cm, annot=True, fmt='.0f', cmap='Blues_r')
plt.show()
print('\nCONFUSION MATRIX FORMAT ------------------\n')
print("[true positives false positives]")
print("[false negatives true negatives]\n\n")
print('CONFUSION MATRIX ------------------')
print(cm)
print('\nTEST METRICS ----------------------')
precision = tp/(tp+fp)*100
recall = tp/(tp+fn)*100
specificity = tn/(tn+fp)*100 #Jordan_note: added specificity calculation
print('Accuracy: {}%'.format(acc))
print('Precision: {}%'.format(precision))
print('Recall/Sensitivity: {}%'.format(recall)) #Jordan_note: added sensitivity label
print('Specificity {}%'.format(specificity)) #Jordan_note: added specificity calculation
print('F1-score: {}'.format(2*precision*recall/(precision+recall)))
if enableTraining:
checkpoint = ModelCheckpoint(filepath=hdf5_testSaveFileName, save_best_only=True, save_weights_only=True)
lr_reduce = ReduceLROnPlateau(monitor='val_loss', factor=0.3, patience=2, verbose=2, mode='max')
early_stop = EarlyStopping(monitor='val_loss', min_delta=0.1, patience=1, mode='min')
hist = ___model.fit_generator(
___train_gen, steps_per_epoch=___test_gen.samples // __batch_size,
epochs=__epochs, validation_data=___test_gen,
validation_steps=___test_gen.samples // __batch_size, callbacks=[checkpoint, lr_reduce])
print('\nTRAIN METRIC ----------------------')
print('Covid19 Train acc: {}'.format(np.round((hist.history['acc'][-1])*100, 2)))
###########
#Util Component 2:model architecture description
def defineModelArchitecture (_img_dims ):
# Input layer
inputs = Input(shape=(_img_dims, _img_dims, 3))
# First conv block
x = Conv2D(filters=16, kernel_size=(3, 3), activation='relu', padding='same')(inputs)
x = Conv2D(filters=16, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = MaxPool2D(pool_size=(2, 2))(x)
# Second conv block
x = SeparableConv2D(filters=32, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=32, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
# Third conv block
x = SeparableConv2D(filters=64, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=64, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
# Fourth conv block
x = SeparableConv2D(filters=128, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=128, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Dropout(rate=0.2)(x)
# Fifth conv block
x = SeparableConv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Dropout(rate=0.2)(x)
# FC layer
x = Flatten()(x)
x = Dense(units=512, activation='relu')(x)
x = Dropout(rate=0.7)(x)
x = Dense(units=128, activation='relu')(x)
x = Dropout(rate=0.5)(x)
x = Dense(units=64, activation='relu')(x)
x = Dropout(rate=0.3)(x)
# Output layer
output = Dense(units=1, activation='sigmoid')(x)
return inputs, output
###########
#Util Component 3: Data processor
#Note: This process does not use validation path, because validation path in the original competion reasonably had too little data (8 samples) to create any insight.
# the "directoryProcessArray" param from "reportFileDistributions" function corresponds to the hard-coded sub-directories of train and test, excluding val.
def process_data(___inputPath, img_dims, batch_size):
# Data generation objects
train_datagen = ImageDataGenerator(rescale=1./255, zoom_range=0.3, vertical_flip=True)
test_val_datagen = ImageDataGenerator(rescale=1./255)
# This is fed to the network in the specified batch sizes and image dimensions
train_gen = train_datagen.flow_from_directory(
directory=___inputPath+'train',
target_size=(img_dims, img_dims),
batch_size=batch_size,
class_mode='binary',
shuffle=True)
test_gen = test_val_datagen.flow_from_directory(
directory=___inputPath+'test',
target_size=(img_dims, img_dims),
batch_size=batch_size,
class_mode='binary',
shuffle=True)
# I will be making predictions off of the test set in one batch size
# This is useful to be able to get the confusion matrix
test_data = []
test_labels = []
for cond in ['/NORMAL/', '/PNEUMONIA/']:
for img in (os.listdir(___inputPath + 'test' + cond)):
img = cv2.imread(___inputPath+'test'+cond+img,0) #Replace plt.imread, with gray scale cv2.imread(path,0), so that ui's image load process doesn't throw a pyimage10 error
img = cv2.resize(img, (img_dims, img_dims))
img = np.dstack([img, img, img])
img = img.astype('float32') / 255
if cond=='/NORMAL/':
label = 0
elif cond=='/PNEUMONIA/':
label = 1
test_data.append(img)
test_labels.append(label)
test_data = np.array(test_data)
test_labels = np.array(test_labels)
return train_gen, test_gen, test_data, test_labels
###########
#Util Component 4: Report file distributions
#directoryProcessArray eg, = ['train', 'val', 'test'], in the case that training val and test folders exist in sub-dir for processing.
def reportFileDistributions (___inputPath, directoryProcessArray ):
for _set in directoryProcessArray:
n_normal = len(os.listdir(___inputPath + _set + '/NORMAL'))
n_infect = len(os.listdir(___inputPath + _set + '/PNEUMONIA'))
print('Set: {}, normal images: {}, illness-positive images: {}'.format(_set, n_normal, n_infect))
########################################################################
# disabling warnings
import logging
logging.getLogger('tensorflow').disabled = True #Jordan_note: Disable red warning lines seen at model architecture creation.
# Setting seeds for reproducibility
seed = 232
np.random.seed(seed)
tf.set_random_seed(seed)
########################################################################
#SECTION A: MODEL ARCHITECTURE NON-COVID19 PNEUMONIA DETECTOR
inputs, output = defineModelArchitecture ( img_dims )
# Creating model and compiling
model_pneumoniaDetector = Model(inputs=inputs, outputs=output)
model_pneumoniaDetector.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model_pneumoniaDetector.load_weights('best_weights_kaggle_user_pneumonia2_0.hdf5')
########################################################################
#SECTION B: NON-COVID19 PNEUMONIA VS NORMAL LUNG ACCURACY REPORT [LOADED MODEL/WEIGHTS]
print('\n\n#######TRAINED NON-COVID19 PNEUMONIA VS NORMAL LUNG TEST REPORT [LOADED MODEL/WEIGHTS]')
# Lets first look at some of our X-ray images and how each dataset is distributed:
input_path_b = 'xray_dataset/'
# Report file distributions
reportFileDistributions (input_path_b, ['train', 'val', 'test'] )
# Getting the data
train_gen, test_gen, test_data_b, test_labels_b = process_data(input_path_b, img_dims, batch_size)
# Reporting on accuracies
renderConfusionMetrics ( model_pneumoniaDetector, test_data_b, test_labels_b, False, None, None, None, None, None )
########################################################################
#SECTION C: MODEL ARCHITECTURE COVID19 DETECTOR
inputs, output = defineModelArchitecture ( img_dims )
# Creating model and compiling
model_covid19PneumoniaDetector = Model(inputs=inputs, outputs=output)
model_covid19PneumoniaDetector.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model_covid19PneumoniaDetector.load_weights('covid19_neural_network_weights_jordan.hdf5')
###################################################################
#SECTION D: COVID19 PNEUMONIA VS NORMAL LUNG ACCURACY REPORT [LOADED MODEL/WEIGHTS]
print('\n\n#######TRAINED COVID19 PNEUMONIA VS NORMAL LUNG TEST REPORT [LOADED MODEL/WEIGHTS]')
#Jordan_note establish custom_path for covid 19 test data
input_path_d = 'xray_dataset_covid19/'
# Report file distributions
reportFileDistributions (input_path_d, ['train', 'test'])
# Getting the data
train_gen_d, test_gen_d, test_data_d, test_labels_d = process_data(input_path_d, img_dims, batch_size)
# Reporting on accuracies
renderConfusionMetrics ( model_covid19PneumoniaDetector, test_data_d, test_labels_d, False, train_gen_d, test_gen_d, batch_size, 11, 'covid19_neural_network_weights_jordan_v2.hdf5' )