Recognizing Dog Breeds

This project creates an algorithm for recognizing dog breeds using Keras and TensorFlow. If a dog is detected in the image, it will provide a guess at the dog's breed. If a human face is detected, it will provide an output of the dog breed that most resembles the human face. I completed this project as part of Udacity's Machine Learning Nanodegree.

Import Dog Dataset

To get started, I first imported a dataset of dog images.

In [1]:
from sklearn.datasets import load_files       
from keras.utils import np_utils
import numpy as np
from glob import glob

#defines 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

#loads train, test, and validation datasets
train_files, train_targets = load_dataset('dogImages/train')
valid_files, valid_targets = load_dataset('dogImages/valid')
test_files, test_targets = load_dataset('dogImages/test')

#loads list of dog names
dog_names = [item[20:-1] for item in sorted(glob("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' % len(np.hstack([train_files, valid_files, 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))
Using TensorFlow backend.
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.

Import Human Dataset

Next, I imported a dataset of human images, where the file paths are stored in a numpy array called human_files.

In [2]:
import random
random.seed(8675309)

#loads filenames in shuffled human dataset
human_files = np.array(glob("lfw/*/*"))
random.shuffle(human_files)

#prints statistics about the dataset
print('There are %d total human images.' % len(human_files))
There are 13233 total human images.

Detecting Humans

To detect human faces in the images, I used OpenCV's implementation of Haar feature-based cascade classifiers to detect human faces in images. There are several pre-trained face detectors on OpenCV (https://github.com/opencv/opencv/tree/master/data/haarcascades). I used the frontal face detector. In order to use the OpenCV face detectors, the images must be converted to grayscale.

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

#extracts a pre-trained face detector
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_alt.xml')

#loads color (BGR) image
img = cv2.imread(human_files[3])
#converts BGR image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#finds faces in image
faces = face_cascade.detectMultiScale(gray)

#prints number of faces detected in the image
print('Number of faces detected:', len(faces))

#gets bounding box for each detected face
for (x,y,w,h) in faces:
    # add bounding box to color image
    cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
    
#converts BGR image to RGB for plotting
cv_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

#displays the image, along with bounding box
plt.imshow(cv_rgb)
plt.show()
Number of faces detected: 1

Human Face Detector Function

Next, I used the face dectector algorithm to create a function that outputs TRUE if a face is detected in the image.

In [4]:
#function that returns "True" if a face is detected in image stored at img_path
def face_detector(img_path):
    img = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray)
    return len(faces) > 0

Assess the Human Face Detector

Next, I assessed the human face detection algorithm on a sample of 100 human and dog images. The output shows that the algorithm works well for human faces at 99% accuracy but does classify 11% of dog images as humans.

In [5]:
human_files_short = human_files[:100]
dog_files_short = train_files[:100]

## Tests the performance of the face_detector algorithm 
humans_in_humans = 0
for human in human_files_short:
    if face_detector(human):
        humans_in_humans += 1
        
humans_in_dogs = 0
for dog in dog_files_short:
    if face_detector(dog):
        humans_in_dogs += 1

print ("Percent of humans faces detected in humans is {}".format(humans_in_humans))
print ("Percent of humans faces detected in dogs is {}".format(humans_in_dogs))
Percent of humans faces detected in humans is 99
Percent of humans faces detected in dogs is 11

Detect Dogs

To detect dogs, I leveraged a pre-trained ResNet-50 model to detect dogs in images. The ResNet model has been trained on ImageNet. ImageNet is a very popular dataset used for image classification and other vision applications. This pre-trained ResNet model will return a prediction for the object that is contained in the image.

In [6]:
from keras.applications.resnet50 import ResNet50

#defines ResNet50 model
ResNet50_model = ResNet50(weights='imagenet')
In [7]:
from keras.preprocessing import image                  
from tqdm import tqdm

def path_to_tensor(img_path):
    #loads RGB image as PIL.Image.Image type
    img = image.load_img(img_path, target_size=(224, 224))
    #converts PIL.Image.Image type to 3D tensor with shape (224, 224, 3)
    x = image.img_to_array(img)
    #converts 3D tensor to 4D tensor with shape (1, 224, 224, 3) and return 4D tensor
    return np.expand_dims(x, axis=0)

def paths_to_tensor(img_paths):
    list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)]
    return np.vstack(list_of_tensors)
