CNN X-ray Pneumonia detection without transfer learning: 95% test accuracy

Abstract

The purpose of this script is to build a CNN model from scratch, understand various stages, play around with mode architecture and regularization to achieve in the end a relatively good test accuracy. The final result shows close to 95% accuracy on test dataset.

Key highlights:

  • Create a model from scratch without transfer learning.
  • Gain insights on how the depth and width of the model affects the loss curve
  • Write custom callbacks for saving best model or early stopper
In [1]:
import os
import zipfile
import random
import shutil
from shutil import copyfile
from os import getcwd
import pathlib
import datetime

import cv2
from PIL import Image


import tensorflow_datasets as tfds
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.preprocessing import image_dataset_from_directory
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.optimizers import RMSprop, Adam, SGD
from tensorflow.keras.callbacks import TensorBoard
from tensorflow.keras.models import model_from_json
from sklearn.model_selection import train_test_split


import matplotlib.pylab as plt
import numpy as np

#Write twice, because sometimes it does not work the first time, especially if you are switching back to notebook
%matplotlib notebook
%matplotlib notebook

Set path of the dataset

The images are under two folders, /train, /test under /chest_xray_images folder. Each folder /train and /test subsequently contains two categories Pneumonia and Normal.

In [2]:
current_working_dir = os.getcwd()

path_to_images = pathlib.Path(os.path.join(current_working_dir, 'chest_xray_images'))
path_to_training = pathlib.Path(os.path.join(path_to_images, 'train'))
path_to_testing = pathlib.Path(os.path.join(path_to_images, 'test'))

#============ Total training images ===============
total_images = 0
for root, dirs, files in os.walk(path_to_training):
    total_images += len(files)
print("Total training images: ", total_images)

#=============== Total testing images ===============
total_images = 0
for root, dirs, files in os.walk(path_to_testing):
    total_images += len(files)
print("Total testing images: ", total_images)

#=============== Total images ===============
total_images = 0
for root, dirs, files in os.walk(path_to_images):
    total_images += len(files)
print("Total  images: ", total_images)
Total training images:  5216
Total testing images:  624
Total  images:  5856

Create data feeder

  • Resize images
  • set batch size
  • seed to shuffle and randomly dividing the training and validation dataset
  • parameters for image augmentation
In [33]:
new_img_size = (225, 225)
batch_size = 32

train_datagen = ImageDataGenerator(
    rescale=1./255,
    shear_range=0.2,
    zoom_range=0.35,
    #vertical_flip=True,
)

test_datagen = ImageDataGenerator(rescale=1./255)


seed = 107
training_ds = train_datagen.flow_from_directory(path_to_training,
                                                seed=seed,
                                                shuffle=True,
                                                target_size=new_img_size,
                                                batch_size=batch_size,
                                                class_mode='binary')

test_ds = test_datagen.flow_from_directory(path_to_testing,
                                           shuffle=True,
                                           seed=seed,
                                           target_size=new_img_size,
                                           batch_size=batch_size,
                                           class_mode='binary')
Found 5216 images belonging to 2 classes.
Found 624 images belonging to 2 classes.
In [4]:
class_names = training_ds.class_indices
print(class_names)

#invert the dictionary
class_names = {v: k for k, v in class_names.items()}
print(class_names)
{'NORMAL': 0, 'PNEUMONIA': 1}
{0: 'NORMAL', 1: 'PNEUMONIA'}

Inspect some images

Take 1 batch from the validation dataset and plot.

In [5]:
plt.figure(figsize=(8, 8))
image_batch, label_batch = training_ds.next()
for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(image_batch[i])
    plt.title(class_names[label_batch[i]])
    plt.axis("off")

Create model

Use at least 3 layers. The number of filters are increased progressively starting with 16. We use dropout as regularization to avoid over fitting. We do not want high dropout in the initial layers, usually it is progressively increased over the deeper layers. And finally, since we are using ReLu activation function, using kernel_initializer='he_uniform' is highly recommended.

In [26]:
# lr_reduce = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_acc', factor=0.1, min_delta=0.0001, patience=3, verbose=1)
lr_reduce = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=4, verbose=2, mode='max')
In [34]:
model = tf.keras.models.Sequential([
    #ayers.experimental.preprocessing.Rescaling(1./255,input_shape=(new_img_size[0],new_img_size[1],3)),
    #tf.keras.layers.experimental.preprocessing.RandomFlip('horizontal'),
    #tf.keras.layers.experimental.preprocessing.RandomRotation(0.3),
    
    tf.keras.layers.Conv2D(16, (3,3), input_shape=(new_img_size[0],new_img_size[1],3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.Conv2D(16, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPool2D(2,2),
    #tf.keras.layers.Dropout(0.2),
    
    tf.keras.layers.SeparableConv2D(32, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.SeparableConv2D(32, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPool2D(2,2),
    #tf.keras.layers.Dropout(0.2),
    
    tf.keras.layers.SeparableConv2D(64, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.SeparableConv2D(64, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPool2D(2,2),
    #tf.keras.layers.Dropout(0.2),
    
    tf.keras.layers.SeparableConv2D(128, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.SeparableConv2D(128, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPool2D(2,2),
    tf.keras.layers.Dropout(0.2),
    
    tf.keras.layers.SeparableConv2D(256, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.SeparableConv2D(256, (3,3), activation='relu', kernel_initializer='he_uniform', padding='same'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPool2D(2,2),
    tf.keras.layers.Dropout(0.2),
    
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(512, activation='relu', kernel_initializer='he_uniform'),
    #tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Dropout(0.5),

    tf.keras.layers.Dense(128, activation='relu', kernel_initializer='he_uniform'),
    #tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Dropout(0.3),
    
    tf.keras.layers.Dense(64, activation='relu', kernel_initializer='he_uniform'),
    #tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Dropout(0.3),
    
    tf.keras.layers.Dense(1, activation='sigmoid')
])
In [35]:
learning_rate = 0.0005

model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc']) #SGD(lr=0.007, momentum=0.9), #RMSprop(lr=0.0001), #Adam(lr=0.001)
#model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])

#print(model.summary())

Create callbacks

In [36]:
#==================================================
# This function keeps the initial learning rate for the first ten epochs
# and decreases it exponentially after that.
# def scheduler(epoch, lr):
#     if epoch < 10:
#         return lr
#     else:
#         return lr * tf.math.exp(-0.1)

# lr_callback = tf.keras.callbacks.LearningRateScheduler(scheduler)

#==================================================
#==================================================

class MyThresholdCallback(tf.keras.callbacks.Callback):
    def __init__(self, threshold):
        super(MyThresholdCallback, self).__init__()
        self.threshold = threshold

    def on_epoch_end(self, epoch, logs=None): 
        val_acc = logs["val_acc"]
        if val_acc >= self.threshold:
            self.model.stop_training = True

early_stopping_callback = MyThresholdCallback(threshold=0.951)
In [37]:
logdir = os.path.join(
    "logs", datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))

modelfname = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")+'bs_'+ str(batch_size) + 'lr' + str(learning_rate) +'.h5'


model_folder = pathlib.Path(os.path.join(current_working_dir,'model'))
mcp_save = tf.keras.callbacks.ModelCheckpoint(os.path.join(model_folder, modelfname), save_best_only=True, monitor='val_loss', mode='min')

tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)

print(logdir)
# # ======= Go to the logs folder within the current project. ===========
# # ======= Open a terminal, activate correct env, run following command to launch tensorboard
# # tensorboard --port=6007 --logdir ~/logs
# # =====================================================================

epochs = 200
history = model.fit(
    training_ds,
    validation_data=test_ds,
    epochs=epochs,
    verbose=1,
    callbacks=[tensorboard_callback, mcp_save, early_stopping_callback] #lr_reduce
)

#model.save(os.path.join(model_folder, modelfname))
logs/20201105-065549
Epoch 1/200
163/163 [==============================] - 73s 449ms/step - loss: 0.2804 - acc: 0.8775 - val_loss: 1.1171 - val_acc: 0.6250
Epoch 2/200
163/163 [==============================] - 73s 447ms/step - loss: 0.2322 - acc: 0.9020 - val_loss: 0.7431 - val_acc: 0.6250
Epoch 3/200
163/163 [==============================] - 73s 447ms/step - loss: 0.2087 - acc: 0.9166 - val_loss: 0.7963 - val_acc: 0.6250
Epoch 4/200
163/163 [==============================] - 73s 447ms/step - loss: 0.1847 - acc: 0.9316 - val_loss: 1.9360 - val_acc: 0.6250
Epoch 5/200
163/163 [==============================] - 73s 446ms/step - loss: 0.1707 - acc: 0.9335 - val_loss: 0.3531 - val_acc: 0.8718
Epoch 6/200
163/163 [==============================] - 73s 445ms/step - loss: 0.1699 - acc: 0.9402 - val_loss: 0.7099 - val_acc: 0.7612
Epoch 7/200
163/163 [==============================] - 73s 448ms/step - loss: 0.1477 - acc: 0.9421 - val_loss: 1.1142 - val_acc: 0.6907
Epoch 8/200
163/163 [==============================] - 73s 447ms/step - loss: 0.1441 - acc: 0.9452 - val_loss: 0.3326 - val_acc: 0.8606
Epoch 9/200
163/163 [==============================] - 73s 448ms/step - loss: 0.1392 - acc: 0.9482 - val_loss: 0.6245 - val_acc: 0.8446
Epoch 10/200
163/163 [==============================] - 73s 447ms/step - loss: 0.1467 - acc: 0.9444 - val_loss: 0.3494 - val_acc: 0.8814
Epoch 11/200
163/163 [==============================] - 73s 446ms/step - loss: 0.1327 - acc: 0.9494 - val_loss: 0.2961 - val_acc: 0.8942
Epoch 12/200
163/163 [==============================] - 73s 446ms/step - loss: 0.1273 - acc: 0.9542 - val_loss: 0.2840 - val_acc: 0.8862
Epoch 13/200
163/163 [==============================] - 73s 446ms/step - loss: 0.1407 - acc: 0.9490 - val_loss: 0.2943 - val_acc: 0.8830
Epoch 14/200
163/163 [==============================] - 73s 448ms/step - loss: 0.1234 - acc: 0.9567 - val_loss: 1.8046 - val_acc: 0.6875
Epoch 15/200
163/163 [==============================] - 73s 446ms/step - loss: 0.1272 - acc: 0.9540 - val_loss: 0.3902 - val_acc: 0.8814
Epoch 16/200
163/163 [==============================] - 73s 445ms/step - loss: 0.1113 - acc: 0.9580 - val_loss: 0.3177 - val_acc: 0.8894
Epoch 17/200
163/163 [==============================] - 73s 445ms/step - loss: 0.1158 - acc: 0.9582 - val_loss: 0.3499 - val_acc: 0.8093
Epoch 18/200
163/163 [==============================] - 73s 447ms/step - loss: 0.1388 - acc: 0.9540 - val_loss: 0.4719 - val_acc: 0.8750
Epoch 19/200
163/163 [==============================] - 73s 449ms/step - loss: 0.1115 - acc: 0.9586 - val_loss: 0.4430 - val_acc: 0.8734
Epoch 20/200
163/163 [==============================] - 73s 449ms/step - loss: 0.1183 - acc: 0.9549 - val_loss: 0.2225 - val_acc: 0.9183
Epoch 21/200
163/163 [==============================] - 73s 446ms/step - loss: 0.1125 - acc: 0.9609 - val_loss: 0.3292 - val_acc: 0.8590
Epoch 22/200
163/163 [==============================] - 73s 446ms/step - loss: 0.1187 - acc: 0.9561 - val_loss: 0.6527 - val_acc: 0.8221
Epoch 23/200
163/163 [==============================] - 73s 448ms/step - loss: 0.1013 - acc: 0.9630 - val_loss: 0.4939 - val_acc: 0.8734
Epoch 24/200
163/163 [==============================] - 73s 447ms/step - loss: 0.1055 - acc: 0.9655 - val_loss: 0.3129 - val_acc: 0.8750
Epoch 25/200
163/163 [==============================] - 73s 446ms/step - loss: 0.1080 - acc: 0.9618 - val_loss: 0.2667 - val_acc: 0.8750
Epoch 26/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0990 - acc: 0.9657 - val_loss: 0.3729 - val_acc: 0.8862
Epoch 27/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0952 - acc: 0.9664 - val_loss: 0.4029 - val_acc: 0.8109
Epoch 28/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0967 - acc: 0.9640 - val_loss: 0.2467 - val_acc: 0.8878
Epoch 29/200
163/163 [==============================] - 73s 448ms/step - loss: 0.1030 - acc: 0.9649 - val_loss: 0.2811 - val_acc: 0.9231
Epoch 30/200
163/163 [==============================] - 72s 445ms/step - loss: 0.0998 - acc: 0.9657 - val_loss: 0.4691 - val_acc: 0.8750
Epoch 31/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0938 - acc: 0.9672 - val_loss: 1.2169 - val_acc: 0.6827
Epoch 32/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0957 - acc: 0.9655 - val_loss: 0.2854 - val_acc: 0.8958
Epoch 33/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0968 - acc: 0.9664 - val_loss: 0.3170 - val_acc: 0.8878
Epoch 34/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0971 - acc: 0.9678 - val_loss: 0.4851 - val_acc: 0.7965
Epoch 35/200
163/163 [==============================] - 72s 442ms/step - loss: 0.0934 - acc: 0.9695 - val_loss: 0.4433 - val_acc: 0.8862
Epoch 36/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0864 - acc: 0.9689 - val_loss: 0.4294 - val_acc: 0.8894
Epoch 37/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0908 - acc: 0.9716 - val_loss: 0.3044 - val_acc: 0.9054
Epoch 38/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0844 - acc: 0.9714 - val_loss: 0.3850 - val_acc: 0.9119
Epoch 39/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0886 - acc: 0.9676 - val_loss: 0.5572 - val_acc: 0.8542
Epoch 40/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0840 - acc: 0.9711 - val_loss: 0.2993 - val_acc: 0.8942
Epoch 41/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0750 - acc: 0.9735 - val_loss: 0.4058 - val_acc: 0.8638
Epoch 42/200
163/163 [==============================] - 72s 444ms/step - loss: 0.1022 - acc: 0.9647 - val_loss: 0.2314 - val_acc: 0.9167
Epoch 43/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0815 - acc: 0.9732 - val_loss: 0.2841 - val_acc: 0.9087
Epoch 44/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0908 - acc: 0.9691 - val_loss: 0.2970 - val_acc: 0.9103
Epoch 45/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0774 - acc: 0.9734 - val_loss: 0.6785 - val_acc: 0.8478
Epoch 46/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0750 - acc: 0.9739 - val_loss: 0.9743 - val_acc: 0.8574
Epoch 47/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0855 - acc: 0.9693 - val_loss: 0.4456 - val_acc: 0.8942
Epoch 48/200
163/163 [==============================] - 72s 442ms/step - loss: 0.0774 - acc: 0.9751 - val_loss: 0.3492 - val_acc: 0.8910
Epoch 49/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0795 - acc: 0.9737 - val_loss: 0.3073 - val_acc: 0.9279
Epoch 50/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0834 - acc: 0.9716 - val_loss: 1.2500 - val_acc: 0.7436
Epoch 51/200
163/163 [==============================] - 72s 445ms/step - loss: 0.0871 - acc: 0.9705 - val_loss: 0.3336 - val_acc: 0.9295
Epoch 52/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0821 - acc: 0.9734 - val_loss: 0.2058 - val_acc: 0.9295
Epoch 53/200
163/163 [==============================] - 72s 445ms/step - loss: 0.0791 - acc: 0.9735 - val_loss: 0.6662 - val_acc: 0.8462
Epoch 54/200
163/163 [==============================] - 73s 448ms/step - loss: 0.0716 - acc: 0.9734 - val_loss: 0.2185 - val_acc: 0.9215
Epoch 55/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0802 - acc: 0.9712 - val_loss: 0.6663 - val_acc: 0.8830
Epoch 56/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0793 - acc: 0.9753 - val_loss: 0.3920 - val_acc: 0.8670
Epoch 57/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0776 - acc: 0.9734 - val_loss: 0.4089 - val_acc: 0.8766
Epoch 58/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0790 - acc: 0.9695 - val_loss: 0.3444 - val_acc: 0.8910
Epoch 59/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0740 - acc: 0.9720 - val_loss: 0.4419 - val_acc: 0.9054
Epoch 60/200
163/163 [==============================] - 73s 449ms/step - loss: 0.0745 - acc: 0.9764 - val_loss: 0.5010 - val_acc: 0.8958
Epoch 61/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0699 - acc: 0.9772 - val_loss: 0.4766 - val_acc: 0.9006
Epoch 62/200
163/163 [==============================] - 72s 442ms/step - loss: 0.0697 - acc: 0.9749 - val_loss: 0.8292 - val_acc: 0.8462
Epoch 63/200
163/163 [==============================] - 72s 445ms/step - loss: 0.0739 - acc: 0.9741 - val_loss: 0.4147 - val_acc: 0.8958
Epoch 64/200
163/163 [==============================] - 72s 442ms/step - loss: 0.0722 - acc: 0.9755 - val_loss: 0.2326 - val_acc: 0.9071
Epoch 65/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0809 - acc: 0.9743 - val_loss: 0.4066 - val_acc: 0.9006
Epoch 66/200
163/163 [==============================] - 73s 450ms/step - loss: 0.0746 - acc: 0.9760 - val_loss: 0.5318 - val_acc: 0.8862
Epoch 67/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0911 - acc: 0.9682 - val_loss: 0.2885 - val_acc: 0.9167
Epoch 68/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0668 - acc: 0.9734 - val_loss: 0.4621 - val_acc: 0.9022
Epoch 69/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0719 - acc: 0.9778 - val_loss: 0.2723 - val_acc: 0.9119
Epoch 70/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0723 - acc: 0.9741 - val_loss: 0.4349 - val_acc: 0.9038
Epoch 71/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0703 - acc: 0.9768 - val_loss: 0.2337 - val_acc: 0.9423
Epoch 72/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0667 - acc: 0.9774 - val_loss: 0.7337 - val_acc: 0.8333
Epoch 73/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0620 - acc: 0.9751 - val_loss: 0.2635 - val_acc: 0.9263
Epoch 74/200
163/163 [==============================] - 73s 449ms/step - loss: 0.0638 - acc: 0.9780 - val_loss: 0.5949 - val_acc: 0.8718
Epoch 75/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0522 - acc: 0.9824 - val_loss: 0.4579 - val_acc: 0.9151
Epoch 76/200
163/163 [==============================] - 72s 442ms/step - loss: 0.0657 - acc: 0.9766 - val_loss: 0.3629 - val_acc: 0.9054
Epoch 77/200
163/163 [==============================] - 72s 441ms/step - loss: 0.0691 - acc: 0.9762 - val_loss: 0.3167 - val_acc: 0.9103
Epoch 78/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0642 - acc: 0.9801 - val_loss: 0.5793 - val_acc: 0.9119
Epoch 79/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0538 - acc: 0.9818 - val_loss: 0.2613 - val_acc: 0.9295
Epoch 80/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0726 - acc: 0.9768 - val_loss: 1.0666 - val_acc: 0.7997
Epoch 81/200
163/163 [==============================] - 74s 454ms/step - loss: 0.0694 - acc: 0.9768 - val_loss: 0.1556 - val_acc: 0.9471
Epoch 82/200
163/163 [==============================] - 74s 452ms/step - loss: 0.0617 - acc: 0.9804 - val_loss: 0.4113 - val_acc: 0.9119
Epoch 83/200
163/163 [==============================] - 73s 450ms/step - loss: 0.0640 - acc: 0.9768 - val_loss: 0.9435 - val_acc: 0.7516
Epoch 84/200
163/163 [==============================] - 73s 449ms/step - loss: 0.0605 - acc: 0.9814 - val_loss: 0.5066 - val_acc: 0.9295
Epoch 85/200
163/163 [==============================] - 75s 460ms/step - loss: 0.0611 - acc: 0.9791 - val_loss: 0.3030 - val_acc: 0.9167
Epoch 86/200
163/163 [==============================] - 74s 454ms/step - loss: 0.0617 - acc: 0.9780 - val_loss: 0.3004 - val_acc: 0.9423
Epoch 87/200
163/163 [==============================] - 75s 459ms/step - loss: 0.0618 - acc: 0.9793 - val_loss: 0.4998 - val_acc: 0.8990
Epoch 88/200
163/163 [==============================] - 75s 459ms/step - loss: 0.0554 - acc: 0.9810 - val_loss: 0.5951 - val_acc: 0.9054
Epoch 89/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0582 - acc: 0.9787 - val_loss: 0.4343 - val_acc: 0.9263
Epoch 90/200
163/163 [==============================] - 75s 459ms/step - loss: 0.0648 - acc: 0.9803 - val_loss: 0.6596 - val_acc: 0.9119
Epoch 91/200
163/163 [==============================] - 74s 453ms/step - loss: 0.0517 - acc: 0.9795 - val_loss: 1.3327 - val_acc: 0.8077
Epoch 92/200
163/163 [==============================] - 74s 452ms/step - loss: 0.0589 - acc: 0.9804 - val_loss: 0.4059 - val_acc: 0.9343
Epoch 93/200
163/163 [==============================] - 74s 453ms/step - loss: 0.0614 - acc: 0.9783 - val_loss: 0.4128 - val_acc: 0.9343
Epoch 94/200
163/163 [==============================] - 73s 450ms/step - loss: 0.0675 - acc: 0.9787 - val_loss: 0.4541 - val_acc: 0.8990
Epoch 95/200
163/163 [==============================] - 72s 445ms/step - loss: 0.0581 - acc: 0.9812 - val_loss: 0.7484 - val_acc: 0.8910
Epoch 96/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0522 - acc: 0.9814 - val_loss: 0.6151 - val_acc: 0.9087
Epoch 97/200
163/163 [==============================] - 74s 456ms/step - loss: 0.0538 - acc: 0.9797 - val_loss: 0.4730 - val_acc: 0.9038
Epoch 98/200
163/163 [==============================] - 74s 456ms/step - loss: 0.0444 - acc: 0.9883 - val_loss: 0.3920 - val_acc: 0.9022
Epoch 99/200
163/163 [==============================] - 73s 450ms/step - loss: 0.0661 - acc: 0.9764 - val_loss: 0.7270 - val_acc: 0.8413
Epoch 100/200
163/163 [==============================] - 74s 454ms/step - loss: 0.0670 - acc: 0.9778 - val_loss: 0.2479 - val_acc: 0.9343
Epoch 101/200
163/163 [==============================] - 73s 451ms/step - loss: 0.0666 - acc: 0.9778 - val_loss: 0.4366 - val_acc: 0.9263
Epoch 102/200
163/163 [==============================] - 74s 453ms/step - loss: 0.0726 - acc: 0.9757 - val_loss: 0.4169 - val_acc: 0.8734
Epoch 103/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0625 - acc: 0.9806 - val_loss: 0.3161 - val_acc: 0.9343
Epoch 104/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0591 - acc: 0.9810 - val_loss: 0.2928 - val_acc: 0.9151
Epoch 105/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0583 - acc: 0.9789 - val_loss: 0.4112 - val_acc: 0.9279
Epoch 106/200
163/163 [==============================] - 74s 457ms/step - loss: 0.0507 - acc: 0.9826 - val_loss: 0.3524 - val_acc: 0.9327
Epoch 107/200
163/163 [==============================] - 73s 448ms/step - loss: 0.0550 - acc: 0.9827 - val_loss: 0.4181 - val_acc: 0.9327
Epoch 108/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0590 - acc: 0.9816 - val_loss: 0.4764 - val_acc: 0.9006
Epoch 109/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0609 - acc: 0.9812 - val_loss: 0.2803 - val_acc: 0.9311
Epoch 110/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0545 - acc: 0.9822 - val_loss: 0.5962 - val_acc: 0.9119
Epoch 111/200
163/163 [==============================] - 73s 448ms/step - loss: 0.0435 - acc: 0.9829 - val_loss: 0.3823 - val_acc: 0.9455
Epoch 112/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0623 - acc: 0.9812 - val_loss: 0.3012 - val_acc: 0.9103
Epoch 113/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0688 - acc: 0.9768 - val_loss: 0.3091 - val_acc: 0.9407
Epoch 114/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0651 - acc: 0.9826 - val_loss: 0.2426 - val_acc: 0.9439
Epoch 115/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0502 - acc: 0.9824 - val_loss: 0.5341 - val_acc: 0.9215
Epoch 116/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0541 - acc: 0.9803 - val_loss: 0.4946 - val_acc: 0.9151
Epoch 117/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0515 - acc: 0.9829 - val_loss: 1.0058 - val_acc: 0.9231
Epoch 118/200
163/163 [==============================] - 72s 445ms/step - loss: 0.0663 - acc: 0.9812 - val_loss: 0.4794 - val_acc: 0.9103
Epoch 119/200
163/163 [==============================] - 73s 448ms/step - loss: 0.0553 - acc: 0.9789 - val_loss: 0.5463 - val_acc: 0.9167
Epoch 120/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0429 - acc: 0.9868 - val_loss: 0.7003 - val_acc: 0.9343
Epoch 121/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0561 - acc: 0.9833 - val_loss: 0.2364 - val_acc: 0.9375
Epoch 122/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0510 - acc: 0.9824 - val_loss: 1.0754 - val_acc: 0.8590
Epoch 123/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0500 - acc: 0.9852 - val_loss: 0.7891 - val_acc: 0.8574
Epoch 124/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0496 - acc: 0.9810 - val_loss: 0.3457 - val_acc: 0.9391
Epoch 125/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0457 - acc: 0.9850 - val_loss: 0.4399 - val_acc: 0.9407
Epoch 126/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0460 - acc: 0.9864 - val_loss: 0.4191 - val_acc: 0.9455
Epoch 127/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0503 - acc: 0.9820 - val_loss: 0.6112 - val_acc: 0.8462
Epoch 128/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0407 - acc: 0.9831 - val_loss: 0.3097 - val_acc: 0.9391
Epoch 129/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0425 - acc: 0.9845 - val_loss: 0.3677 - val_acc: 0.9359
Epoch 130/200
163/163 [==============================] - 73s 447ms/step - loss: 0.0453 - acc: 0.9854 - val_loss: 0.3655 - val_acc: 0.9295
Epoch 131/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0484 - acc: 0.9833 - val_loss: 0.2098 - val_acc: 0.9375
Epoch 132/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0358 - acc: 0.9872 - val_loss: 0.8005 - val_acc: 0.9119
Epoch 133/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0448 - acc: 0.9839 - val_loss: 0.5663 - val_acc: 0.9022
Epoch 134/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0627 - acc: 0.9758 - val_loss: 0.4680 - val_acc: 0.9343
Epoch 135/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0544 - acc: 0.9831 - val_loss: 0.5571 - val_acc: 0.9311
Epoch 136/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0537 - acc: 0.9826 - val_loss: 0.7005 - val_acc: 0.9423
Epoch 137/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0440 - acc: 0.9854 - val_loss: 2.0090 - val_acc: 0.9119
Epoch 138/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0417 - acc: 0.9852 - val_loss: 0.7383 - val_acc: 0.9375
Epoch 139/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0480 - acc: 0.9854 - val_loss: 0.3192 - val_acc: 0.9359
Epoch 140/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0358 - acc: 0.9866 - val_loss: 0.5542 - val_acc: 0.9327
Epoch 141/200
163/163 [==============================] - 72s 445ms/step - loss: 0.0402 - acc: 0.9866 - val_loss: 0.5446 - val_acc: 0.9215
Epoch 142/200
163/163 [==============================] - 72s 445ms/step - loss: 0.0404 - acc: 0.9852 - val_loss: 1.0535 - val_acc: 0.8702
Epoch 143/200
163/163 [==============================] - 73s 445ms/step - loss: 0.0460 - acc: 0.9837 - val_loss: 2.5159 - val_acc: 0.9231
Epoch 144/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0482 - acc: 0.9829 - val_loss: 0.4665 - val_acc: 0.9311
Epoch 145/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0537 - acc: 0.9820 - val_loss: 0.2765 - val_acc: 0.9263
Epoch 146/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0509 - acc: 0.9822 - val_loss: 1.2889 - val_acc: 0.9119
Epoch 147/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0407 - acc: 0.9873 - val_loss: 0.6174 - val_acc: 0.9391
Epoch 148/200
163/163 [==============================] - 73s 446ms/step - loss: 0.0509 - acc: 0.9818 - val_loss: 0.2844 - val_acc: 0.9359
Epoch 149/200
163/163 [==============================] - 72s 444ms/step - loss: 0.0563 - acc: 0.9816 - val_loss: 0.8779 - val_acc: 0.9359
Epoch 150/200
163/163 [==============================] - 72s 442ms/step - loss: 0.0503 - acc: 0.9827 - val_loss: 0.7962 - val_acc: 0.9391
Epoch 151/200
163/163 [==============================] - 72s 443ms/step - loss: 0.0387 - acc: 0.9862 - val_loss: 0.3484 - val_acc: 0.9535
In [38]:
# PLOT LOSS AND ACCURACY

#import matplotlib.image  as mpimg
#import matplotlib.pyplot as plt
#-----------------------------------------------------------
# Retrieve a list of list results on training and test data
# sets for each training epoch
#-----------------------------------------------------------
acc=history.history['acc']
val_acc=history.history['val_acc']
loss=history.history['loss']
val_loss=history.history['val_loss']

epochs=np.arange(len(acc)) # Get number of epochs

#------------------------------------------------
# Plot training and validation accuracy per epoch
#------------------------------------------------
plt.figure()
plt.plot(epochs, acc, 'r', label="Training Accuracy")
plt.plot(epochs, val_acc, 'b', label="Validation Accuracy")
plt.xlabel("Epochs")
plt.ylabel("Acc.")
plt.title('Training and validation accuracy')
plt.legend()
#------------------------------------------------
# Plot training and validation loss per epoch
#------------------------------------------------
plt.figure()
plt.plot(epochs, loss, 'r', label="Training Loss")
plt.plot(epochs, val_loss, 'b', label="Validation Loss")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend()

plt.title('Training and validation loss')

# Desired output. Charts with training and validation metrics. No crash :)
Out[38]:
Text(0.5, 1.0, 'Training and validation loss')

Save and Load best model

In [39]:
# serialize model to JSON
model_json = model.to_json()
with open(pathlib.Path(os.path.join(current_working_dir,'model','best_model','model_structure.json')), "w") as json_file:
    json_file.write(model_json)
In [40]:
# load json and create model
json_file = open(pathlib.Path(os.path.join(current_working_dir, 'model', 'best_model', 'model_structure.json')), 'r')
loaded_model_json = json_file.read()
json_file.close()

loaded_model = model_from_json(loaded_model_json)
# load weights into new model
loaded_model.load_weights(pathlib.Path(os.path.join(
    current_working_dir, 'model', 'best_model', modelfname)
)
)

print("Loaded model from disk")

# use loaded model on test data
# loaded_model.compile(loss='binary_crossentropy',
#                      optimizer='rmsprop', metrics=['accuracy'])
Loaded model from disk

Predictions

Test on validation set images

In [41]:
image_batch, label_batch = test_ds.next()
i = random.randint(0,batch_size-1)

img_array = keras.preprocessing.image.img_to_array(image_batch[i])
img_array = tf.expand_dims(img_array, 0)  # Create batch axis

true_class = int(label_batch[i])
predicted_class = model.predict_classes(img_array)[0]
predictions = model.predict(img_array)
score = predictions.flatten()[0]

print("Prediction: ",class_names[predicted_class[0]], " | Truth: ", class_names[true_class])
print("Score: ", np.round(score,2))

plt.figure()
plt.imshow(image_batch[i])
plt.title(class_names[true_class])
plt.axis("off")
Prediction:  PNEUMONIA  | Truth:  PNEUMONIA
Score:  1.0
Out[41]:
(-0.5, 224.5, 224.5, -0.5)

Conclusions

We have developed Convolutional Neural Network based model without using any pre-trained models or transfer learning methods. This model achieves accuracy of ~95% on test dataset. Notice that the validation loss curve fluctuates a lot around a more or less constant average value. Whereas the training loss improves slowly. This is a clear indication of over fitting. The orver fitting is suppressed quite a bit by using dropouts. There is still some opportunity to improve the model.

In [ ]: