Diff of /RSNA_ResNet50_model.py [000000] .. [638a85]

Switch to unified view

a b/RSNA_ResNet50_model.py
1
#!/usr/bin/env python
2
# coding: utf-8
3
4
# ## ResNet50 model 
5
# 
6
# **Due to GPU quota is only 30 hours/per week on Kaggle, each training need 15+ hours, so the notebook cann't commiting(otherwise will exceeding the quota), only download the csv files to submit**
7
# 
8
# 
9
10
11
import numpy as np
12
import pandas as pd
13
import pydicom
14
import os
15
import matplotlib.pyplot as plt
16
import collections
17
from tqdm import tqdm_notebook as tqdm
18
from datetime import datetime
19
20
from math import ceil, floor, log
21
import cv2
22
23
import tensorflow as tf
24
import keras
25
26
import sys
27
28
from keras_applications.resnet import ResNet50
29
30
from sklearn.model_selection import ShuffleSplit
31
32
33
# ### Model Parameters Setup
34
35
# Paths 
36
Path = '../input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection/'
37
train_img_path = Path + 'stage_2_train/'
38
test_img_path = Path + 'stage_2_test/'
39
sample_csv = Path + "stage_2_sample_submission.csv"
40
train_csv = Path + 'stage_2_train.csv'
41
42
43
def read_testset(filename):
44
    df = pd.read_csv(filename)
45
    df["Image"] = df["ID"].str.slice(stop=12)
46
    df["Diagnosis"] = df["ID"].str.slice(start=13)
47
    
48
    df = df.loc[:, ["Label", "Diagnosis", "Image"]]
49
    df = df.set_index(['Image', 'Diagnosis']).unstack(level=-1)
50
    
51
    return df
52
53
54
55
def read_trainset(filename = Path + "stage_2_train.csv"):
56
    df = pd.read_csv(filename)
57
    df["Image"] = df["ID"].str.slice(stop=12)
58
    df["Diagnosis"] = df["ID"].str.slice(start=13)
59
    duplicates_to_remove = [56346, 56347, 56348, 56349,
60
                            56350, 56351, 1171830, 1171831,
61
                            1171832, 1171833, 1171834, 1171835,
62
                            3705312, 3705313, 3705314, 3705315,
63
                            3705316, 3705317, 3842478, 3842479,
64
                            3842480, 3842481, 3842482, 3842483 ]
65
    df = df.drop(index = duplicates_to_remove)
66
    df = df.reset_index(drop = True)    
67
    df = df.loc[:, ["Label", "Diagnosis", "Image"]]
68
    df = df.set_index(['Image', 'Diagnosis']).unstack(level=-1)
69
    return df
70
71
72
    
73
test_df = read_testset(sample_csv)
74
train_df = read_trainset(train_csv)
75
76
77
# ### Data EDA and Cleaning
78
79
def correct_dcm(dcm):
80
    x = dcm.pixel_array + 1000
81
    px_mode = 4096
82
    x[x>=px_mode] = x[x>=px_mode] - px_mode
83
    dcm.PixelData = x.tobytes()
84
    dcm.RescaleIntercept = -1000
85
86
def window_image(dcm, window_center, window_width):
87
    
88
    if (dcm.BitsStored == 12) and (dcm.PixelRepresentation == 0) and (int(dcm.RescaleIntercept) > -1000):
89
        correct_dcm(dcm)
90
    
91
    img = dcm.pixel_array * dcm.RescaleSlope + dcm.RescaleIntercept
92
    img_min = window_center - window_width // 2
93
    img_max = window_center + window_width // 2
94
    img = np.clip(img, img_min, img_max)
95
96
    return img
97
98
def bsb_window(dcm):
99
    brain_img = window_image(dcm, 40, 80)
100
    subdural_img = window_image(dcm, 80, 200)
101
    soft_img = window_image(dcm, 40, 380)
102
    
103
    brain_img = (brain_img - 0) / 80
104
    subdural_img = (subdural_img - (-20)) / 200
105
    soft_img = (soft_img - (-150)) / 380
106
    bsb_img = np.array([brain_img, subdural_img, soft_img]).transpose(1,2,0)
