Transfer Learning on Dog Breeds

April 26, 2019 - an end-to-end model for classifying dog breeds.

This is a pipeline for transfer learning. A pretrained neural net - Xception - is used to classify 120 dog breed classes.


Libaries

In [2]:
import tensorflow as tf
print(tf.__version__)
1.13.1
In [3]:
import requests
from lxml import html
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import cv2 as cv
import h5py
import os
import re
import xml.etree.ElementTree as ET
from tqdm import tqdm
In [4]:
import plotly.offline as py
import plotly.graph_objs as go
py.init_notebook_mode(connected=True)
In [5]:
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator
from tensorflow.python.keras.models import Sequential, Model, load_model
from tensorflow.python.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, Concatenate, Input, BatchNormalization, Activation
from tensorflow.python.keras.metrics import top_k_categorical_accuracy
from tensorflow.python.keras.applications.vgg16 import VGG16
from tensorflow.python.keras.applications.xception import Xception
from tensorflow.python.keras import optimizers
In [6]:
CLOUD = False

Data Preproccessing

Retrieval

Either store the data or in Google drive.

In [7]:
if CLOUD:
    from google.colab import drive
    drive.mount('/gdrive')
In [8]:
if CLOUD: PATH = "/gdrive/My Drive/AI/Databases/Dogs/"
else: PATH = "/Users/desiredewaele/drive/AI/Databases/Dogs/"

The images are not cropped but are accompanied with boundary box annotations. Here, the images are read, cropped and assigned to a train, valis and test folder.

In [0]:
os.makedirs(PATH+"/Train")
os.makedirs(PATH+"/Valid")
os.makedirs(PATH+"/Test")

classes = [f for f in os.listdir(PATH+"Images/") if not f.startswith(".")]

for imagefolder in (classes[:]):
    label = imagefolder[10:].replace("_", " ").replace("-", " ").title()
    os.makedirs(PATH+"/Train/"+label)
    os.makedirs(PATH+"/Valid/"+label)
    os.makedirs(PATH+"/Test/"+label)

    imagefiles = [f for f in os.listdir(PATH+"Images/"+imagefolder) if not f.startswith(".")]
    split1 = len(imagefiles)*0.8
    split2 = len(imagefiles)*0.9
    
    for i, imagefile in enumerate(imagefiles):
        if i < split1: dataset = "Train/"
        elif i < split2: dataset = "Valid/"
        else: dataset = "Test/"

        root = ET.parse(PATH+"Annotation/"+imagefolder+"/"+imagefile[:-4]).getroot()
        img = cv.imread(PATH+"Images/"+imagefolder+"/"+imagefile)
            
        for box in root.findall('object/bndbox'):
            
            # CROP
            xmin = int(box.find("xmin").text)
            ymin = int(box.find("ymin").text)
            xmax = int(box.find("xmax").text)
            ymax = int(box.find("ymax").text)
            crop = img[ymin:ymax,xmin:xmax]
            
            cv.imwrite(PATH+dataset+label+"/"+imagefile, crop)
    print("DONE", label)

This is a helper function for plotting the images.

In [9]:
def showImages(data, labels, loop=False, loops=1, grid=(3,7), figsize = (17,8), seed=0):
    n = len(data) / loops
    fig, ax = plt.subplots(grid[0], grid[1], figsize=figsize)
    np.random.seed(seed)
    for j in range(grid[1]):
        x = np.random.randint(n)
        for i in range(grid[0]):
            if loop: index = int(x+(i*n))
            else: index = np.random.randint(n)
            ax[i,j].imshow(data[index])
            ax[i,j].set_title("{}".format(labels[index]))
            ax[i,j].axis('off')
    plt.show()

Augmentation

We can easily augment the data by:

  • Adding 10° rotations to the dataset.
  • Adding horizontal flips to the dataset.
  • Performing other variations, like shifting and zooming.
In [10]:
trainAugmenter = ImageDataGenerator(
        rescale = 1./255.,
        rotation_range=10,
        width_shift_range=0.1,
        height_shift_range=0.1,
        shear_range=0.1,
        zoom_range=(1, 1.2),
        horizontal_flip=True)
validAugmenter = ImageDataGenerator(rescale = 1./255.)
testAugmenter = ImageDataGenerator(rescale = 1./255.)
In [11]:
kwargs = {"batch_size":32, "class_mode":"categorical", "target_size":(299,299), "interpolation":"nearest", "seed":42}
trainGenerator = trainAugmenter.flow_from_directory(PATH+'Train', shuffle=True, **kwargs)
validGenerator = validAugmenter.flow_from_directory(PATH+'Valid', shuffle=False, **kwargs)
testGenerator = testAugmenter.flow_from_directory(PATH+'Test', shuffle=False, **kwargs)
Found 16508 images belonging to 120 classes.
Found 2063 images belonging to 120 classes.
Found 2009 images belonging to 120 classes.
In [12]:
targetLabels = list(trainGenerator.class_indices.keys())
In [13]:
batchX, batchY = trainGenerator.next()
showImages(batchX, [targetLabels[x] for x in np.argmax(batchY, axis=1)], seed=1)