Detecting Abnormalities in Mammograms

Jay Narhan

May 2017

Screening for breast cancer will often make use of mammography as the primary imaging modality for early detection efforts. However identifying abnormalities is a difficult problem due to large variability in the appearance of normal and abnormal tissue in mammograms and due to the subtlety associated with abnormal manifestations. Indeed, the interpretation of mammography imaging is still a manual process that is prone to human error. The application of automated classification of mammography is described as an unsolved problem. As such, this workbook aims to extend research into how machine learning technologies may supplement the detection efforts of radiologists.

Presented in this workbook are a number of models that have been developed to classify breast tissue imaging as either normal or abnormal, where abnormality contains benign and/or malignant lesions (masses or calcifications).

A number of preprocessing steps have been performed before application of these models. These steps include thresholding and segmentation of hardware artifacts from breast tissue. The preprocessing also includes the generation of difference images for every bilateral pairings of CC and MLO mammogram views for each patient in the data set. The data being used consists of these differenced images.

The workbook also leverages a modified version of the Synthetic Minority Over-sampling Technique (SMOTE), which looks to balance under-represented classes in the differenced dataset through creating synthetic minority cases via image augmentation (e.g. rotations, vertical and horizontal pixel shifts).

In detection cases, this has a minor improvement in performance metrics. In the diagnosis workbook, it has a major impact on improvements.



In [1]:
import os
import sys
import time
import numpy as np

from tqdm import tqdm

import sklearn.metrics as skm
from sklearn import metrics
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split

from skimage import color

import keras.callbacks as cb
import keras.utils.np_utils as np_utils
from keras import applications
from keras import regularizers
from keras.models import Sequential
from keras.constraints import maxnorm
from keras.preprocessing.image import ImageDataGenerator
from keras.layers.convolutional import Convolution2D, MaxPooling2D
from keras.layers import Activation, Dense, Dropout, Flatten, GaussianNoise

from matplotlib import pyplot as plt
%matplotlib inline

plt.rcParams['figure.figsize'] = (10,10)
np.set_printoptions(precision=2)

sys.path.insert(0, '../helper_modules/')
import jn_bc_helper as bc


Using Theano backend.

Reproducible Research


In [2]:
%%python
import os
os.system('python -V')
os.system('python ../helper_modules/Package_Versions.py')


scipy:           0.19.0
numpy:           1.12.1
matplotlib:      2.0.0
sklearn:         0.18.1
skimage:         0.13.0
theano:          0.9.0.dev-c697eeab84e5b8a74908da654b66ec9eca4f1291
tensorflow:      0.10.0
keras:           2.0.3
Python 2.7.13 :: Continuum Analytics, Inc.
Using Theano backend.

In [3]:
SEED = 7
np.random.seed(SEED)

CURR_DIR  = os.getcwd()
DATA_DIR  = '/Users/jnarhan/Dropbox/Breast_Cancer_Data/Data_Differenced/ALL_IMGS/'
AUG_DIR   = '/Users/jnarhan/Dropbox/Breast_Cancer_Data/Data_Differenced/AUG_DETECT_IMGS/'
meta_file = '../../Meta_Data_Files/meta_data_detection.csv'
PATHO_INX = 3    # Column number of detected label in meta_file
FILE_INX  = 1    # Column number of File name in meta_file

meta_data, cls_cnts = tqdm( bc.load_meta(meta_file, patho_idx=PATHO_INX, file_idx=FILE_INX,
                                   balanceByRemoval=False, verbose=True) )


100%|██████████| 2/2 [00:00<00:00, 14004.35it/s]
----------------
Before Balancing
----------------
abnormal  : 1472
normal    : 1072


In [4]:
bc.pprint('Loading data')
cats = bc.bcLabels(['normal', 'abnormal'])

# For smaller images supply tuple argument for a parameter 'imgResize':
# X_data, Y_data = bc.load_data(meta_data, DATA_DIR, cats, imgResize=(150,150)) 
X_data, Y_data = tqdm( bc.load_data(meta_data, DATA_DIR, cats) )


------------
Loading data
------------
100%|██████████| 2/2 [00:00<00:00, 14074.85it/s]

Class Balancing

Here - I look at a modified version of SMOTE, growing the under-represented class via synthetic augmentation, until there is a balance among the categories:


In [5]:
datagen = ImageDataGenerator(rotation_range=5, width_shift_range=.01, height_shift_range=0.01,
                             data_format='channels_first')

In [6]:
X_data, Y_data = bc.balanceViaSmote(cls_cnts, meta_data, DATA_DIR, AUG_DIR, cats, 
                                    datagen, X_data, Y_data, seed=SEED, verbose=True)


---------------
After Balancing
---------------
abnormal  : 1472
normal    : 1472

Create the Training and Test Datasets


In [7]:
X_train, X_test, Y_train, Y_test = train_test_split(X_data, Y_data,
                                                    test_size=0.25,
                                                    random_state=SEED,
                                                    stratify=zip(*Y_data)[0])

print 'Size of X_train: {:>5}'.format(len(X_train))
print 'Size of X_test: {:>5}'.format(len(X_test))
print 'Size of Y_train: {:>5}'.format(len(Y_train))
print 'Size of Y_test: {:>5}'.format(len(Y_test))

print X_train.shape
print X_test.shape
print Y_train.shape
print Y_test.shape