107
108
    return bsb_img
109
110
111
def window_with_correction(dcm, window_center, window_width):
112
    if (dcm.BitsStored == 12) and (dcm.PixelRepresentation == 0) and (int(dcm.RescaleIntercept) > -100):
113
        correct_dcm(dcm)
114
    img = dcm.pixel_array * dcm.RescaleSlope + dcm.RescaleIntercept
115
    img_min = window_center - window_width // 2
116
    img_max = window_center + window_width // 2
117
    img = np.clip(img, img_min, img_max)
118
    return img
119
120
def window_without_correction(dcm, window_center, window_width):
121
    img = dcm.pixel_array * dcm.RescaleSlope + dcm.RescaleIntercept
122
    img_min = window_center - window_width // 2
123
    img_max = window_center + window_width // 2
124
    img = np.clip(img, img_min, img_max)
125
    return img
126
127
def window_testing(img, window):
128
    brain_img = window(img, 40, 80)
129
    subdural_img = window(img, 80, 200)
130
    soft_img = window(img, 40, 380)
131
    
132
    brain_img = (brain_img - 0) / 80
133
    subdural_img = (subdural_img - (-20)) / 200
134
    soft_img = (soft_img - (-150)) / 380
135
    bsb_img = np.array([brain_img, subdural_img, soft_img]).transpose(1,2,0)
136
137
    return bsb_img
138
139
140
# example of a "bad data point" (i.e. (dcm.BitsStored == 12) and (dcm.PixelRepresentation == 0) and (int(dcm.RescaleIntercept) > -100) == True)
141
example_img = train_img_path + train_df.index[102] + ".dcm"
142
143
dicom = pydicom.dcmread(example_img)
144
145
fig, ax = plt.subplots(1, 2)
146
147
ax[0].imshow(window_testing(dicom, window_without_correction), cmap=plt.cm.bone);
148
ax[0].set_title("original")
149
ax[1].imshow(window_testing(dicom, window_with_correction), cmap=plt.cm.bone);
150
ax[1].set_title("corrected");
151
152
153
# ### Load Data
154
155
156
def _read(path, desired_size):
157
    """Will be used in DataGenerator"""
158
    
159
    dcm = pydicom.dcmread(path)
160
    
161
    try:
162
        img = bsb_window(dcm)
163
    except:
164
        img = np.zeros(desired_size)
165
    
166
    
167
    img = cv2.resize(img, desired_size[:2], interpolation=cv2.INTER_LINEAR)
168
    
169
    return img
170
171
172
# ### Data generators
173
# 
174
# Inherits from keras.utils.Sequence object and thus should be safe for multiprocessing.
175
# 
176
177
178
class DataGenerator(keras.utils.Sequence):
179
180
    def __init__(self, list_IDs, labels=None, batch_size=1, img_size=(512, 512, 1), 
181
                 img_dir=train_img_path, *args, **kwargs):
182
183
        self.list_IDs = list_IDs
184
        self.labels = labels
185
        self.batch_size = batch_size
186
        self.img_size = img_size
187
        self.img_dir = img_dir
188
        self.on_epoch_end()
189
190
    def __len__(self):
191
        return int(ceil(len(self.indices) / self.batch_size))
192
193
    def __getitem__(self, index):
194
        indices = self.indices[index*self.batch_size:(index+1)*self.batch_size]
195
        list_IDs_temp = [self.list_IDs[k] for k in indices]
196
        
197
        if self.labels is not None:
198
            X, Y = self.__data_generation(list_IDs_temp)
199
            return X, Y
200
        else:
201
            X = self.__data_generation(list_IDs_temp)
202
            return X
203
        
204
    def on_epoch_end(self):
205
        
206
        
207
        if self.labels is not None: # for training phase we undersample and shuffle
208
            # keep probability of any=0 and any=1
209
            keep_prob = self.labels.iloc[:, 0].map({0: 0.35, 1: 0.5})
210
            keep = (keep_prob > np.random.rand(len(keep_prob)))
211
            self.indices = np.arange(len(self.list_IDs))[keep]
212
            np.random.shuffle(self.indices)
213
        else:
