Chris Malec

Data Scientist

Previous: Baseline Model

Previous: Baseline Model

#Import necessary packages
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from itertools import product
from tensorflow.python.keras import layers
from tensorflow.python.keras import optimizers
from tensorflow.python.keras import losses
from tensorflow.python.keras import models
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from scipy.ndimage.interpolation import map_coordinates
from scipy.ndimage.filters import gaussian_filter
#Import data files
fullsize_training_inputs = np.load('training_inputs.npy')
fullsize_training_ground_truth = np.load('training_ground_truth.npy')
fullsize_testing_inputs = np.load('testing_inputs.npy')
fullsize_testing_ground_truth = np.load('testing_ground_truth.npy')
#Dice the tensor into smaller, square images
def stack_tensor(tensor,num_stack_H,num_stack_W,rotate=False):
    '''
    divides tensor of size (N,H,W,C) into more instances of smaller images (N*n^2,H/n,W/n,C)
    tensor: A four dimensional tensor object or numpy array
    num_stack: number of times to divide width and height
    '''
    #unpack values
    N, H, W, C = tensor.shape
    new_N = N*(num_stack_H*num_stack_W)
    new_H = H//num_stack_H
    new_W = W//num_stack_W
    image_segments = product(range(num_stack_H), range(num_stack_W))
    stacked_tensor = np.empty((new_N,new_H,new_W,C))
    if rotate == True:
        stacked_tensor = np.empty((new_N,new_W,new_H,C))
        tensor = np.swapaxes(tensor,1,2)
        image_segments = product(range(num_stack_W), range(num_stack_H))
        new_H, new_W = new_W, new_H
    new_n=0
    for r, c in image_segments:
        for n in range(N):
            stacked_tensor[new_n,:,:,:] = tensor[n, r*new_H:(r+1)*new_H,c*new_W:(c+1)*new_W,:]
            new_n += 1
    return stacked_tensor

#subtract a mean image to center the data
def preprocess_tensor(tensor):
    '''
    Applies common pre-processing transformations and returns the necessary constants 
    to apply the same transforms to validation and test data
    '''
    tensor_mean = np.mean(tensor,axis=0,keepdims=True)
    preprocessed_tensor = tensor - tensor_mean
    return preprocessed_tensor, tensor_mean
#Apply dicing function and delete fullsize images to save memory
training_inputs = stack_tensor(fullsize_training_inputs,3,4)/255
training_ground_truth = stack_tensor(fullsize_training_ground_truth,3,4)/255
testing_inputs = stack_tensor(fullsize_testing_inputs,3,4)/255
testing_ground_truth = stack_tensor(fullsize_testing_ground_truth,3,4)/255
input_shape = training_inputs.shape

del fullsize_training_inputs
del fullsize_training_ground_truth
del fullsize_testing_inputs
del fullsize_testing_ground_truth
seed = 0
alpha = 10
sigma = 10
global image_seed
image_seed = 0
#Define some additional transormations
def elastic_transform(image):
    #alpha, sigma, random_state must be passed as global variables
    random_state = np.random.RandomState(image_seed)
    shape = image.shape
    dx = gaussian_filter((random_state.rand(*shape[:2])*2 - 1), sigma, mode = "constant",cval = 0)*alpha
    dy = gaussian_filter((random_state.rand(*shape[:2])*2 - 1), sigma, mode = "constant",cval = 0)*alpha
    
    x,y = np.meshgrid(np.arange(int(shape[1])),np.arange(int(shape[0])))
    indices = np.reshape(y+dy,(-1,1)), np.reshape(x+dx,(-1,1))
    
    transformed_image = np.empty(shape)
    for c in range(shape[2]):
        mapped_image = map_coordinates(image[:,:,c],indices, order=1,mode='reflect')
        transformed_image[:,:,c] = mapped_image.reshape((shape[0],shape[1]))
    return transformed_image

def random_noise(image):
    #add random_noise to the images
    #seed must be passed as a global variable
    random_state = np.random.RandomState(image_seed)
    noise_mag = random_state.uniform(low=0.01,high=0.1)
    noise_width = random_state.uniform(low = 0.01, high = 0.5)
    noise = noise_mag*random_state.normal(scale=noise_width,
                                size=image.shape
                            )
    transformed_image = image+noise
    return transformed_image