In [8]:
from keras.applications.resnet50 import preprocess_input, decode_predictions

def ResNet50_predict_labels(img_path):
    # returns prediction vector for image located at img_path
    img = preprocess_input(path_to_tensor(img_path))
    return np.argmax(ResNet50_model.predict(img))

Write a Dog Detector

Next, I created a function that detects if a dog is in the image. From the ImageNet dictionary, dog keys are 151-268 and include all dog categories. If the ResNet50 model returns a value in the number range, a dog is in the image.

In [9]:
###returns "True" if a dog is detected in the image stored at img_path
def dog_detector(img_path):
    prediction = ResNet50_predict_labels(img_path)
    return ((prediction <= 268) & (prediction >= 151)) 

Assess the Dog Detector

After creating the function, I will assess my dog detector function in the same way I did for the human face detector. From the output, the dog detector performs well detecting 100% of the dogs and incorrectly classifying only 1% of the human faces.

In [10]:
dogs_in_humans = 0
for human in human_files_short:
    if dog_detector(human):
        dogs_in_humans += 1
        
dogs_in_dogs = 0
for dog in dog_files_short:
    if dog_detector(dog):
        dogs_in_dogs += 1

print ("Percent of dogs detected in humans is {}".format(dogs_in_humans))
print ("Percent of dogs detected in dogs is {}".format(dogs_in_dogs))
Percent of dogs detected in humans is 1
Percent of dogs detected in dogs is 100

Transfer Learning to Create a CNN for Dog Breed Detection

After creating my dog and face detection algorithms, I used transfer learning to create a convolutional neural network that can then identify dog breeds. I leveraged a pre-trained InceptionV3 model and its bottleneck features as the baseline. Below is the initial setup.

In [11]:
from PIL import ImageFile                            
ImageFile.LOAD_TRUNCATED_IMAGES = True                 

# pre-process the data for Keras
train_tensors = paths_to_tensor(train_files).astype('float32')/255
valid_tensors = paths_to_tensor(valid_files).astype('float32')/255
test_tensors = paths_to_tensor(test_files).astype('float32')/255
100%|██████████████████████████████████████████████████████████████████████████████| 6680/6680 [01:59<00:00, 55.83it/s]
100%|████████████████████████████████████████████████████████████████████████████████| 835/835 [00:15<00:00, 81.09it/s]
100%|████████████████████████████████████████████████████████████████████████████████| 836/836 [00:10<00:00, 76.78it/s]
In [12]:
from keras.callbacks import ModelCheckpoint  
from keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D, Dropout, Flatten, Dense
from keras.models import Sequential
In [14]:
### Obtains bottleneck features from the pre-trained Inception V3 CNN.
bottleneck_features = np.load('bottleneck_features/DogInceptionV3Data.npz')
train_InceptionV3 = bottleneck_features['train']
valid_InceptionV3 = bottleneck_features['valid']
test_InceptionV3 = bottleneck_features['test']
In [15]:
### Defines the architecture.
inceptionV3_model = Sequential()
inceptionV3_model.add(GlobalAveragePooling2D(input_shape=train_InceptionV3.shape[1:]))
inceptionV3_model.add(Dense(133, activation='softmax'))

inceptionV3_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_1 ( (None, 2048)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 133)               272517    
=================================================================
Total params: 272,517.0
Trainable params: 272,517.0
Non-trainable params: 0.0
_________________________________________________________________

Compile and Train the Model

In [16]:
###Compiles the model.
inceptionV3_model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
In [17]:
###Trains the model.
from keras.callbacks import ModelCheckpoint  
checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.inceptionV3.hdf5', 
                               verbose=1, save_best_only=True)

inceptionV3_model.fit(train_InceptionV3, train_targets, 
          validation_data=(valid_InceptionV3, valid_targets),
          epochs=5, batch_size=20, callbacks=[checkpointer], verbose=2)
Train on 6680 samples, validate on 835 samples
Epoch 1/5
Epoch 00000: val_loss improved from inf to 0.69028, saving model to saved_models/weights.best.inceptionV3.hdf5
13s - loss: 1.1640 - acc: 0.7088 - val_loss: 0.6903 - val_acc: 0.8096
Epoch 2/5
Epoch 00001: val_loss improved from 0.69028 to 0.62049, saving model to saved_models/weights.best.inceptionV3.hdf5
5s - loss: 0.4813 - acc: 0.8551 - val_loss: 0.6205 - val_acc: 0.8299
Epoch 3/5
Epoch 00002: val_loss did not improve
5s - loss: 0.3623 - acc: 0.8862 - val_loss: 0.6373 - val_acc: 0.8419
Epoch 4/5
Epoch 00003: val_loss did not improve
4s - loss: 0.2895 - acc: 0.9111 - val_loss: 0.6288 - val_acc: 0.8515
Epoch 5/5
Epoch 00004: val_loss did not improve
5s - loss: 0.2402 - acc: 0.9234 - val_loss: 0.7268 - val_acc: 0.8431
Out[17]:
<keras.callbacks.History at 0x19217907160>
In [18]:
###Loads the model weights with the best validation loss.
inceptionV3_model.load_weights('saved_models/weights.best.inceptionV3.hdf5')

Test the Model

After training my model, I tested the model on the test dataset of dog images. The result was 79%.

In [19]:
### Calculates classification accuracy on the test dataset.
inceptionV3_predictions = [np.argmax(inceptionV3_model.predict(np.expand_dims(feature, axis=0))) for feature in test_InceptionV3]

# reports test accuracy
test_accuracy = 100*np.sum(np.array(inceptionV3_predictions)==np.argmax(test_targets, axis=1))/len(inceptionV3_predictions)
print('Test accuracy: %.4f%%' % test_accuracy)
Test accuracy: 79.0670%

Predict Dog Breed with the Model

Now that I have built my model, I then wrote a function that takes an image path and returns the dog breed.

In [20]:
from extract_bottleneck_features import *

def inceptionV3_predict_breed(img_path):
    #extracts bottleneck features
    bottleneck_feature = extract_InceptionV3(path_to_tensor(img_path))
    #obtain predicted vector
    predicted_vector = inceptionV3_model.predict(bottleneck_feature)
    # return dog breed that is predicted by the model
    return dog_names[np.argmax(predicted_vector)]
In [21]:
def dog_app_breed(img_path):
    #first figure out if human or dog using the functions defined earlier
    human_found = face_detector(img_path)
    dog_found = dog_detector(img_path)
    dog_type = inceptionV3_predict_breed(img_path)
    
    #first checks if there is human or dog, else then first provide dog type, then provide human type. Did not include situation for both dog and human.
    if not human_found and not dog_found:
        print("No humans or dogs were found in the image. Please use another image.")
    elif dog_found:        
        print("The dog looks like a...\n" + dog_type)
    else:
        print("Hello, your dog look like is a....\n" + dog_type)

Using My Algorithm

I then tested my function on sample dog images. On the 5 images I used, the model was able to recognize correctly the dog breed.

In [22]:
import matplotlib.pyplot as plt
from scipy.misc import imread

def test_dog_identifier(img_path):
    print("For image: " + img_path +"\n")
    plt.imshow(imread(img_path))
    plt.show()
    print("\n")
    dog_app_breed(img_path)
    print("\n")
    print("----------------------------------------")
In [23]:
test_dog_identifier('images\American_water_spaniel_00648.jpg')
For image: images\American_water_spaniel_00648.jpg


The dog looks like a...
American_water_spaniel


----------------------------------------
In [24]:
test_dog_identifier('images\Labrador_retriever_06449.jpg')
For image: images\Labrador_retriever_06449.jpg


The dog looks like a...
Labrador_retriever


----------------------------------------
In [25]:
test_dog_identifier('images\Welsh_springer_spaniel_08203.jpg')
For image: images\Welsh_springer_spaniel_08203.jpg


The dog looks like a...
Welsh_springer_spaniel


----------------------------------------
In [26]:
test_dog_identifier('images\Curly-coated_retriever_03896.jpg')
For image: images\Curly-coated_retriever_03896.jpg


The dog looks like a...
Curly-coated_retriever


----------------------------------------
In [27]:
test_dog_identifier('images\Brittany_02625.jpg')
For image: images\Brittany_02625.jpg


The dog looks like a...
Brittany


----------------------------------------