data = [X_train, X_test, Y_train, Y_test]


Size of X_train:  2208
Size of X_test:   736
Size of Y_train:  2208
Size of Y_test:   736
(2208, 255, 255)
(736, 255, 255)
(2208, 1)
(736, 1)

Support Vector Machine Model


In [8]:
X_train_svm = X_train.reshape( (X_train.shape[0], -1)) 
X_test_svm  = X_test.reshape( (X_test.shape[0], -1))

In [9]:
SVM_model = SVC(gamma=0.001)
SVM_model.fit( X_train_svm, Y_train)


/Users/jnarhan/miniconda2/envs/bc_venv/lib/python2.7/site-packages/sklearn/utils/validation.py:526: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  y = column_or_1d(y, warn=True)
Out[9]:
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape=None, degree=3, gamma=0.001, kernel='rbf',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)

In [10]:
predictOutput = SVM_model.predict(X_test_svm)
svm_acc = metrics.accuracy_score(y_true=Y_test, y_pred=predictOutput)

print 'SVM Accuracy: {: >7.2f}%'.format(svm_acc * 100)
print 'SVM Error: {: >10.2f}%'.format(100 - svm_acc * 100)


SVM Accuracy:   87.23%
SVM Error:      12.77%

In [11]:
svm_matrix = skm.confusion_matrix(y_true=Y_test, y_pred=predictOutput)
numBC = bc.reverseDict(cats)
class_names = numBC.values()

plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(svm_matrix, classes=class_names, normalize=True, 
                         title='SVM Normalized Confusion Matrix Using Differencing \n')
plt.tight_layout()
plt.savefig('../../figures/jn_SVM_Detect_CM_20170530.png', dpi=100)


Normalized confusion matrix
[[ 0.86  0.14]
 [ 0.11  0.89]]

In [12]:
plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(svm_matrix, classes=class_names, normalize=False, 
                         title='SVM Raw Confusion Matrix Using Differencing \n')
plt.tight_layout()


Confusion matrix, without normalization
[[316  52]
 [ 42 326]]

In [13]:
bc.cat_stats(svm_matrix)


Out[13]:
{'Accuracy': 87.23,
 'F1': 0.87,
 'NPV': 88.27,
 'PPV': 86.24,
 'Sensitivity': 88.59,
 'Specificity': 85.87}

CNN Modelling Using VGG16 in Transfer Learning


In [14]:
def VGG_Prep(img_data):
    """
    :param img_data: training or test images of shape [#images, height, width]
    :return: the array transformed to the correct shape for the VGG network
                shape = [#images, height, width, 3] transforms to rgb and reshapes
    """
    images = np.zeros([len(img_data), img_data.shape[1], img_data.shape[2], 3])
    for i in range(0, len(img_data)):
        im = (img_data[i] * 255)        # Original imagenet images were not rescaled
        im = color.gray2rgb(im)
        images[i] = im
    return(images)

In [15]:
def vgg16_bottleneck(data, modelPath, fn_train_feats, fn_train_lbls, fn_test_feats, fn_test_lbls):
    # Loading data
    X_train, X_test, Y_train, Y_test = data
    
    print('Preparing the Training Data for the VGG_16 Model.')
    X_train = VGG_Prep(X_train)
    print('Preparing the Test Data for the VGG_16 Model')
    X_test = VGG_Prep(X_test)
        
    print('Loading the VGG_16 Model')
    # "model" excludes top layer of VGG16:
    model = applications.VGG16(include_top=False, weights='imagenet') 
        
    # Generating the bottleneck features for the training data
    print('Evaluating the VGG_16 Model on the Training Data')
    bottleneck_features_train = model.predict(X_train)
    
    # Saving the bottleneck features for the training data
    featuresTrain = os.path.join(modelPath, fn_train_feats)
    labelsTrain = os.path.join(modelPath, fn_train_lbls)
    print('Saving the Training Data Bottleneck Features.')
    np.save(open(featuresTrain, 'wb'), bottleneck_features_train)
    np.save(open(labelsTrain, 'wb'), Y_train)

    # Generating the bottleneck features for the test data
    print('Evaluating the VGG_16 Model on the Test Data')
    bottleneck_features_test = model.predict(X_test)
    
    # Saving the bottleneck features for the test data
    featuresTest = os.path.join(modelPath, fn_test_feats)
    labelsTest = os.path.join(modelPath, fn_test_lbls)
    print('Saving the Test Data Bottleneck Feaures.')
    np.save(open(featuresTest, 'wb'), bottleneck_features_test)
    np.save(open(labelsTest, 'wb'), Y_test)

In [16]:
# Locations for the bottleneck and labels files that we need
train_bottleneck = '2Class_VGG16_bottleneck_features_train.npy'
train_labels     = '2Class_VGG16_labels_train.npy'
test_bottleneck  = '2Class_VGG16_bottleneck_features_test.npy'
test_labels      = '2Class_VGG16_labels_test.npy'
modelPath = os.getcwd()

top_model_weights_path = './weights/'

np.random.seed(SEED)
vgg16_bottleneck(data, modelPath, train_bottleneck, train_labels, test_bottleneck, test_labels)


Preparing the Training Data for the VGG_16 Model.
Preparing the Test Data for the VGG_16 Model
Loading the VGG_16 Model
Evaluating the VGG_16 Model on the Training Data
Saving the Training Data Bottleneck Features.
Evaluating the VGG_16 Model on the Test Data
Saving the Test Data Bottleneck Feaures.