214
            self.indices = np.arange(len(self.list_IDs))
215
216
    def __data_generation(self, list_IDs_temp):
217
        X = np.empty((self.batch_size, *self.img_size))
218
        
219
        if self.labels is not None: # training phase
220
            Y = np.empty((self.batch_size, 6), dtype=np.float32)
221
        
222
            for i, ID in enumerate(list_IDs_temp):
223
                X[i,] = _read(self.img_dir+ID+".dcm", self.img_size)
224
                Y[i,] = self.labels.loc[ID].values
225
        
226
            return X, Y
227
        
228
        else: # test phase
229
            for i, ID in enumerate(list_IDs_temp):
230
                X[i,] = _read(self.img_dir+ID+".dcm", self.img_size)
231
            
232
            return X
233
234
235
# ### Metrics
236
237
from keras import backend as K
238
239
def weighted_log_loss(y_true, y_pred):
240
    """
241
    Can be used as the loss function in model.compile()
242
    ---------------------------------------------------
243
    """
244
    
245
    class_weights = np.array([2., 1., 1., 1., 1., 1.])
246
    
247
    eps = K.epsilon()
248
    
249
    y_pred = K.clip(y_pred, eps, 1.0-eps)
250
251
    out = -(         y_true  * K.log(      y_pred) * class_weights
252
            + (1.0 - y_true) * K.log(1.0 - y_pred) * class_weights)
253
    
254
    return K.mean(out, axis=-1)
255
256
257
def _normalized_weighted_average(arr, weights=None):
258
    """
259
    A simple Keras implementation that mimics that of 
260
    numpy.average(), specifically for this competition
261
    """
262
    
263
    if weights is not None:
264
        scl = K.sum(weights)
265
        weights = K.expand_dims(weights, axis=1)
266
        return K.sum(K.dot(arr, weights), axis=1) / scl
267
    return K.mean(arr, axis=1)
268
269
270
def weighted_loss(y_true, y_pred):
271
    """
272
    Will be used as the metric in model.compile()
273
    ---------------------------------------------
274
    
275
    Similar to the custom loss function 'weighted_log_loss()' above
276
    but with normalized weights, which should be very similar 
277
    to the official competition metric:
278
        https://www.kaggle.com/kambarakun/lb-probe-weights-n-of-positives-scoring
279
    and hence:
280
        sklearn.metrics.log_loss with sample weights
281
    """
282
    
283
    class_weights = K.variable([2., 1., 1., 1., 1., 1.])
284
    
285
    eps = K.epsilon()
286
    
287
    y_pred = K.clip(y_pred, eps, 1.0-eps)
288
289
    loss = -(        y_true  * K.log(      y_pred)
290
            + (1.0 - y_true) * K.log(1.0 - y_pred))
291
    
292
    loss_samples = _normalized_weighted_average(loss, class_weights)
293
    
294
    return K.mean(loss_samples)
295
296
297
def weighted_log_loss_metric(trues, preds):
298
    """
299
    Will be used to calculate the log loss 
300
    of the validation set in PredictionCheckpoint()
301
    ------------------------------------------
302
    """
303
    class_weights = [2., 1., 1., 1., 1., 1.]
304
    
305
    epsilon = 1e-7
306
    
307
    preds = np.clip(preds, epsilon, 1-epsilon)
308
    loss = trues * np.log(preds) + (1 - trues) * np.log(1 - preds)
309
    loss_samples = np.average(loss, axis=1, weights=class_weights)
310
311
    return - loss_samples.mean()
312
313
314
# ###  Model
315
# 
316
# 
317
318
319
class PredictionCheckpoint(keras.callbacks.Callback):
320
    
321
    def __init__(self, test_df, valid_df, 
322
                 test_images_dir=test_img_path, 
323
                 valid_images_dir=train_img_path, 
324
                 batch_size=32, input_size=(224, 224, 3)):
325
        
326
        self.test_df = test_df
327
        self.valid_df = valid_df
328
        self.test_images_dir = test_images_dir
329
        self.valid_images_dir = valid_images_dir
330
        self.batch_size = batch_size
331
        self.input_size = input_size
332
        
333
    def on_train_begin(self, logs={}):
334
        self.test_predictions = []
335
        self.valid_predictions = []
336
        
337
    def on_epoch_end(self,batch, logs={}):
338
        self.test_predictions.append(
339
            self.model.predict_generator(
340
                DataGenerator(self.test_df.index, None, self.batch_size, self.input_size, self.test_images_dir), verbose=2)[:len(self.test_df)])
341
 
342
343
344
class ResNet50_Model:
345
    
346
    def __init__(self, engine, input_dims, batch_size=5, num_epochs=4, learning_rate=1e-3, 
347
                 decay_rate=1e-6, decay_steps=1, weights="imagenet", verbose=1):
348
        
349
        self.engine = engine
350
        self.input_dims = input_dims
351
        self.batch_size = batch_size
352
        self.num_epochs = num_epochs
353
        self.learning_rate = learning_rate
354
        self.decay_rate = decay_rate
355
        self.decay_steps = decay_steps
356
        self.weights = weights
357
        self.verbose = verbose
358
        self._build()
359
360
    def _build(self):
361
        
362
        
363
        engine = self.engine(include_top=False, weights=self.weights, input_shape=self.input_dims,
364
                             backend = keras.backend, layers = keras.layers,
365
                             models = keras.models, utils = keras.utils)
366
        
367
        x = keras.layers.GlobalAveragePooling2D(name='avg_pool')(engine.output)
368
        out = keras.layers.Dense(6, activation="sigmoid", name='dense_output')(x)
369
370
        self.model = keras.models.Model(inputs=engine.input, outputs=out)
371
372
        self.model.compile(loss="binary_crossentropy", optimizer=keras.optimizers.Adam(), metrics=[weighted_loss])
373
    
374
375
    def fit_and_predict(self, train_df, valid_df, test_df):
376
        
377
        # callbacks
378
        pred_history = PredictionCheckpoint(test_df, valid_df, input_size=self.input_dims)
379
        scheduler = keras.callbacks.LearningRateScheduler(lambda epoch: self.learning_rate * pow(self.decay_rate, floor(epoch / self.decay_steps)))
380
        
381
        self.model.fit_generator(
382
            DataGenerator(
383
                train_df.index, 
384
                train_df, 
385
                self.batch_size, 
386
                self.input_dims, 
387
                train_img_path
388
            ),
389
            epochs=self.num_epochs,
390
            verbose=self.verbose,
391
            use_multiprocessing=True,
392
            workers=4,
393
            callbacks=[pred_history, scheduler]
394
        )
395
        
396
        return pred_history
397
    
398
    def save(self, path):
399
        self.model.save_weights(path)
400
    
401
    def load(self, path):
402
        self.model.load_weights(path)
403
404
405
# ### Train model and predict
406
# 
407
# 
408
# 
409
410
411
# Train/Valid/Test Split
412
train_df_ss = ShuffleSplit(n_splits=10, test_size=0.1, random_state=42).split(train_df.index)
413
414
415
train_idx, valid_idx = next(train_df_ss)
416
417
# model
418
model = ResNet50_Model(engine=ResNet50, input_dims=(224, 224, 3), batch_size=32, learning_rate=5e-4,
419
                    num_epochs=5, decay_rate=0.8, decay_steps=1, weights="imagenet", verbose=1)
420
421
#predictions 
422
history = model.fit_and_predict(train_df.iloc[train_idx], train_df.iloc[valid_idx], test_df)
423
424
425
# ### Ensemble and average all submission_predictions.
426
427
test_df.iloc[:, :] = np.average(history.test_predictions, axis=0, weights=[0, 1, 2, 4, 6]) # let's do a weighted average for epochs (>1)
428
test_df = test_df.stack().reset_index()
429
test_df.insert(loc=0, column='ID', value=test_df['Image'].astype(str) + "_" + test_df['Diagnosis'])
430
test_df = test_df.drop(["Image", "Diagnosis"], axis=1)
431
test_df.to_csv('submission.csv', index=False)
432
433
434
from IPython.display import FileLink, FileLinks
435
FileLink('submission.csv')
436
437
438
439
440
441