Prepare the dataset

  • keras has an efficient support for images, but we need to prepare all the images in a special structure:
    • Separate images in training and test folder.
    • In order to differentiate the class labels, the simple trick is to make two different folders: one for dog and one for cat. Keras will understand how to differentiate the labels of our independent variables.

In [1]:
# List all directories and sub-directories
!find ./Convolutional_Neural_Networks/dataset -type d -maxdepth 5


find: warning: you have specified the -maxdepth option after a non-option argument -type, but options are not positional (-maxdepth affects tests specified before it as well as those specified after it).  Please specify options before other arguments.

./Convolutional_Neural_Networks/dataset
./Convolutional_Neural_Networks/dataset/training_set
./Convolutional_Neural_Networks/dataset/training_set/dogs
./Convolutional_Neural_Networks/dataset/training_set/cats
./Convolutional_Neural_Networks/dataset/test_set
./Convolutional_Neural_Networks/dataset/test_set/dogs
./Convolutional_Neural_Networks/dataset/test_set/cats
./Convolutional_Neural_Networks/dataset/single_prediction

Building the CNN


In [2]:
# Importing the Keras libraries and packages
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import Flatten
from keras.layers import Dense


Using TensorFlow backend.

In [3]:
# Initialising the CNN
classifier = Sequential()

# Step 1 - Convolution
classifier.add(Conv2D(32, (3, 3), input_shape = (64, 64, 3), activation = 'relu'))

# Step 2 - Pooling
classifier.add(MaxPooling2D(pool_size = (2, 2)))

# Adding a second convolutional layer
classifier.add(Conv2D(32, (3, 3), activation = 'relu'))
classifier.add(MaxPooling2D(pool_size = (2, 2)))

# Step 3 - Flattening
classifier.add(Flatten())

# Step 4 - Full connection
classifier.add(Dense(units = 128, activation = 'relu'))
classifier.add(Dense(units = 1, activation = 'sigmoid'))

# Compiling the CNN
classifier.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy'])

Fitting the CNN to the images

  • We only have 10,000 images, that is not enough to make great performance results. We can either get more images or use the trick of data augmentation:
    • Image augmentation will create many batches of our images and then each batch, it will apply some random transformation on a random selection of our images like: rotating, flipping, shifting, shearing them. Eventually, in the training process, we have more diverse images inside these batches.
    • Image augmentation trick can help reduce overfitting.
    • ImageDataGenerator

In [4]:
from keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(rescale = 1./255,
                                   shear_range = 0.2,
                                   zoom_range = 0.2,
                                   horizontal_flip = True)

test_datagen = ImageDataGenerator(rescale = 1./255)

training_set = train_datagen.flow_from_directory('Convolutional_Neural_Networks/dataset/training_set',
                                                 target_size = (64, 64),
                                                 batch_size = 32,
                                                 class_mode = 'binary')

test_set = test_datagen.flow_from_directory('Convolutional_Neural_Networks/dataset/test_set',
                                            target_size = (64, 64),
                                            batch_size = 32,
                                            class_mode = 'binary')

classifier.fit_generator(training_set,
                         steps_per_epoch = 8000,
                         epochs = 25,
                         validation_data = test_set,
                         validation_steps = 2000)