In [17]:
def train_top_model(train_feats, train_lab, test_feats, test_lab, model_path, model_save, epoch = 50, batch = 64):
    start_time = time.time()
    
    train_bottleneck = os.path.join(model_path, train_feats)
    train_labels = os.path.join(model_path, train_lab)
    test_bottleneck = os.path.join(model_path, test_feats)
    test_labels = os.path.join(model_path, test_lab)
    
    history = bc.LossHistory()
    
    X_train = np.load(train_bottleneck)
    Y_train = np.load(train_labels)
    Y_train = np_utils.to_categorical(Y_train, num_classes=2)
    
    X_test = np.load(test_bottleneck)
    Y_test = np.load(test_labels)
    Y_test = np_utils.to_categorical(Y_test, num_classes=2)

    model = Sequential()
    model.add(Flatten(input_shape=X_train.shape[1:]))
    model.add( Dropout(0.7))
    
    model.add( Dense(256, activation='relu', kernel_constraint= maxnorm(3.)) )
    model.add( Dropout(0.5))
    
    # Softmax for probabilities for each class at the output layer
    model.add( Dense(2, activation='softmax'))
    
    model.compile(optimizer='rmsprop',  # adadelta
                  loss='binary_crossentropy', 
                  metrics=['accuracy'])

    model.fit(X_train, Y_train,
              epochs=epoch,
              batch_size=batch,
              callbacks=[history],
              validation_data=(X_test, Y_test),
              verbose=2)
    
    print "Training duration : {0}".format(time.time() - start_time)
    score = model.evaluate(X_test, Y_test, batch_size=16, verbose=2)

    print "Network's test score [loss, accuracy]: {0}".format(score)
    print 'CNN Error: {:.2f}%'.format(100 - score[1] * 100)
    
    bc.save_model(model_save, model, "jn_VGG16_Detection_top_weights.h5")
    
    return model, history.losses, history.acc, score

In [18]:
np.random.seed(SEED)
(trans_model, loss_cnn, acc_cnn, test_score_cnn) = train_top_model(train_feats=train_bottleneck,
                                                                   train_lab=train_labels, 
                                                                   test_feats=test_bottleneck, 
                                                                   test_lab=test_labels,
                                                                   model_path=modelPath, 
                                                                   model_save=top_model_weights_path,
                                                                   epoch=100)
plt.figure(figsize=(10,10))
bc.plot_losses(loss_cnn, acc_cnn)
plt.savefig('../../figures/epoch_figures/jn_Transfer_Detection_Learning_20170530.png', dpi=100)