def additional_image_transformations(image):
    #randomly add noise and a deformation
    image_seed = np.random.choice(10000)
    random_state = np.random.RandomState(image_seed)
    transformed_image = image
    alpha = random_state.uniform(low=50,high=250)
    sigma = random_state.uniform(low=10,high=20)
    if random_state.choice(2) == 1:
        transformed_image = random_noise(transformed_image)
    if random_state.choice(2) == 1:
        transformed_image = elastic_transform(transformed_image)
    return transformed_image

def additional_mask_transformations(image):
    #randomly add a deformation
    random_state = np.random.RandomState(image_seed)
    transformed_image = image
    alpha = random_state.uniform(low=50,high=250)
    sigma = random_state.uniform(low=10,high=20)
    if random_state.choice(2) == 1:
        pass
    if random_state.choice(2) == 1:
        transformed_image = elastic_transform(image)
    return transformed_image
# we create two instances with the same arguments
mask_gen_args = {'rotation_range':90,
                 'width_shift_range':0.05,
                 'height_shift_range':0.05,
                 'zoom_range':0.1,
                 'fill_mode':'constant',
                 'cval':0,
                 'horizontal_flip':True,
                 'vertical_flip':True,
                 'preprocessing_function':additional_mask_transformations
                }

image_gen_args = mask_gen_args.copy()
#image_gen_args['preprocessing_function'] = additional_image_transformations
def conv_block(input_tensor, num_filters):
    encoder = layers.Conv2D(filters=num_filters,kernel_size=(3, 3),padding='same')(input_tensor)
    encoder = layers.BatchNormalization()(encoder)
    encoder = layers.Activation('relu')(encoder)
    
    return encoder

def encoder_block(input_tensor, num_filters):
    encoder = conv_block(input_tensor, num_filters)
    encoder_pool = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(encoder)
  
    return encoder_pool, encoder

def decoder_block(input_tensor, concat_tensor, num_filters):
    decoder = layers.Conv2DTranspose(filters=num_filters,kernel_size=(2, 2),strides=(2, 2),padding='same')(input_tensor)
    decoder = layers.concatenate([concat_tensor, decoder], axis=-1)
    decoder = layers.BatchNormalization()(decoder)
    decoder = layers.Activation('relu')(decoder)
    decoder = layers.Conv2D(filters=num_filters,kernel_size=(3, 3),padding='same')(decoder)
    decoder = layers.BatchNormalization()(decoder)
    decoder = layers.Activation('relu')(decoder)

    return decoder
inputs = layers.Input(shape = training_inputs.shape[1:])

encoder0_pool, encoder0 = encoder_block(inputs, 32)

encoder1_pool, encoder1 = encoder_block(encoder0_pool, 64)

encoder2_pool, encoder2 = encoder_block(encoder1_pool, 128)

encoder3_pool, encoder3 = encoder_block(encoder2_pool, 256)

encoder4_pool, encoder4 = encoder_block(encoder3_pool, 512)

center = conv_block(encoder4_pool,1024)

decoder4 = decoder_block(center, encoder4, 512)

decoder3 = decoder_block(decoder4, encoder3, 256)

decoder2 = decoder_block(decoder3, encoder2, 128)

decoder1 = decoder_block(decoder2, encoder1, 64)

decoder0 = decoder_block(decoder1, encoder0, 32)

outputs = layers.Conv2D(1, (1, 1), activation='sigmoid')(decoder0)
model = models.Model(inputs=[inputs], outputs=[outputs])
model.summary()
==================================================================================================
Total params: 15,388,385
Trainable params: 15,378,401
Non-trainable params: 9,984
__________________________________________________________________________________________________
from tensorflow.keras.utils import plot_model

plot_model(model,to_file = 'model_plot.png',rankdir='TB')

png

def dice_coeff(y_true, y_pred):
    smooth = 1.
    # Flatten
    y_true_f = tf.reshape(y_true, [-1])
    y_pred_f = tf.reshape(y_pred, [-1])
    intersection = tf.reduce_sum(y_true_f * y_pred_f)
    score = (2. * intersection) / (tf.reduce_sum(y_true_f) + tf.reduce_sum(y_pred_f) + smooth)
    return score

def dice_loss(y_true, y_pred):
    loss = 1 - dice_coeff(y_true, y_pred)
    return loss

