Machine Learning Engineer Nanodegree

Deep Learning

📑   Practice Project 4: Convolutional Neural Networks

In this notebook, we use transfer learning to train a CNN to classify dog breeds.

1. Load Dog Dataset

Before running the code cell below, download the dataset of dog images here and place it in the respository.

In [1]:
%%html
<style>
@import url('https://fonts.googleapis.com/css?family=Orbitron|Roboto');
body {background-color: #add8e6;} 
a {color: darkblue; font-family: 'Roboto';} 
h1 {color: steelblue; font-family: 'Orbitron'; text-shadow: 4px 4px 4px #aaa;} 
h2, h3 {color: #483d8b; font-family: 'Orbitron'; text-shadow: 4px 4px 4px #aaa;}
h4 {color: slategray; font-family: 'Roboto';}
span {text-shadow: 4px 4px 4px #ccc;}
div.output_prompt, div.output_area pre {color: #483d8b;}
div.input_prompt, div.output_subarea {color: darkblue;}      
div.output_stderr pre {background-color: #add8e6;}  
div.output_stderr {background-color: #483d8b;}        
</style>
In [6]:
from sklearn.datasets import load_files       
from keras.utils import np_utils
import numpy as np
from glob import glob

# define function to load train, test, and validation datasets
def load_dataset(path):
    data = load_files(path)
    dog_files = np.array(data['filenames'])
    dog_targets = np_utils.to_categorical(np.array(data['target']), 133)
    return dog_files, dog_targets

# load train, test, and validation datasets
path = '/Users/olgabelitskaya/projects/nd009/Machine_Learning_Engineer_ND_P10/'
train_files, train_targets = load_dataset(path+'dogImages/train')
valid_files, valid_targets = load_dataset(path+'dogImages/valid')
test_files, test_targets = load_dataset(path+'dogImages/test')

# load ordered list of dog names
dog_names = [item[25:-1] for item in glob(path+'dogImages/train/*/')]

# print statistics about the dataset
print('There are %d total dog categories.' % len(dog_names))
print('There are %s total dog images.\n' % str(len(train_files) + len(valid_files) + len(test_files)))
print('There are %d training dog images.' % len(train_files))
print('There are %d validation dog images.' % len(valid_files))
print('There are %d test dog images.'% len(test_files))
There are 133 total dog categories.
There are 8351 total dog images.

There are 6680 training dog images.
There are 835 validation dog images.
There are 836 test dog images.

2. Visualize the First 12 Training Images

In [7]:
import cv2
import matplotlib.pyplot as plt
%matplotlib inline

def visualize_img(img_path, ax):
    img = cv2.imread(img_path)
    ax.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    
fig = plt.figure(figsize=(20, 10))
for i in range(12):
    ax = fig.add_subplot(3, 4, i + 1, xticks=[], yticks=[])
    visualize_img(train_files[i], ax)

3. Obtain the VGG-16 Bottleneck Features

Before running the code cell below, download the file linked here and place it in the bottleneck_features/ folder.

In [8]:
bottleneck_features = np.load(path+'bottleneck_features/DogVGG16Data.npz')
train_vgg16 = bottleneck_features['train']
valid_vgg16 = bottleneck_features['valid']
test_vgg16 = bottleneck_features['test']

4. Define a Model Architecture (Model 1)

In [44]:
from keras.layers import Dense, Flatten, Dropout
from keras.models import Sequential

model = Sequential()
model.add(Flatten(input_shape=(7, 7, 512)))
model.add(Dense(133, activation='softmax'))
model.compile(loss='categorical_crossentropy', 
              optimizer='nadam', 
              metrics=['accuracy'])
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten_8 (Flatten)          (None, 25088)             0         
_________________________________________________________________
dense_25 (Dense)             (None, 133)               3336837   
=================================================================
Total params: 3,336,837
Trainable params: 3,336,837
Non-trainable params: 0
_________________________________________________________________

5. Define another Model Architecture (Model 2)

In [45]:
from keras.layers import GlobalAveragePooling2D

model = Sequential()
model.add(GlobalAveragePooling2D(input_shape=(7, 7, 512)))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(133, activation='softmax'))
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_8 ( (None, 512)               0         
_________________________________________________________________
dense_26 (Dense)             (None, 512)               262656    
_________________________________________________________________
dropout_10 (Dropout)         (None, 512)               0         
_________________________________________________________________
dense_27 (Dense)             (None, 256)               131328    
_________________________________________________________________
dropout_11 (Dropout)         (None, 256)               0         
_________________________________________________________________
dense_28 (Dense)             (None, 133)               34181     
=================================================================
Total params: 428,165
Trainable params: 428,165
Non-trainable params: 0
_________________________________________________________________

6. Compile the Model (Model 2)

In [46]:
model.compile(loss='categorical_crossentropy', 
              optimizer='nadam', 
              metrics=['accuracy'])

7. Train the Model (Model 2)

In [47]:
from keras.callbacks import ModelCheckpoint   

# train the model
checkpointer = ModelCheckpoint(filepath='dogvgg16.weights.best.hdf5', verbose=1, 
                               save_best_only=True)
model.fit(train_vgg16, train_targets, epochs=20, validation_data=(valid_vgg16, valid_targets), 
          callbacks=[checkpointer], verbose=2, shuffle=True);
Train on 6680 samples, validate on 835 samples
Epoch 1/20
Epoch 00000: val_loss improved from inf to 1.76245, saving model to dogvgg16.weights.best.hdf5
6s - loss: 3.9243 - acc: 0.2129 - val_loss: 1.7625 - val_acc: 0.5281
Epoch 2/20
Epoch 00001: val_loss improved from 1.76245 to 1.22034, saving model to dogvgg16.weights.best.hdf5
4s - loss: 1.8610 - acc: 0.4922 - val_loss: 1.2203 - val_acc: 0.6455
Epoch 3/20
Epoch 00002: val_loss improved from 1.22034 to 1.13482, saving model to dogvgg16.weights.best.hdf5
4s - loss: 1.4723 - acc: 0.5817 - val_loss: 1.1348 - val_acc: 0.6587
Epoch 4/20
Epoch 00003: val_loss improved from 1.13482 to 1.13443, saving model to dogvgg16.weights.best.hdf5
4s - loss: 1.2753 - acc: 0.6352 - val_loss: 1.1344 - val_acc: 0.6515
Epoch 5/20
Epoch 00004: val_loss improved from 1.13443 to 1.01131, saving model to dogvgg16.weights.best.hdf5
4s - loss: 1.1392 - acc: 0.6674 - val_loss: 1.0113 - val_acc: 0.6982
Epoch 6/20
Epoch 00005: val_loss did not improve
4s - loss: 1.0571 - acc: 0.6883 - val_loss: 1.0864 - val_acc: 0.6910
Epoch 7/20
Epoch 00006: val_loss did not improve
4s - loss: 0.9844 - acc: 0.7120 - val_loss: 1.0524 - val_acc: 0.7042
Epoch 8/20
Epoch 00007: val_loss improved from 1.01131 to 1.00572, saving model to dogvgg16.weights.best.hdf5
4s - loss: 0.9546 - acc: 0.7223 - val_loss: 1.0057 - val_acc: 0.7222
Epoch 9/20
Epoch 00008: val_loss did not improve
4s - loss: 0.8846 - acc: 0.7373 - val_loss: 1.0749 - val_acc: 0.6886
Epoch 10/20
Epoch 00009: val_loss did not improve
4s - loss: 0.8307 - acc: 0.7460 - val_loss: 1.1931 - val_acc: 0.6994
Epoch 11/20
Epoch 00010: val_loss did not improve
4s - loss: 0.8825 - acc: 0.7382 - val_loss: 1.0522 - val_acc: 0.7138
Epoch 12/20
Epoch 00011: val_loss did not improve
4s - loss: 0.8498 - acc: 0.7575 - val_loss: 1.2286 - val_acc: 0.6922
Epoch 13/20
Epoch 00012: val_loss did not improve
4s - loss: 0.8145 - acc: 0.7656 - val_loss: 1.1259 - val_acc: 0.6934
Epoch 14/20
Epoch 00013: val_loss did not improve
4s - loss: 0.7954 - acc: 0.7749 - val_loss: 1.2013 - val_acc: 0.6814
Epoch 15/20
Epoch 00014: val_loss did not improve
4s - loss: 0.8605 - acc: 0.7702 - val_loss: 1.1887 - val_acc: 0.7138
Epoch 16/20
Epoch 00015: val_loss did not improve
4s - loss: 0.7948 - acc: 0.7820 - val_loss: 1.2054 - val_acc: 0.7006
Epoch 17/20
Epoch 00016: val_loss did not improve
4s - loss: 0.7712 - acc: 0.7906 - val_loss: 1.1083 - val_acc: 0.7234
Epoch 18/20
Epoch 00017: val_loss did not improve
4s - loss: 0.7732 - acc: 0.7900 - val_loss: 1.1477 - val_acc: 0.7126
Epoch 19/20
Epoch 00018: val_loss did not improve
4s - loss: 0.7895 - acc: 0.7859 - val_loss: 1.1950 - val_acc: 0.7198
Epoch 20/20
Epoch 00019: val_loss did not improve
4s - loss: 0.7386 - acc: 0.7973 - val_loss: 1.1647 - val_acc: 0.7162

8. Load the Model with the Best Validation Accuracy (Model 2)

In [48]:
# load the weights that yielded the best validation accuracy
model.load_weights('dogvgg16.weights.best.hdf5')

9. Calculate Classification Accuracy on Test Set (Model 2)

In [49]:
# get index of predicted dog breed for each image in test set
vgg16_predictions = [np.argmax(model.predict(np.expand_dims(feature, axis=0))) 
                     for feature in test_vgg16]

# report test accuracy
test_accuracy = 100*np.sum(np.array(vgg16_predictions)==
                           np.argmax(test_targets, axis=1))/len(vgg16_predictions)
print('\nTest accuracy: %.4f%%' % test_accuracy)
Test accuracy: 72.8469%