Train on 2208 samples, validate on 736 samples
Epoch 1/100
6s - loss: 2.9987 - acc: 0.8057 - val_loss: 2.3087 - val_acc: 0.8560
Epoch 2/100
5s - loss: 2.7739 - acc: 0.8256 - val_loss: 2.3087 - val_acc: 0.8560
Epoch 3/100
5s - loss: 2.7267 - acc: 0.8288 - val_loss: 2.2651 - val_acc: 0.8587
Epoch 4/100
5s - loss: 2.7205 - acc: 0.8274 - val_loss: 2.1783 - val_acc: 0.8614
Epoch 5/100
5s - loss: 2.6980 - acc: 0.8284 - val_loss: 2.2881 - val_acc: 0.8560
Epoch 6/100
5s - loss: 2.6550 - acc: 0.8306 - val_loss: 2.2951 - val_acc: 0.8560
Epoch 7/100
5s - loss: 2.7126 - acc: 0.8261 - val_loss: 2.1046 - val_acc: 0.8668
Epoch 8/100
5s - loss: 2.6140 - acc: 0.8329 - val_loss: 2.0790 - val_acc: 0.8682
Epoch 9/100
5s - loss: 2.6839 - acc: 0.8288 - val_loss: 2.0763 - val_acc: 0.8682
Epoch 10/100
5s - loss: 2.6625 - acc: 0.8302 - val_loss: 2.0051 - val_acc: 0.8723
Epoch 11/100
5s - loss: 2.6241 - acc: 0.8333 - val_loss: 2.1224 - val_acc: 0.8655
Epoch 12/100
5s - loss: 2.6681 - acc: 0.8324 - val_loss: 2.1175 - val_acc: 0.8668
Epoch 13/100
5s - loss: 2.7134 - acc: 0.8279 - val_loss: 2.0876 - val_acc: 0.8682
Epoch 14/100
5s - loss: 2.7132 - acc: 0.8288 - val_loss: 2.0634 - val_acc: 0.8696
Epoch 15/100
6s - loss: 2.6600 - acc: 0.8324 - val_loss: 2.1810 - val_acc: 0.8628
Epoch 16/100
6s - loss: 2.5416 - acc: 0.8392 - val_loss: 2.0478 - val_acc: 0.8696
Epoch 17/100
6s - loss: 2.4761 - acc: 0.8438 - val_loss: 2.0503 - val_acc: 0.8668
Epoch 18/100
6s - loss: 2.4126 - acc: 0.8465 - val_loss: 1.5958 - val_acc: 0.8940
Epoch 19/100
6s - loss: 2.4407 - acc: 0.8451 - val_loss: 2.0923 - val_acc: 0.8655
Epoch 20/100
6s - loss: 2.5227 - acc: 0.8392 - val_loss: 1.9883 - val_acc: 0.8709
Epoch 21/100
6s - loss: 2.5727 - acc: 0.8365 - val_loss: 2.0348 - val_acc: 0.8696
Epoch 22/100
6s - loss: 2.4986 - acc: 0.8388 - val_loss: 1.8031 - val_acc: 0.8845
Epoch 23/100
7s - loss: 2.4351 - acc: 0.8451 - val_loss: 1.8696 - val_acc: 0.8764
Epoch 24/100
7s - loss: 2.3419 - acc: 0.8483 - val_loss: 1.5829 - val_acc: 0.8995
Epoch 25/100
7s - loss: 2.1962 - acc: 0.8601 - val_loss: 1.7948 - val_acc: 0.8845
Epoch 26/100
7s - loss: 2.3312 - acc: 0.8492 - val_loss: 1.5235 - val_acc: 0.9008
Epoch 27/100
7s - loss: 2.3772 - acc: 0.8483 - val_loss: 1.8020 - val_acc: 0.8859
Epoch 28/100
7s - loss: 2.2959 - acc: 0.8542 - val_loss: 1.8296 - val_acc: 0.8859
Epoch 29/100
7s - loss: 2.5066 - acc: 0.8392 - val_loss: 1.6263 - val_acc: 0.8954
Epoch 30/100
7s - loss: 2.2293 - acc: 0.8555 - val_loss: 1.7287 - val_acc: 0.8913
Epoch 31/100
7s - loss: 2.2632 - acc: 0.8546 - val_loss: 1.4861 - val_acc: 0.9049
Epoch 32/100
7s - loss: 2.2570 - acc: 0.8569 - val_loss: 1.7870 - val_acc: 0.8872
Epoch 33/100
7s - loss: 2.4082 - acc: 0.8474 - val_loss: 2.0328 - val_acc: 0.8709
Epoch 34/100
7s - loss: 2.4258 - acc: 0.8460 - val_loss: 1.8729 - val_acc: 0.8777
Epoch 35/100
8s - loss: 2.1724 - acc: 0.8623 - val_loss: 1.8092 - val_acc: 0.8872
Epoch 36/100
8s - loss: 2.2104 - acc: 0.8596 - val_loss: 1.8133 - val_acc: 0.8845
Epoch 37/100
8s - loss: 2.2610 - acc: 0.8569 - val_loss: 1.9494 - val_acc: 0.8750
Epoch 38/100
8s - loss: 2.2526 - acc: 0.8569 - val_loss: 1.7664 - val_acc: 0.8859
Epoch 39/100
8s - loss: 2.2093 - acc: 0.8591 - val_loss: 1.6721 - val_acc: 0.8940
Epoch 40/100
8s - loss: 2.0834 - acc: 0.8682 - val_loss: 1.6339 - val_acc: 0.8981
Epoch 41/100
8s - loss: 2.1345 - acc: 0.8632 - val_loss: 1.7311 - val_acc: 0.8899
Epoch 42/100
8s - loss: 2.1858 - acc: 0.8614 - val_loss: 1.9052 - val_acc: 0.8777
Epoch 43/100
8s - loss: 2.1487 - acc: 0.8628 - val_loss: 1.6698 - val_acc: 0.8940
Epoch 44/100
8s - loss: 2.1952 - acc: 0.8610 - val_loss: 1.7062 - val_acc: 0.8913
Epoch 45/100
8s - loss: 2.1560 - acc: 0.8628 - val_loss: 1.7136 - val_acc: 0.8927
Epoch 46/100
8s - loss: 2.1335 - acc: 0.8641 - val_loss: 1.6239 - val_acc: 0.8967
Epoch 47/100
8s - loss: 2.1816 - acc: 0.8628 - val_loss: 1.8369 - val_acc: 0.8845
Epoch 48/100
8s - loss: 2.1000 - acc: 0.8678 - val_loss: 1.9348 - val_acc: 0.8777
Epoch 49/100
8s - loss: 1.9796 - acc: 0.8755 - val_loss: 1.6990 - val_acc: 0.8927
Epoch 50/100
8s - loss: 2.0134 - acc: 0.8727 - val_loss: 1.7216 - val_acc: 0.8913
Epoch 51/100
8s - loss: 2.0857 - acc: 0.8673 - val_loss: 1.9354 - val_acc: 0.8750
Epoch 52/100
8s - loss: 2.1014 - acc: 0.8678 - val_loss: 1.6624 - val_acc: 0.8927
Epoch 53/100
8s - loss: 2.0132 - acc: 0.8723 - val_loss: 1.7055 - val_acc: 0.8899
Epoch 54/100
8s - loss: 2.0967 - acc: 0.8673 - val_loss: 1.7362 - val_acc: 0.8872
Epoch 55/100
8s - loss: 2.0408 - acc: 0.8700 - val_loss: 1.8609 - val_acc: 0.8818
Epoch 56/100
8s - loss: 2.1845 - acc: 0.8619 - val_loss: 1.6771 - val_acc: 0.8954
Epoch 57/100
8s - loss: 2.0482 - acc: 0.8709 - val_loss: 1.7186 - val_acc: 0.8913
Epoch 58/100
8s - loss: 2.0073 - acc: 0.8727 - val_loss: 1.6802 - val_acc: 0.8927
Epoch 59/100
8s - loss: 2.0829 - acc: 0.8687 - val_loss: 1.7949 - val_acc: 0.8845
Epoch 60/100
8s - loss: 2.1801 - acc: 0.8605 - val_loss: 1.7783 - val_acc: 0.8872
Epoch 61/100
8s - loss: 2.1488 - acc: 0.8637 - val_loss: 1.7246 - val_acc: 0.8913
Epoch 62/100
8s - loss: 2.0641 - acc: 0.8696 - val_loss: 1.7425 - val_acc: 0.8913
Epoch 63/100
9s - loss: 2.0474 - acc: 0.8718 - val_loss: 1.6614 - val_acc: 0.8940
Epoch 64/100
9s - loss: 2.0518 - acc: 0.8718 - val_loss: 1.6131 - val_acc: 0.8967
Epoch 65/100
9s - loss: 1.9529 - acc: 0.8759 - val_loss: 1.7043 - val_acc: 0.8927
Epoch 66/100
9s - loss: 2.2086 - acc: 0.8610 - val_loss: 1.7225 - val_acc: 0.8913
Epoch 67/100
9s - loss: 2.1418 - acc: 0.8637 - val_loss: 1.8312 - val_acc: 0.8832
Epoch 68/100
9s - loss: 2.1136 - acc: 0.8673 - val_loss: 1.8754 - val_acc: 0.8791
Epoch 69/100
9s - loss: 1.9560 - acc: 0.8759 - val_loss: 1.6736 - val_acc: 0.8954
Epoch 70/100
9s - loss: 2.0797 - acc: 0.8687 - val_loss: 1.8077 - val_acc: 0.8832
Epoch 71/100
9s - loss: 2.0350 - acc: 0.8709 - val_loss: 1.6668 - val_acc: 0.8927
Epoch 72/100
9s - loss: 2.0913 - acc: 0.8687 - val_loss: 1.6568 - val_acc: 0.8954
Epoch 73/100
9s - loss: 1.9867 - acc: 0.8750 - val_loss: 1.6278 - val_acc: 0.8967
Epoch 74/100
9s - loss: 2.4402 - acc: 0.8447 - val_loss: 1.6800 - val_acc: 0.8927
Epoch 75/100
9s - loss: 1.9812 - acc: 0.8750 - val_loss: 1.8329 - val_acc: 0.8832
Epoch 76/100
9s - loss: 2.1068 - acc: 0.8659 - val_loss: 1.7304 - val_acc: 0.8899
Epoch 77/100
9s - loss: 2.0411 - acc: 0.8696 - val_loss: 1.7181 - val_acc: 0.8859
Epoch 78/100
9s - loss: 2.0102 - acc: 0.8727 - val_loss: 1.7637 - val_acc: 0.8872
Epoch 79/100
9s - loss: 2.0525 - acc: 0.8714 - val_loss: 1.6602 - val_acc: 0.8927
Epoch 80/100
9s - loss: 2.0615 - acc: 0.8705 - val_loss: 1.7200 - val_acc: 0.8927
Epoch 81/100
9s - loss: 1.9624 - acc: 0.8755 - val_loss: 1.7817 - val_acc: 0.8859
Epoch 82/100
9s - loss: 2.0516 - acc: 0.8705 - val_loss: 1.6905 - val_acc: 0.8927
Epoch 83/100
9s - loss: 2.0427 - acc: 0.8714 - val_loss: 1.7253 - val_acc: 0.8886
Epoch 84/100
9s - loss: 2.1240 - acc: 0.8655 - val_loss: 1.6964 - val_acc: 0.8927
Epoch 85/100
9s - loss: 1.9781 - acc: 0.8745 - val_loss: 1.5723 - val_acc: 0.9008
Epoch 86/100
9s - loss: 1.9772 - acc: 0.8745 - val_loss: 1.6030 - val_acc: 0.8995
Epoch 87/100
9s - loss: 2.0132 - acc: 0.8718 - val_loss: 1.6590 - val_acc: 0.8954
Epoch 88/100
9s - loss: 1.9904 - acc: 0.8727 - val_loss: 1.8502 - val_acc: 0.8832
Epoch 89/100
9s - loss: 2.0053 - acc: 0.8732 - val_loss: 1.6827 - val_acc: 0.8940
Epoch 90/100
9s - loss: 1.9360 - acc: 0.8768 - val_loss: 1.6744 - val_acc: 0.8940
Epoch 91/100
9s - loss: 2.0913 - acc: 0.8678 - val_loss: 2.0348 - val_acc: 0.8709
Epoch 92/100
9s - loss: 2.0256 - acc: 0.8723 - val_loss: 1.8922 - val_acc: 0.8804
Epoch 93/100
9s - loss: 2.0528 - acc: 0.8700 - val_loss: 1.7752 - val_acc: 0.8859
Epoch 94/100
9s - loss: 1.9756 - acc: 0.8764 - val_loss: 1.7721 - val_acc: 0.8886
Epoch 95/100
9s - loss: 2.3782 - acc: 0.8487 - val_loss: 2.2865 - val_acc: 0.8519
Epoch 96/100
9s - loss: 2.1466 - acc: 0.8646 - val_loss: 1.7196 - val_acc: 0.8913
Epoch 97/100
9s - loss: 1.9464 - acc: 0.8768 - val_loss: 1.7742 - val_acc: 0.8886
Epoch 98/100
9s - loss: 1.8494 - acc: 0.8836 - val_loss: 1.7250 - val_acc: 0.8899
Epoch 99/100
9s - loss: 1.8198 - acc: 0.8836 - val_loss: 1.6863 - val_acc: 0.8927
Epoch 100/100
9s - loss: 2.1122 - acc: 0.8655 - val_loss: 1.7642 - val_acc: 0.8899
Training duration : 821.658025026
Network's test score [loss, accuracy]: [1.7641984831665223, 0.88994565217391308]
CNN Error: 11.01%
Model and Weights Saved to Disk
<matplotlib.figure.Figure at 0x11d1f1510>

In [19]:
print 'Transfer Learning CNN Accuracy: {: >7.2f}%'.format(test_score_cnn[1] * 100)
print 'Transfer Learning CNN Error: {: >10.2f}%'.format(100 - test_score_cnn[1] * 100)

predictOutput = bc.predict(trans_model, np.load(test_bottleneck))
trans_matrix = skm.confusion_matrix(y_true=Y_test, y_pred=predictOutput)

plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(trans_matrix, classes=class_names, normalize=True,
                         title='Transfer CNN Normalized Confusion Matrix Using Differencing \n')
plt.tight_layout()
plt.savefig('../../figures/jn_Transfer_Detection_CM_20170530.png', dpi=100)


Transfer Learning CNN Accuracy:   88.99%
Transfer Learning CNN Error:      11.01%
Normalized confusion matrix
[[ 0.87  0.13]
 [ 0.09  0.91]]

In [20]:
plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(trans_matrix, classes=class_names, normalize=False,
                         title='Transfer CNN Raw Confusion Matrix Using Differencing \n')
plt.tight_layout()


Confusion matrix, without normalization
[[321  47]
 [ 34 334]]

In [21]:
bc.cat_stats(trans_matrix)


Out[21]:
{'Accuracy': 88.99,
 'F1': 0.89,
 'NPV': 90.42,
 'PPV': 87.66,
 'Sensitivity': 90.76,
 'Specificity': 87.23}

Core CNN Modelling

Prep and package the data for Keras processing:


In [22]:
data = [X_train, X_test, Y_train, Y_test]
X_train, X_test, Y_train, Y_test = bc.prep_data(data, cats)
data = [X_train, X_test, Y_train, Y_test]

print X_train.shape
print X_test.shape
print Y_train.shape
print Y_test.shape


Prep data for NNs ...
Data Prepped for Neural Nets.
(2208, 1, 255, 255)
(736, 1, 255, 255)
(2208, 2)
(736, 2)

Heavy Regularization


In [23]:
def diff_model_v7_reg(numClasses, input_shape=(3, 150,150), add_noise=False, noise=0.01, verbose=False):
    model = Sequential()
    if (add_noise):
        model.add( GaussianNoise(noise, input_shape=input_shape))
        model.add( Convolution2D(filters=16, 
                                 kernel_size=(5,5), 
                                 data_format='channels_first',
                                 padding='same',
                                 activation='relu'))
    else:
        model.add( Convolution2D(filters=16, 
                                 kernel_size=(5,5), 
                                 data_format='channels_first',
                                 padding='same',
                                 activation='relu',
                                 input_shape=input_shape))
    model.add( Dropout(0.7))
    
    model.add( Convolution2D(filters=32, kernel_size=(3,3), 
                             data_format='channels_first', padding='same', activation='relu'))
    model.add( MaxPooling2D(pool_size= (2,2), data_format='channels_first'))
    model.add( Dropout(0.4))
    model.add( Convolution2D(filters=32, kernel_size=(3,3), 
                             data_format='channels_first', activation='relu'))
    
    model.add( Convolution2D(filters=64, kernel_size=(3,3), 
                             data_format='channels_first', padding='same', activation='relu',
                             kernel_regularizer=regularizers.l2(0.01)))
    model.add( MaxPooling2D(pool_size= (2,2), data_format='channels_first'))
    model.add( Convolution2D(filters=64, kernel_size=(3,3), 
                             data_format='channels_first', activation='relu',
                             kernel_regularizer=regularizers.l2(0.01)))
    model.add( Dropout(0.4))
    
    model.add( Convolution2D(filters=128, kernel_size=(3,3), 
                             data_format='channels_first', padding='same', activation='relu',
                             kernel_regularizer=regularizers.l2(0.01)))
    model.add( MaxPooling2D(pool_size= (2,2), data_format='channels_first'))
    
    model.add( Convolution2D(filters=128, kernel_size=(3,3), 
                             data_format='channels_first', activation='relu',
                             kernel_regularizer=regularizers.l2(0.01)))
    model.add(Dropout(0.4))
    
    model.add( Flatten())
    
    model.add( Dense(128, activation='relu', kernel_constraint= maxnorm(3.)) )
    model.add( Dropout(0.4))
    
    model.add( Dense(64, activation='relu', kernel_constraint= maxnorm(3.)) )
    model.add( Dropout(0.4))
    
    # Softmax for probabilities for each class at the output layer
    model.add( Dense(numClasses, activation='softmax'))
    
    if verbose:
        print( model.summary() )
    
    model.compile(loss='binary_crossentropy',
                  optimizer='rmsprop',
                  metrics=['accuracy'])
    return model

In [24]:
diff_model7_noise_reg = diff_model_v7_reg(len(cats),
                                          input_shape=(X_train.shape[1], X_train.shape[2], X_train.shape[3]),
                                          add_noise=True, verbose=True)


_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
gaussian_noise_1 (GaussianNo (None, 1, 255, 255)       0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 16, 255, 255)      416       
_________________________________________________________________
dropout_3 (Dropout)          (None, 16, 255, 255)      0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 32, 255, 255)      4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 32, 127, 127)      0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 32, 127, 127)      0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 32, 125, 125)      9248      
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 64, 125, 125)      18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 64, 62, 62)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 64, 60, 60)        36928     
_________________________________________________________________
dropout_5 (Dropout)          (None, 64, 60, 60)        0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 128, 60, 60)       73856     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 128, 30, 30)       0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 128, 28, 28)       147584    
_________________________________________________________________
dropout_6 (Dropout)          (None, 128, 28, 28)       0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 100352)            0         
_________________________________________________________________
dense_3 (Dense)              (None, 128)               12845184  
_________________________________________________________________
dropout_7 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 64)                8256      
_________________________________________________________________
dropout_8 (Dropout)          (None, 64)                0         
_________________________________________________________________
dense_5 (Dense)              (None, 2)                 130       
=================================================================
Total params: 13,144,738
Trainable params: 13,144,738
Non-trainable params: 0
_________________________________________________________________
None

In [25]:
np.random.seed(SEED)

(cnn_model, loss_cnn, acc_cnn, test_score_cnn) = bc.run_network(model=diff_model7_noise_reg, 
                                                                data=data, 
                                                                epochs=50, batch=64)
plt.figure(figsize=(10,10))
bc.plot_losses(loss_cnn, acc_cnn)
plt.savefig('../../figures/epoch_figures/jn_Core_CNN_Detection_Learning_20170530.png', dpi=100)