def log_dice_loss(y_true, y_pred):
    loss = tf.log(1 - dice_coeff(y_true, y_pred))
    return loss

def bce_loss(y_true,y_pred):
    bce_loss = losses.binary_crossentropy(y_true,y_pred)
    return bce_loss
nonzero_inds = []
for i in range(training_ground_truth.shape[0]):
    if training_ground_truth[i,:].sum() != 0:
        nonzero_inds.append(i)
cleaned_inputs = training_inputs[nonzero_inds,:,:,:]
cleaned_ground_truth = training_ground_truth[nonzero_inds,:,:,:]
cleaned_inputs, train_mean = preprocess_tensor(cleaned_inputs)
val_inds = np.random.choice(testing_inputs.shape[0],100)
validation_inputs = testing_inputs[val_inds,:]
validation_ground_truth = testing_ground_truth[val_inds,:]
validation_inputs -= train_mean
batch_size = 15
seed = 135
#Instantiate Data Generators
image_datagen = ImageDataGenerator(**image_gen_args)
mask_datagen = ImageDataGenerator(**mask_gen_args)

train_image_generator = image_datagen.flow(
    cleaned_inputs,
    batch_size=batch_size,
    seed=seed)

train_mask_generator = mask_datagen.flow(
    cleaned_ground_truth,
    batch_size=batch_size,
    seed=seed)

train_generator = (pair for pair in zip(train_image_generator,train_mask_generator))
#Initiate Adam optimizer with default values
opt = optimizers.Adam(lr=1e-6,beta_1=0.8,beta_2=0.99)

#Compile model
model.compile(optimizer=opt, 
              loss=log_dice_loss, 
              metrics=[dice_coeff])
model.load_weights('tmp/weights_final.hdf5')
#Some parameters

epochs = 60

#Define a model callback
save_model_path = 'tmp/weights_final.hdf5'
cp = tf.keras.callbacks.ModelCheckpoint(filepath=save_model_path, 
                                        monitor='val_loss',
                                        save_best_only=True, 
                                        verbose=1
                                       )
'''
No augmentation
default lr 1e-3 
batch_size = 15 
loss function bce 
normalization_method=batch
model_depth = 5 maxpool layers, 
filter_size = 3x3
01: True 5x5 filter
02: Effective 5x5 filter
03: 16 filter layer at beginning end
04: 2048 filter layer at center
05: Layer normalization
06: No normalization
07: Log Dice loss
08: Batch size 10
09: Batch size 5
10: eps 1e-4
11: eps 1e-1
12: lr 1e-1
13: lr 1e-5
14: Batch size = 20
15: Batch size = 20
16: Deeper network
17: Deeper network, Batch size 10, log dice loss, lr=1e-3,eps=1e-4
'''
#Fit the model
history3 = model.fit_generator(train_generator,
                              epochs=epochs,
                              steps_per_epoch = cleaned_inputs.shape[0]//batch_size,
                              validation_data=(validation_inputs,validation_ground_truth),
                              shuffle=True,
                              callbacks=[cp],
                              initial_epoch = 50
                             )
Epoch 51/60
108/109 [============================>.] - ETA: 32s - loss: -2.7314 - dice_coeff: 0.9341 
Epoch 00051: val_loss improved from inf to -2.60830, saving model to tmp/weights_final.hdf5
109/109 [==============================] - 3629s 33s/step - loss: -2.7312 - dice_coeff: 0.9341 - val_loss: -2.6083 - val_dice_coeff: 0.9245
Epoch 52/60
108/109 [============================>.] - ETA: 32s - loss: -2.7245 - dice_coeff: 0.9332 
Epoch 00052: val_loss improved from -2.60830 to -2.60953, saving model to tmp/weights_final.hdf5
109/109 [==============================] - 3532s 32s/step - loss: -2.7253 - dice_coeff: 0.9333 - val_loss: -2.6095 - val_dice_coeff: 0.9245
Epoch 53/60
108/109 [============================>.] - ETA: 32s - loss: -2.7228 - dice_coeff: 0.9334 
Epoch 00053: val_loss did not improve from -2.60953
109/109 [==============================] - 3573s 33s/step - loss: -2.7230 - dice_coeff: 0.9334 - val_loss: -2.6084 - val_dice_coeff: 0.9245
Epoch 54/60
108/109 [============================>.] - ETA: 32s - loss: -2.7363 - dice_coeff: 0.9336 
Epoch 00054: val_loss did not improve from -2.60953
109/109 [==============================] - 3592s 33s/step - loss: -2.7353 - dice_coeff: 0.9336 - val_loss: -2.6085 - val_dice_coeff: 0.9245
Epoch 55/60
108/109 [============================>.] - ETA: 32s - loss: -2.7240 - dice_coeff: 0.9336 
Epoch 00055: val_loss improved from -2.60953 to -2.61043, saving model to tmp/weights_final.hdf5
109/109 [==============================] - 3599s 33s/step - loss: -2.7214 - dice_coeff: 0.9334 - val_loss: -2.6104 - val_dice_coeff: 0.9246
Epoch 56/60
108/109 [============================>.] - ETA: 32s - loss: -2.7385 - dice_coeff: 0.9344 
Epoch 00056: val_loss did not improve from -2.61043
109/109 [==============================] - 3558s 33s/step - loss: -2.7401 - dice_coeff: 0.9345 - val_loss: -2.6086 - val_dice_coeff: 0.9245
Epoch 57/60
108/109 [============================>.] - ETA: 32s - loss: -2.7446 - dice_coeff: 0.9351 
Epoch 00057: val_loss did not improve from -2.61043
109/109 [==============================] - 3563s 33s/step - loss: -2.7421 - dice_coeff: 0.9349 - val_loss: -2.6073 - val_dice_coeff: 0.9244
Epoch 58/60
108/109 [============================>.] - ETA: 32s - loss: -2.7234 - dice_coeff: 0.9337 
Epoch 00058: val_loss did not improve from -2.61043
109/109 [==============================] - 3575s 33s/step - loss: -2.7241 - dice_coeff: 0.9337 - val_loss: -2.6093 - val_dice_coeff: 0.9245
Epoch 59/60
108/109 [============================>.] - ETA: 32s - loss: -2.7493 - dice_coeff: 0.9349 
Epoch 00059: val_loss improved from -2.61043 to -2.61064, saving model to tmp/weights_final.hdf5
109/109 [==============================] - 3592s 33s/step - loss: -2.7480 - dice_coeff: 0.9349 - val_loss: -2.6106 - val_dice_coeff: 0.9246
Epoch 60/60
108/109 [============================>.] - ETA: 32s - loss: -2.6988 - dice_coeff: 0.9316 
Epoch 00060: val_loss did not improve from -2.61064
109/109 [==============================] - 3529s 32s/step - loss: -2.7011 - dice_coeff: 0.9318 - val_loss: -2.6088 - val_dice_coeff: 0.9245
tf.keras.models.save_model(model,'tmp/model_final.hdf5')
#Load best model
model.load_weights('tmp/weights_final.hdf5')
# Score trained model.
possible_test_inds = [i for i in range(testing_inputs.shape[0]) if i not in val_inds]
test_inds = np.random.choice(possible_test_inds,100,replace=False)
scores = model.evaluate(testing_inputs[test_inds,:,:,:] - train_mean, 
                        testing_ground_truth[test_inds,:,:,:], 
                        verbose=1)

print('Test loss:', scores)
100/100 [==============================] - 14s 137ms/sample - loss: -2.3179 - dice_coeff: 0.9121
Test loss: [-2.3179107666015626, 0.9120971]
index = 80
_,H,W,_ = testing_inputs.shape
image = testing_inputs[test_inds[index],:,:,0]
predict_mask = np.zeros(shape=(H,W,4))
test_mask = np.zeros(shape=(H,W,4))
oops_mask = np.zeros(shape=(H,W,4))

predict_mask[:,:,0] = model.predict(testing_inputs[test_inds,:,:,:]-train_mean)[index][:,:,0]
predict_mask[:,:,3] = 0.2*(predict_mask[:,:,0]>0.)

test_mask[:,:,0] = testing_ground_truth[test_inds[index],:,:,0]
test_mask[test_mask>1] = 1
test_mask[:,:,3] = 0.2*(test_mask[:,:,0]>0.)


oops_mask[:,:,0] = 0.5*(predict_mask[:,:,0] - test_mask[:,:,0])+0.5
oops_mask[:,:,3] = 0.4*((oops_mask[:,:,0]>0.51))+0.4*(oops_mask[:,:,0]<0.49)

fig = plt.figure(figsize=[12,7])
ax1 = plt.subplot(2,3,1)
ax1.imshow(test_mask[:,:,0],cmap='PuRd')
ax1.set_title('Ground Truth')

ax2 = plt.subplot(2,3,2)
ax2.imshow(predict_mask[:,:,0],cmap='PuRd')
ax2.set_title('Predicted Truth')

ax3 = plt.subplot(2,3,3)
ax3.imshow(oops_mask[:,:,0],cmap='PuRd')
ax3.set_title('Misclassified Areas')

ax4 = plt.subplot(2,3,4)
ax4.imshow(image,cmap = 'gray',label='real_image')
ax4.imshow(test_mask,cmap='Reds')

ax5 = plt.subplot(2,3,5)
ax5.imshow(image,cmap = 'gray',label='predicted_image')
ax5.imshow(predict_mask,cmap='Reds')

ax6 = plt.subplot(2,3,6)
ax6.imshow(image,cmap = 'gray',label='mistakes_image')
ax6.imshow(oops_mask,cmap='PuRd')

plt.show()

png

def predict_on_fullsize_image(model,image,stride,width=256,train_mean = 0):
    H, W, Ch = image.shape
    R = (H - width)//stride+1
    C = (W - width)//stride+1
    N = R*C
    predict_stack = np.empty(shape=(N,width,width,Ch))
    image_segments = product(range(R), range(C))
    n=0
    for r, c in image_segments:
        predict_stack[n,:,:,:] = image[r*stride:r*stride+width,c*stride:c*stride+width,:]
        n += 1
    
    predictions = model.predict(predict_stack - train_mean)
    combined_predictions = np.zeros(shape = (H,W))
    counts = np.zeros(shape = (H,W))
    
    image_segments = product(range(R), range(C))
    n=0
    for r,c in image_segments:
        combined_predictions[r*stride:r*stride+width,c*stride:c*stride+width] += predictions[n,:,:,0]
        counts[r*stride:r*stride+width,c*stride:c*stride+width] += np.ones(shape = (width,width))
        n += 1
        
    predict_image = combined_predictions / counts
    predict_image[counts == 0] = 0
    return predict_image, counts
fullsize_testing_inputs = np.load('testing_inputs.npy')
fullsize_testing_ground_truth = np.load('testing_ground_truth.npy')
fullsize_ind = 0
predict_image,counts = predict_on_fullsize_image(model,fullsize_testing_inputs[fullsize_ind,:,:,:]/255,32,train_mean=train_mean)
_,H,W,_ = fullsize_testing_inputs.shape
image = fullsize_testing_inputs[fullsize_ind,:,:,0]

predict_mask = np.zeros(shape=(H,W,4))
test_mask = np.zeros(shape=(H,W,4))
oops_mask = np.zeros(shape=(H,W,4))

predict_mask[:,:,0] = predict_image > 0.5
predict_mask[:,:,3] = 0.2*(predict_image>0.)

test_mask[:,:,0] = fullsize_testing_ground_truth[fullsize_ind,:,:,0]/255
test_mask[:,:,3] = 0.2*(test_mask[:,:,0]>0.)


oops_mask[:,:,0] = 0.5*(predict_mask[:,:,0] - test_mask[:,:,0])+0.5
oops_mask[:,:,2] = oops_mask[:,:,0]<0.5
oops_mask[:,:,3] = 0.4*((oops_mask[:,:,0]>0.51))+0.4*(oops_mask[:,:,0]<0.49)
oops_mask[:,:,0] = oops_mask[:,:,0]>0.5

fig = plt.figure(figsize=[15,7])
ax1 = plt.subplot(1,3,1)
ax1.imshow(image,cmap='gray')
ax1.imshow(predict_mask,cmap='PuRd')
ax1.set_title('Prediction')

ax2 = plt.subplot(1,3,2)
ax2.imshow(image,cmap='gray')
ax2.imshow(test_mask,cmap='PuRd')
ax2.set_title('Ground Truth')

ax3 = plt.subplot(1,3,3)
ax3.imshow(image,cmap='gray',alpha=0.4)
ax3.imshow(oops_mask,cmap='PuRd')
ax3.set_title('Misclassified')

plt.show()

png