Found 8000 images belonging to 2 classes.
Found 2000 images belonging to 2 classes.
Epoch 1/25
8000/8000 [==============================] - 1069s - loss: 0.3867 - acc: 0.8178 - val_loss: 0.4384 - val_acc: 0.8270
Epoch 2/25
8000/8000 [==============================] - 1069s - loss: 0.1794 - acc: 0.9270 - val_loss: 0.5647 - val_acc: 0.8240
Epoch 3/25
8000/8000 [==============================] - 1068s - loss: 0.1085 - acc: 0.9579 - val_loss: 0.7012 - val_acc: 0.8261
Epoch 4/25
8000/8000 [==============================] - 1069s - loss: 0.0801 - acc: 0.9703 - val_loss: 0.7968 - val_acc: 0.8211
Epoch 5/25
8000/8000 [==============================] - 1067s - loss: 0.0638 - acc: 0.9768 - val_loss: 0.8670 - val_acc: 0.8211
Epoch 6/25
8000/8000 [==============================] - 1068s - loss: 0.0551 - acc: 0.9802 - val_loss: 0.9107 - val_acc: 0.8256
Epoch 7/25
8000/8000 [==============================] - 1067s - loss: 0.0474 - acc: 0.9831 - val_loss: 1.0068 - val_acc: 0.8128
Epoch 8/25
8000/8000 [==============================] - 1069s - loss: 0.0417 - acc: 0.9850 - val_loss: 0.9306 - val_acc: 0.8274
Epoch 9/25
8000/8000 [==============================] - 1068s - loss: 0.0392 - acc: 0.9861 - val_loss: 0.9454 - val_acc: 0.8234
Epoch 10/25
8000/8000 [==============================] - 1069s - loss: 0.0344 - acc: 0.9879 - val_loss: 1.0476 - val_acc: 0.8194
Epoch 11/25
8000/8000 [==============================] - 1069s - loss: 0.0318 - acc: 0.9891 - val_loss: 0.9880 - val_acc: 0.8267
Epoch 12/25
8000/8000 [==============================] - 1069s - loss: 0.0284 - acc: 0.9904 - val_loss: 1.1262 - val_acc: 0.8189
Epoch 13/25
8000/8000 [==============================] - 1069s - loss: 0.0269 - acc: 0.9907 - val_loss: 1.0884 - val_acc: 0.8300
Epoch 14/25
8000/8000 [==============================] - 1069s - loss: 0.0252 - acc: 0.9912 - val_loss: 1.0410 - val_acc: 0.8319
Epoch 15/25
8000/8000 [==============================] - 1069s - loss: 0.0234 - acc: 0.9919 - val_loss: 1.0591 - val_acc: 0.8222
Epoch 16/25
8000/8000 [==============================] - 1068s - loss: 0.0222 - acc: 0.9925 - val_loss: 1.1519 - val_acc: 0.8200
Epoch 17/25
8000/8000 [==============================] - 1070s - loss: 0.0216 - acc: 0.9927 - val_loss: 1.1189 - val_acc: 0.8292
Epoch 18/25
8000/8000 [==============================] - 1070s - loss: 0.0203 - acc: 0.9930 - val_loss: 1.1424 - val_acc: 0.8176
Epoch 19/25
8000/8000 [==============================] - 1072s - loss: 0.0192 - acc: 0.9935 - val_loss: 1.1804 - val_acc: 0.8249
Epoch 20/25
8000/8000 [==============================] - 1064s - loss: 0.0178 - acc: 0.9939 - val_loss: 1.2060 - val_acc: 0.8269
Epoch 21/25
8000/8000 [==============================] - 1064s - loss: 0.0179 - acc: 0.9940 - val_loss: 1.1506 - val_acc: 0.8290
Epoch 22/25
8000/8000 [==============================] - 1063s - loss: 0.0173 - acc: 0.9942 - val_loss: 1.1783 - val_acc: 0.8270
Epoch 23/25
8000/8000 [==============================] - 1062s - loss: 0.0170 - acc: 0.9945 - val_loss: 1.1579 - val_acc: 0.8296
Epoch 24/25
8000/8000 [==============================] - 1062s - loss: 0.0159 - acc: 0.9946 - val_loss: 1.1559 - val_acc: 0.8237
Epoch 25/25
8000/8000 [==============================] - 1064s - loss: 0.0159 - acc: 0.9946 - val_loss: 1.2083 - val_acc: 0.8229
Out[4]:
<keras.callbacks.History at 0x7f6d24839e80>

Making new predictions


In [6]:
import numpy as np
from keras.preprocessing import image
test_image = image.load_img('Convolutional_Neural_Networks/dataset/single_prediction/cat_or_dog_1.jpg', 
                            target_size = (64, 64))
test_image = image.img_to_array(test_image)
test_image = np.expand_dims(test_image, axis = 0)
result = classifier.predict(test_image)
training_set.class_indices
if result[0][0] == 1:
    prediction = 'dog'
else:
    prediction = 'cat'
    
print (prediction)


dog

Challenge

Evaluation was already made during the training with the validation set, therefore k-Fold Cross Validation is not needed.

Then the techniques to improve and tune a CNN model are the same as for ANNs. So here is the challenge:

Apply the techniques you've learnt and use your architect power to make a CNN that will give you the gold medal:

  • Bronze medal: Test set accuracy between 80% and 85%
  • Silver medal: Test set accuracy between 85% and 90%
  • Gold medal: Test set accuracy over 90%!!

Rules:

  • Use the same training set
  • Dropout allowed
  • Customized SGD allowed
  • Specific seeds not allowed

Better solution


In [ ]:
from tensorflow.contrib.keras.api.keras.layers import Dropout
from tensorflow.contrib.keras.api.keras.models import Sequential
from tensorflow.contrib.keras.api.keras.layers import Conv2D
from tensorflow.contrib.keras.api.keras.layers import MaxPooling2D
from tensorflow.contrib.keras.api.keras.layers import Flatten
from tensorflow.contrib.keras.api.keras.layers import Dense
from tensorflow.contrib.keras.api.keras.callbacks import Callback
from tensorflow.contrib.keras.api.keras.preprocessing.image import ImageDataGenerator
from tensorflow.contrib.keras import backend
import os
 
 
class LossHistory(Callback):
    def __init__(self):
        super().__init__()
        self.epoch_id = 0
        self.losses = ''
 
    def on_epoch_end(self, epoch, logs={}):
        self.losses += "Epoch {}: accuracy -> {:.4f}, val_accuracy -> {:.4f}\n"\
            .format(str(self.epoch_id), logs.get('acc'), logs.get('val_acc'))
        self.epoch_id += 1
 
    def on_train_begin(self, logs={}):
        self.losses += 'Training begins...\n'
 
script_dir = os.path.dirname(__file__)
training_set_path = os.path.join(script_dir, '../dataset/training_set')
test_set_path = os.path.join(script_dir, '../dataset/test_set')
 
# Initialising the CNN
classifier = Sequential()
 
# Step 1 - Convolution
input_size = (128, 128)
classifier.add(Conv2D(32, (3, 3), input_shape=(*input_size, 3), activation='relu'))
 
# Step 2 - Pooling
classifier.add(MaxPooling2D(pool_size=(2, 2)))  # 2x2 is optimal
 
# Adding a second convolutional layer
classifier.add(Conv2D(32, (3, 3), activation='relu'))
classifier.add(MaxPooling2D(pool_size=(2, 2)))
 
# Adding a third convolutional layer
classifier.add(Conv2D(64, (3, 3), activation='relu'))
classifier.add(MaxPooling2D(pool_size=(2, 2)))
 
# Step 3 - Flattening
classifier.add(Flatten())
 
# Step 4 - Full connection
classifier.add(Dense(units=64, activation='relu'))
classifier.add(Dropout(0.5))
classifier.add(Dense(units=1, activation='sigmoid'))
 
# Compiling the CNN
classifier.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
 
# Part 2 - Fitting the CNN to the images
batch_size = 32
train_datagen = ImageDataGenerator(rescale=1. / 255,
                                   shear_range=0.2,
                                   zoom_range=0.2,
                                   horizontal_flip=True)
 
test_datagen = ImageDataGenerator(rescale=1. / 255)
 
training_set = train_datagen.flow_from_directory(training_set_path,
                                                 target_size=input_size,
                                                 batch_size=batch_size,
                                                 class_mode='binary')
 
test_set = test_datagen.flow_from_directory(test_set_path,
                                            target_size=input_size,
                                            batch_size=batch_size,
                                            class_mode='binary')
 
# Create a loss history
history = LossHistory()
 
classifier.fit_generator(training_set,
                         steps_per_epoch=8000/batch_size,
                         epochs=90,
                         validation_data=test_set,
                         validation_steps=2000/batch_size,
                         workers=12,
                         max_q_size=100,
                         callbacks=[history])
 
 
# Save model
model_backup_path = os.path.join(script_dir, '../dataset/cat_or_dogs_model.h5')
classifier.save(model_backup_path)
print("Model saved to", model_backup_path)
 
# Save loss history to file
loss_history_path = os.path.join(script_dir, '../loss_history.log')
myFile = open(loss_history_path, 'w+')
myFile.write(history.losses)
myFile.close()
 
backend.clear_session()
print("The model class indices are:", training_set.class_indices)