Training model...
Train on 2208 samples, validate on 736 samples
Epoch 1/50
655s - loss: 2.0236 - acc: 0.7446 - val_loss: 1.0254 - val_acc: 0.8505
Epoch 2/50
650s - loss: 0.7703 - acc: 0.8166 - val_loss: 0.5096 - val_acc: 0.8519
Epoch 3/50
653s - loss: 0.5146 - acc: 0.8256 - val_loss: 0.4934 - val_acc: 0.8179
Epoch 4/50
648s - loss: 0.4750 - acc: 0.8188 - val_loss: 0.3879 - val_acc: 0.8533
Epoch 5/50
632s - loss: 0.4646 - acc: 0.8288 - val_loss: 0.5748 - val_acc: 0.7201
Epoch 6/50
637s - loss: 0.4450 - acc: 0.8261 - val_loss: 0.4364 - val_acc: 0.8261
Epoch 7/50
640s - loss: 0.4383 - acc: 0.8274 - val_loss: 0.4021 - val_acc: 0.8560
Epoch 8/50
637s - loss: 0.4193 - acc: 0.8311 - val_loss: 0.3678 - val_acc: 0.8696
Epoch 9/50
638s - loss: 0.4067 - acc: 0.8406 - val_loss: 0.3823 - val_acc: 0.8587
Epoch 10/50
637s - loss: 0.3966 - acc: 0.8428 - val_loss: 0.4114 - val_acc: 0.8736
Epoch 11/50
628s - loss: 0.4074 - acc: 0.8374 - val_loss: 0.3495 - val_acc: 0.8696
Epoch 12/50
627s - loss: 0.3884 - acc: 0.8483 - val_loss: 0.3769 - val_acc: 0.8709
Epoch 13/50
628s - loss: 0.3760 - acc: 0.8460 - val_loss: 0.3749 - val_acc: 0.8736
Epoch 14/50
629s - loss: 0.3701 - acc: 0.8578 - val_loss: 0.3615 - val_acc: 0.8696
Epoch 15/50
630s - loss: 0.3663 - acc: 0.8587 - val_loss: 0.3974 - val_acc: 0.8587
Epoch 16/50
631s - loss: 0.3556 - acc: 0.8650 - val_loss: 0.3316 - val_acc: 0.8723
Epoch 17/50
631s - loss: 0.3497 - acc: 0.8714 - val_loss: 0.3765 - val_acc: 0.8668
Epoch 18/50
633s - loss: 0.3316 - acc: 0.8696 - val_loss: 0.4197 - val_acc: 0.8410
Epoch 19/50
637s - loss: 0.3274 - acc: 0.8809 - val_loss: 0.3382 - val_acc: 0.8682
Epoch 20/50
642s - loss: 0.3263 - acc: 0.8736 - val_loss: 0.4060 - val_acc: 0.8533
Epoch 21/50
644s - loss: 0.3027 - acc: 0.8800 - val_loss: 0.4360 - val_acc: 0.8111
Epoch 22/50
648s - loss: 0.2979 - acc: 0.8845 - val_loss: 0.4042 - val_acc: 0.8573
Epoch 23/50
650s - loss: 0.2964 - acc: 0.8895 - val_loss: 0.3391 - val_acc: 0.8682
Epoch 24/50
653s - loss: 0.2902 - acc: 0.8863 - val_loss: 0.4443 - val_acc: 0.8546
Epoch 25/50
642s - loss: 0.2745 - acc: 0.8967 - val_loss: 0.4429 - val_acc: 0.8668
Epoch 26/50
641s - loss: 0.2903 - acc: 0.8931 - val_loss: 0.4458 - val_acc: 0.8451
Epoch 27/50
642s - loss: 0.2534 - acc: 0.9017 - val_loss: 0.5270 - val_acc: 0.8451
Epoch 28/50
641s - loss: 0.2704 - acc: 0.9049 - val_loss: 0.3628 - val_acc: 0.8709
Epoch 29/50
641s - loss: 0.2684 - acc: 0.9035 - val_loss: 0.3737 - val_acc: 0.8614
Epoch 30/50
641s - loss: 0.2556 - acc: 0.9094 - val_loss: 0.4254 - val_acc: 0.8410
Epoch 31/50
643s - loss: 0.2384 - acc: 0.9198 - val_loss: 0.3652 - val_acc: 0.8764
Epoch 32/50
643s - loss: 0.2599 - acc: 0.9085 - val_loss: 0.3878 - val_acc: 0.8465
Epoch 33/50
645s - loss: 0.2276 - acc: 0.9153 - val_loss: 0.3671 - val_acc: 0.8750
Epoch 34/50
647s - loss: 0.2253 - acc: 0.9162 - val_loss: 0.3574 - val_acc: 0.8682
Epoch 35/50
640s - loss: 0.2267 - acc: 0.9180 - val_loss: 0.3862 - val_acc: 0.8641
Epoch 36/50
642s - loss: 0.2061 - acc: 0.9289 - val_loss: 0.4644 - val_acc: 0.8465
Epoch 37/50
642s - loss: 0.2242 - acc: 0.9244 - val_loss: 0.4038 - val_acc: 0.8818
Epoch 38/50
642s - loss: 0.2006 - acc: 0.9316 - val_loss: 0.5509 - val_acc: 0.8478
Epoch 39/50
641s - loss: 0.2000 - acc: 0.9339 - val_loss: 0.3708 - val_acc: 0.8764
Epoch 40/50
641s - loss: 0.2348 - acc: 0.9230 - val_loss: 0.3697 - val_acc: 0.8736
Epoch 41/50
643s - loss: 0.1958 - acc: 0.9325 - val_loss: 0.3463 - val_acc: 0.8940
Epoch 42/50
645s - loss: 0.2229 - acc: 0.9307 - val_loss: 0.4022 - val_acc: 0.8723
Epoch 43/50
642s - loss: 0.1917 - acc: 0.9366 - val_loss: 0.4996 - val_acc: 0.8614
Epoch 44/50
643s - loss: 0.1872 - acc: 0.9348 - val_loss: 0.3808 - val_acc: 0.8750
Epoch 45/50
642s - loss: 0.2018 - acc: 0.9334 - val_loss: 0.5730 - val_acc: 0.8587
Epoch 46/50
650s - loss: 0.2102 - acc: 0.9316 - val_loss: 0.3553 - val_acc: 0.8859
Epoch 47/50
646s - loss: 0.1696 - acc: 0.9497 - val_loss: 0.6868 - val_acc: 0.8438
Epoch 48/50
647s - loss: 0.1926 - acc: 0.9434 - val_loss: 0.4849 - val_acc: 0.8696
Epoch 49/50
648s - loss: 0.1660 - acc: 0.9461 - val_loss: 0.4224 - val_acc: 0.8560
Epoch 50/50
649s - loss: 0.1964 - acc: 0.9402 - val_loss: 0.3717 - val_acc: 0.8791
Training duration : 32099.092628
Network's test score [loss, accuracy]: [0.37165422000638815, 0.87907608695652173]
CNN Error: 12.09%
<matplotlib.figure.Figure at 0x11bd936d0>

In [26]:
bc.save_model(dir_path='./weights/', model=cnn_model, name='jn_Core_CNN_Detection_20170530')


Model and Weights Saved to Disk

In [27]:
print 'Core CNN Accuracy: {: >7.2f}%'.format(test_score_cnn[1] * 100)
print 'Core CNN Error: {: >10.2f}%'.format(100 - test_score_cnn[1] * 100)

predictOutput = bc.predict(cnn_model, X_test)

cnn_matrix = skm.confusion_matrix(y_true=[val.argmax() for val in Y_test], y_pred=predictOutput)

plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(cnn_matrix, classes=class_names, normalize=True,
                         title='CNN Normalized Confusion Matrix Using Differencing \n')
plt.tight_layout()
plt.savefig('../../figures/jn_Core_CNN_Detection_CM_20170530.png', dpi=100)


Core CNN Accuracy:   87.91%
Core CNN Error:      12.09%
Normalized confusion matrix
[[ 0.88  0.12]
 [ 0.12  0.88]]

In [28]:
plt.figure(figsize=(8,6))
bc.plot_confusion_matrix(cnn_matrix, classes=class_names, normalize=False,
                         title='CNN Raw Confusion Matrix Using Differencing \n')
plt.tight_layout()


Confusion matrix, without normalization
[[322  46]
 [ 43 325]]

In [29]:
bc.cat_stats(cnn_matrix)


Out[29]:
{'Accuracy': 87.91,
 'F1': 0.88,
 'NPV': 88.22,
 'PPV': 87.6,
 'Sensitivity': 88.32,
 'Specificity': 87.5}

In [ ]: