Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mrdbourke
GitHub Repository: mrdbourke/zero-to-mastery-ml
Path: blob/master/section-4-unstructured-data-projects/end-to-end-dog-vision-video.ipynb
874 views
Kernel: Python 3

Open In Colab

🐶 End-to-end Multil-class Dog Breed Classification

This notebook builds an end-to-end multi-class image classifier using TensorFlow 2.x and TensorFlow Hub.

1. Problem

Identifying the breed of a dog given an image of a dog.

When I'm sitting at the cafe and I take a photo of a dog, I want to know what breed of dog it is.

2. Data

The data we're using is from Kaggle's dog breed identification competition.

https://www.kaggle.com/c/dog-breed-identification/data

3. Evaluation

The evaluation is a file with prediction probabilities for each dog breed of each test image.

https://www.kaggle.com/c/dog-breed-identification/overview/evaluation

4. Features

Some information about the data:

  • We're dealing with images (unstructured data) so it's probably best we use deep learning/transfer learning.

  • There are 120 breeds of dogs (this means there are 120 different classes).

  • There are around 10,000+ images in the training set (these images have labels).

  • There are around 10,000+ images in the test set (these images have no labels, because we'll want to predict them).

# Unzip the uploaded data into Google Drive #!unzip "drive/My Drive/Dog Vision/dog-breed-identification.zip" -d "drive/My Drive/Dog Vision/"

Get our workspace ready

  • Import TensorFlow 2.x ✅

  • Import TensorFlow Hub ✅

  • Make sure we're using a GPU ✅

# # Import TensorFlow into Colab # import tensorflow as tf # print("TF version:", tf.__version__)
# Run this cell if TensorFlow 2.x isn't the default in Colab try: # %tensorflow_version only exists in Colab %tensorflow_version 2.x except Exception: pass
TensorFlow 2.x selected.
# Import necessary tools import tensorflow as tf import tensorflow_hub as hub print("TF version:", tf.__version__) print("TF Hub version:", hub.__version__) # Check for GPU availability print("GPU", "available (YESSSS!!!!!)" if tf.config.list_physical_devices("GPU") else "not available :(")
TF version: 2.1.0 TF Hub version: 0.7.0 GPU available (YESSSS!!!!!)

Getting our data ready (turning into Tensors)

With all machine learning models, our data has to be in numerical format. So that's what we'll be doing first. Turning our images into Tensors (numerical representations).

Let's start by accessing our data and checking out the labels.

# Checkout the labels of our data import pandas as pd labels_csv = pd.read_csv("drive/My Drive/Dog Vision/labels.csv") print(labels_csv.describe()) print(labels_csv.head())
id breed count 10222 10222 unique 10222 120 top 578fed8e31c96406eaa447ae3ff604ba scottish_deerhound freq 1 126 id breed 0 000bec180eb18c7604dcecc8fe0dba07 boston_bull 1 001513dfcb2ffafc82cccf4d8bbaba97 dingo 2 001cdf01b096e06d78e9e5112d419397 pekinese 3 00214f311d5d2247d5dfe4fe24b2303d bluetick 4 0021f9ceb3235effd7fcde7f7538ed62 golden_retriever
labels_csv.head()
# How many images are there of each breed? labels_csv["breed"].value_counts().plot.bar(figsize=(20, 10))
<matplotlib.axes._subplots.AxesSubplot at 0x7fdac82fc5f8>
Image in a Jupyter notebook
# What's the median number of images per class? labels_csv["breed"].value_counts().median()
82.0
# Let's view an image from IPython.display import Image # Image("drive/My Drive/Dog Vision/train/001513dfcb2ffafc82cccf4d8bbaba97.jpg")

Getting images and their labels

Let's get a list of all of our image file pathnames.

labels_csv.head()
# Create pathnames from image ID's filenames = ["drive/My Drive/Dog Vision/train/" + fname + ".jpg" for fname in labels_csv["id"]] # Check the first 10 filenames[:10]
['drive/My Drive/Dog Vision/train/000bec180eb18c7604dcecc8fe0dba07.jpg', 'drive/My Drive/Dog Vision/train/001513dfcb2ffafc82cccf4d8bbaba97.jpg', 'drive/My Drive/Dog Vision/train/001cdf01b096e06d78e9e5112d419397.jpg', 'drive/My Drive/Dog Vision/train/00214f311d5d2247d5dfe4fe24b2303d.jpg', 'drive/My Drive/Dog Vision/train/0021f9ceb3235effd7fcde7f7538ed62.jpg', 'drive/My Drive/Dog Vision/train/002211c81b498ef88e1b40b9abf84e1d.jpg', 'drive/My Drive/Dog Vision/train/00290d3e1fdd27226ba27a8ce248ce85.jpg', 'drive/My Drive/Dog Vision/train/002a283a315af96eaea0e28e7163b21b.jpg', 'drive/My Drive/Dog Vision/train/003df8b8a8b05244b1d920bb6cf451f9.jpg', 'drive/My Drive/Dog Vision/train/0042188c895a2f14ef64a918ed9c7b64.jpg']
# Check whether number of filenames matches number of actual image files import os if len(os.listdir("drive/My Drive/Dog Vision/train/")) == len(filenames): print("Filenames match actual amount of files!!! Proceed.") else: print("Filenames do no match actual amount of files, check the target directory.")
--------------------------------------------------------------------------- OSError Traceback (most recent call last) <ipython-input-12-302a5f437b95> in <module>() 1 import os ----> 2 if len(os.listdir("drive/My Drive/Dog Vision/train/")) == len(filenames): 3 print("Filenames match actual amount of files!!! Proceed.") 4 else: 5 print("Filenames do no match actual amount of files, check the target directory.") OSError: [Errno 5] Input/output error: 'drive/My Drive/Dog Vision/train/'
# One more check # Image(filenames[9000])
labels_csv["breed"][9000]
'tibetan_mastiff'

Since we've now got our training image filepaths in a list, let's prepare our labels.

import numpy as np labels = labels_csv["breed"].to_numpy() # labels = np.array(labels) # does same thing as above labels
array(['boston_bull', 'dingo', 'pekinese', ..., 'airedale', 'miniature_pinscher', 'chesapeake_bay_retriever'], dtype=object)
len(labels)
10222
# See if number of labels matches the number of filenames if len(labels) == len(filenames): print("Number of labels matches number of filenames!") else: print("Number of labels does not match number of filenames, check data directories!")
Number of labels matches number of filenames!
# Find the unique label values unique_breeds = np.unique(labels) len(unique_breeds)
120
unique_breeds
array(['affenpinscher', 'afghan_hound', 'african_hunting_dog', 'airedale', 'american_staffordshire_terrier', 'appenzeller', 'australian_terrier', 'basenji', 'basset', 'beagle', 'bedlington_terrier', 'bernese_mountain_dog', 'black-and-tan_coonhound', 'blenheim_spaniel', 'bloodhound', 'bluetick', 'border_collie', 'border_terrier', 'borzoi', 'boston_bull', 'bouvier_des_flandres', 'boxer', 'brabancon_griffon', 'briard', 'brittany_spaniel', 'bull_mastiff', 'cairn', 'cardigan', 'chesapeake_bay_retriever', 'chihuahua', 'chow', 'clumber', 'cocker_spaniel', 'collie', 'curly-coated_retriever', 'dandie_dinmont', 'dhole', 'dingo', 'doberman', 'english_foxhound', 'english_setter', 'english_springer', 'entlebucher', 'eskimo_dog', 'flat-coated_retriever', 'french_bulldog', 'german_shepherd', 'german_short-haired_pointer', 'giant_schnauzer', 'golden_retriever', 'gordon_setter', 'great_dane', 'great_pyrenees', 'greater_swiss_mountain_dog', 'groenendael', 'ibizan_hound', 'irish_setter', 'irish_terrier', 'irish_water_spaniel', 'irish_wolfhound', 'italian_greyhound', 'japanese_spaniel', 'keeshond', 'kelpie', 'kerry_blue_terrier', 'komondor', 'kuvasz', 'labrador_retriever', 'lakeland_terrier', 'leonberg', 'lhasa', 'malamute', 'malinois', 'maltese_dog', 'mexican_hairless', 'miniature_pinscher', 'miniature_poodle', 'miniature_schnauzer', 'newfoundland', 'norfolk_terrier', 'norwegian_elkhound', 'norwich_terrier', 'old_english_sheepdog', 'otterhound', 'papillon', 'pekinese', 'pembroke', 'pomeranian', 'pug', 'redbone', 'rhodesian_ridgeback', 'rottweiler', 'saint_bernard', 'saluki', 'samoyed', 'schipperke', 'scotch_terrier', 'scottish_deerhound', 'sealyham_terrier', 'shetland_sheepdog', 'shih-tzu', 'siberian_husky', 'silky_terrier', 'soft-coated_wheaten_terrier', 'staffordshire_bullterrier', 'standard_poodle', 'standard_schnauzer', 'sussex_spaniel', 'tibetan_mastiff', 'tibetan_terrier', 'toy_poodle', 'toy_terrier', 'vizsla', 'walker_hound', 'weimaraner', 'welsh_springer_spaniel', 'west_highland_white_terrier', 'whippet', 'wire-haired_fox_terrier', 'yorkshire_terrier'], dtype=object)
# Turn a single label into an array of booleans print(labels[0]) labels[0] == unique_breeds
boston_bull
array([False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False])
len(labels)
10222
# Turn every label into a boolean array boolean_labels = [label == unique_breeds for label in labels] boolean_labels[:2]
[array([False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]), array([False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False])]
len(boolean_labels)
10222
# Example: Turning boolean array into integers print(labels[0]) # original label print(np.where(unique_breeds == labels[0])) # index where label occurs print(boolean_labels[0].argmax()) # index where label occurs in boolean array print(boolean_labels[0].astype(int)) # there will be a 1 where the sample label occurs
boston_bull (array([19]),) 19 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
print(labels[2]) print(boolean_labels[2].astype(int))
pekinese [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
boolean_labels[:2]
[array([False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]), array([False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False])]
filenames[:10]
['drive/My Drive/Dog Vision/train/000bec180eb18c7604dcecc8fe0dba07.jpg', 'drive/My Drive/Dog Vision/train/001513dfcb2ffafc82cccf4d8bbaba97.jpg', 'drive/My Drive/Dog Vision/train/001cdf01b096e06d78e9e5112d419397.jpg', 'drive/My Drive/Dog Vision/train/00214f311d5d2247d5dfe4fe24b2303d.jpg', 'drive/My Drive/Dog Vision/train/0021f9ceb3235effd7fcde7f7538ed62.jpg', 'drive/My Drive/Dog Vision/train/002211c81b498ef88e1b40b9abf84e1d.jpg', 'drive/My Drive/Dog Vision/train/00290d3e1fdd27226ba27a8ce248ce85.jpg', 'drive/My Drive/Dog Vision/train/002a283a315af96eaea0e28e7163b21b.jpg', 'drive/My Drive/Dog Vision/train/003df8b8a8b05244b1d920bb6cf451f9.jpg', 'drive/My Drive/Dog Vision/train/0042188c895a2f14ef64a918ed9c7b64.jpg']

Creating our own validation set

Since the dataset from Kaggle doesn't come with a validation set, we're going to create our own.

# Setup X & y variables X = filenames y = boolean_labels
len(filenames)
10222

We're going to start off experimenting with ~1000 images and increase as needed.

# Set number of images to use for experimenting NUM_IMAGES = 1000 #@param {type:"slider", min:1000, max:10000, step:1000}
# Let's split our data into train and validation sets from sklearn.model_selection import train_test_split # Split them into training and validation of total size NUM_IMAGES X_train, X_val, y_train, y_val = train_test_split(X[:NUM_IMAGES], y[:NUM_IMAGES], test_size=0.2, random_state=42) len(X_train), len(y_train), len(X_val), len(y_val)
(800, 800, 200, 200)
# Let's have a geez at the training data X_train[:5], y_train[:2]
(['drive/My Drive/Dog Vision/train/00bee065dcec471f26394855c5c2f3de.jpg', 'drive/My Drive/Dog Vision/train/0d2f9e12a2611d911d91a339074c8154.jpg', 'drive/My Drive/Dog Vision/train/1108e48ce3e2d7d7fb527ae6e40ab486.jpg', 'drive/My Drive/Dog Vision/train/0dc3196b4213a2733d7f4bdcd41699d3.jpg', 'drive/My Drive/Dog Vision/train/146fbfac6b5b1f0de83a5d0c1b473377.jpg'], [array([False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]), array([False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False])])

Preprocessing Images (turning images into Tensors)

To preprocess our images into Tensors we're going to write a function which does a few things:

  1. Take an image filepath as input

  2. Use TensorFlow to read the file and save it to a variable, image

  3. Turn our image (a jpg) into Tensors

  4. Normalize our image (convert color channel values from from 0-255 to 0-1).

  5. Resize the image to be a shape of (224, 224)

  6. Return the modified image

Before we do, let's see what importing an image looks like.

# Convert image to NumPy array from matplotlib.pyplot import imread image = imread(filenames[42]) image.shape
(257, 350, 3)
image.max(), image.min()
(255, 0)
image[:2]
array([[[ 89, 137, 87], [ 76, 124, 74], [ 63, 111, 59], ..., [ 76, 134, 86], [ 76, 134, 86], [ 76, 134, 86]], [[ 72, 119, 73], [ 67, 114, 68], [ 63, 111, 63], ..., [ 75, 131, 84], [ 74, 132, 84], [ 74, 131, 86]]], dtype=uint8)
# turn image into a tensor tf.constant(image)[:2]
<tf.Tensor: shape=(2, 350, 3), dtype=uint8, numpy= array([[[ 89, 137, 87], [ 76, 124, 74], [ 63, 111, 59], ..., [ 76, 134, 86], [ 76, 134, 86], [ 76, 134, 86]], [[ 72, 119, 73], [ 67, 114, 68], [ 63, 111, 63], ..., [ 75, 131, 84], [ 74, 132, 84], [ 74, 131, 86]]], dtype=uint8)>

Now we've seen what an image looks like as a Tensor, let's make a function to preprocess them.

We'll create a function to:

  1. Take an image filepath as input

  2. Use TensorFlow to read the file and save it to a variable, image

  3. Turn our image (a jpg) into Tensors

  4. Normalize our image (convert color channel values from from 0-255 to 0-1).

  5. Resize the image to be a shape of (224, 224)

  6. Return the modified image

More information on loading images in TensorFlow can be seen here: https://www.tensorflow.org/tutorials/load_data/images

# Define image size IMG_SIZE = 224 # Create a function for preprocessing images def process_image(image_path, img_size=IMG_SIZE): """ Takes an image file path and turns the image into a Tensor. """ # Read in an image file image = tf.io.read_file(image_path) # Turn the jpeg image into numerical Tensor with 3 colour channels (Red, Green, Blue) image = tf.image.decode_jpeg(image, channels=3) # Convert the colour channel values from 0-255 to 0-1 values image = tf.image.convert_image_dtype(image, tf.float32) # Resize the image to our desired value (224, 224) image = tf.image.resize(image, size=[IMG_SIZE, IMG_SIZE]) return image

Turning our data into batches

Why turn our data into batches?

Let's say you're trying to process 10,000+ images in one go... they all might not fit into memory.

So that's why we do about 32 (this is the batch size) images at a time (you can manually adjust the batch size if need be).

In order to use TensorFlow effectively, we need our data in the form of Tensor tuples which look like this: (image, label).

# Create a simple function to return a tuple (image, label) def get_image_label(image_path, label): """ Takes an image file path name and the assosciated label, processes the image and reutrns a typle of (image, label). """ image = process_image(image_path) return image, label
# Demo of the above (process_image(X[42]), tf.constant(y[42]))
(<tf.Tensor: shape=(224, 224, 3), dtype=float32, numpy= array([[[0.3264178 , 0.5222886 , 0.3232816 ], [0.2537167 , 0.44366494, 0.24117757], [0.25699762, 0.4467087 , 0.23893751], ..., [0.29325107, 0.5189916 , 0.3215547 ], [0.29721776, 0.52466875, 0.33030328], [0.2948505 , 0.5223015 , 0.33406618]], [[0.25903144, 0.4537807 , 0.27294815], [0.24375686, 0.4407019 , 0.2554778 ], [0.2838985 , 0.47213382, 0.28298813], ..., [0.2785345 , 0.5027992 , 0.31004712], [0.28428748, 0.5108719 , 0.32523635], [0.28821915, 0.5148036 , 0.32916805]], [[0.20941195, 0.40692952, 0.25792548], [0.24045378, 0.43900946, 0.2868911 ], [0.29001117, 0.47937486, 0.32247734], ..., [0.26074055, 0.48414773, 0.30125174], [0.27101526, 0.49454468, 0.32096273], [0.27939945, 0.5029289 , 0.32934693]], ..., [[0.00634795, 0.03442048, 0.0258106 ], [0.01408936, 0.04459917, 0.0301715 ], [0.01385712, 0.04856448, 0.02839671], ..., [0.4220516 , 0.39761978, 0.21622123], [0.47932503, 0.45370543, 0.2696505 ], [0.48181024, 0.45828083, 0.27004552]], [[0.00222061, 0.02262166, 0.03176915], [0.01008397, 0.03669046, 0.02473482], [0.00608852, 0.03890046, 0.01207283], ..., [0.36070833, 0.33803678, 0.16216145], [0.42499566, 0.3976801 , 0.21701711], [0.4405433 , 0.4139589 , 0.23183356]], [[0.05608025, 0.06760229, 0.10401428], [0.05441074, 0.07435255, 0.05428263], [0.04734282, 0.07581793, 0.02060942], ..., [0.3397559 , 0.31265694, 0.14725602], [0.387725 , 0.360274 , 0.18714729], [0.43941984, 0.41196886, 0.23884216]]], dtype=float32)>, <tf.Tensor: shape=(120,), dtype=bool, numpy= array([False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False])>)

Now we've got a way to turn our data into tuples of Tensors in the form: (image, label), let's make a function to turn all of our data (X & y) into batches!

# Define the batch size, 32 is a good start BATCH_SIZE = 32 # Create a function to turn data into batches def create_data_batches(X, y=None, batch_size=BATCH_SIZE, valid_data=False, test_data=False): """ Creates batches of data out of image (X) and label (y) pairs. Shuffles the data if it's training data but doesn't shuffle if it's validation data. Also accepts test data as input (no labels). """ # If the data is a test dataset, we probably don't have have labels if test_data: print("Creating test data batches...") data = tf.data.Dataset.from_tensor_slices((tf.constant(X))) # only filepaths (no labels) data_batch = data.map(process_image).batch(BATCH_SIZE) return data_batch # If the data is a valid dataset, we don't need to shuffle it elif valid_data: print("Creating validation data batches...") data = tf.data.Dataset.from_tensor_slices((tf.constant(X), # filepaths tf.constant(y))) # labels data_batch = data.map(get_image_label).batch(BATCH_SIZE) return data_batch else: print("Creating training data batches...") # Turn filepaths and labels into Tensors data = tf.data.Dataset.from_tensor_slices((tf.constant(X), tf.constant(y))) # Shuffling pathnames and labels before mapping image processor function is faster than shuffling images data = data.shuffle(buffer_size=len(X)) # Create (image, label) tuples (this also turns the iamge path into a preprocessed image) data = data.map(get_image_label) # Turn the training data into batches data_batch = data.batch(BATCH_SIZE) return data_batch
# Create training and validation data batches train_data = create_data_batches(X_train, y_train) val_data = create_data_batches(X_val, y_val, valid_data=True)
Creating training data batches... Creating validation data batches...
# Check out the different attributes of our data batches train_data.element_spec, val_data.element_spec
((TensorSpec(shape=(None, 224, 224, 3), dtype=tf.float32, name=None), TensorSpec(shape=(None, 120), dtype=tf.bool, name=None)), (TensorSpec(shape=(None, 224, 224, 3), dtype=tf.float32, name=None), TensorSpec(shape=(None, 120), dtype=tf.bool, name=None)))

Visualizing Data Batches

Our data is now in batches, however, these can be a little hard to understand/comprehend, let's visualize them!

import matplotlib.pyplot as plt # Create a function for viewing images in a data batch def show_25_images(images, labels): """ Displays a plot of 25 images and their labels from a data batch. """ # Setup the figure plt.figure(figsize=(10, 10)) # Loop through 25 (for displaying 25 images) for i in range(25): # Create subplots (5 rows, 5 columns) ax = plt.subplot(5, 5, i+1) # Display an image plt.imshow(images[i]) # Add the image label as the title plt.title(unique_breeds[labels[i].argmax()]) # Turn the grid lines off plt.axis("off")
train_data
<BatchDataset shapes: ((None, 224, 224, 3), (None, 120)), types: (tf.float32, tf.bool)>
# # Now let's visualize the data in a training batch # train_images, train_labels = next(train_data.as_numpy_iterator()) # show_25_images(train_images, train_labels)
# # Now let's visualize our validation set # val_images, val_labels = next(val_data.as_numpy_iterator()) # show_25_images(val_images, val_labels)

Building a model

Before we build a model, there are a few things we need to define:

IMG_SIZE
224
# Setup input shape to the model INPUT_SHAPE = [None, IMG_SIZE, IMG_SIZE, 3] # batch, height, width, colour channels # Setup output shape of our model OUTPUT_SHAPE = len(unique_breeds) # Setup model URL from TensorFlow Hub MODEL_URL = "https://tfhub.dev/google/imagenet/mobilenet_v2_130_224/classification/4"
INPUT_SHAPE
[None, 224, 224, 3]

Now we've got our inputs, outputs and model ready to go. Let's put them together into a Keras deep learning model!

Knowing this, let's create a function which:

  • Takes the input shape, output shape and the model we've chosen as parameters.

  • Defines the layers in a Keras model in sequential fashion (do this first, then this, then that).

  • Compiles the model (says it should be evaluated and improved).

  • Builds the model (tells the model the input shape it'll be getting).

  • Returns the model.

All of these steps can be found here: https://www.tensorflow.org/guide/keras/overview

# Create a function which builds a Keras model def create_model(input_shape=INPUT_SHAPE, output_shape=OUTPUT_SHAPE, model_url=MODEL_URL): print("Building model with:", MODEL_URL) # Setup the model layers model = tf.keras.Sequential([ hub.KerasLayer(MODEL_URL), # Layer 1 (input layer) tf.keras.layers.Dense(units=OUTPUT_SHAPE, activation="softmax") # Layer 2 (output layer) ]) # Compile the model model.compile( loss=tf.keras.losses.CategoricalCrossentropy(), optimizer=tf.keras.optimizers.Adam(), metrics=["accuracy"] ) # Build the model model.build(INPUT_SHAPE) return model
model = create_model() model.summary()
Building model with: https://tfhub.dev/google/imagenet/mobilenet_v2_130_224/classification/4 Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= keras_layer (KerasLayer) multiple 5432713 _________________________________________________________________ dense (Dense) multiple 120240 ================================================================= Total params: 5,552,953 Trainable params: 120,240 Non-trainable params: 5,432,713 _________________________________________________________________

Creating callbacks

Callbacks are helper functions a model can use during training to do such things as save its progress, check its progress or stop training early if a model stops improving.

We'll create two callbacks, one for TensorBoard which helps track our models progress and another for early stopping which prevents our model from training for too long.

TensorBoard Callback

To setup a TensorBoard callback, we need to do 3 things:

  1. Load the TensorBoard notebook extension ✅

  2. Create a TensorBoard callback which is able to save logs to a directory and pass it to our model's fit() function. ✅

  3. Visualize our models training logs with the %tensorboard magic function (we'll do this after model training).

https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/TensorBoard

# Load TensorBoard notebook extension %load_ext tensorboard
import datetime # Create a function to build a TensorBoard callback def create_tensorboard_callback(): # Create a log directory for storing TensorBoard logs logdir = os.path.join("drive/My Drive/Dog Vision/logs", # Make it so the logs get tracked whenever we run an experiment datetime.datetime.now().strftime("%Y%m%d-%H%M%S")) return tf.keras.callbacks.TensorBoard(logdir)

Early Stopping Callback

Early stopping helps stop our model from overfitting by stopping training if a certain evaluation metric stops improving.

https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/EarlyStopping

# Create early stopping callback early_stopping = tf.keras.callbacks.EarlyStopping(monitor="val_accuracy", patience=3)

Training a model (on subset of data)

Our first model is only going to train on 1000 images, to make sure everything is working.

NUM_EPOCHS = 100 #@param {type:"slider", min:10, max:100, step:10}
# Check to make sure we're still running on a GPU print("GPU", "available (YESSS!!!!!!)" if tf.config.list_physical_devices("GPU") else "not available :(")
GPU available (YESSS!!!!!!)

Let's create a function which trains a model.

  • Create a model using create_model()

  • Setup a TensorBoard callback using create_tensorboard_callback()

  • Call the fit() function on our model passing it the training data, validation data, number of epochs to train for (NUM_EPOCHS) and the callbacks we'd like to use

  • Return the model

# Build a function to train and return a trained model def train_model(): """ Trains a given model and returns the trained version. """ # Create a model model = create_model() # Create new TensorBoard session everytime we train a model tensorboard = create_tensorboard_callback() # Fit the model to the data passing it the callbacks we created model.fit(x=train_data, epochs=NUM_EPOCHS, validation_data=val_data, validation_freq=1, callbacks=[tensorboard, early_stopping]) # Return the fitted model return model
# Fit the model to the data # model = train_model()
Building model with: https://tfhub.dev/google/imagenet/mobilenet_v2_130_224/classification/4 Train for 25 steps, validate for 7 steps Epoch 1/100 25/25 [==============================] - 604s 24s/step - loss: 4.5757 - accuracy: 0.0988 - val_loss: 3.4853 - val_accuracy: 0.2200 Epoch 2/100 25/25 [==============================] - 4s 179ms/step - loss: 1.6390 - accuracy: 0.6900 - val_loss: 2.1415 - val_accuracy: 0.5100 Epoch 3/100 25/25 [==============================] - 4s 175ms/step - loss: 0.5734 - accuracy: 0.9388 - val_loss: 1.6463 - val_accuracy: 0.6050 Epoch 4/100 25/25 [==============================] - 4s 176ms/step - loss: 0.2523 - accuracy: 0.9912 - val_loss: 1.4408 - val_accuracy: 0.6250 Epoch 5/100 25/25 [==============================] - 4s 174ms/step - loss: 0.1475 - accuracy: 0.9987 - val_loss: 1.3662 - val_accuracy: 0.6450 Epoch 6/100 25/25 [==============================] - 4s 174ms/step - loss: 0.1007 - accuracy: 1.0000 - val_loss: 1.3218 - val_accuracy: 0.6350 Epoch 7/100 25/25 [==============================] - 4s 177ms/step - loss: 0.0757 - accuracy: 1.0000 - val_loss: 1.2891 - val_accuracy: 0.6350 Epoch 8/100 25/25 [==============================] - 4s 180ms/step - loss: 0.0602 - accuracy: 1.0000 - val_loss: 1.2607 - val_accuracy: 0.6300

Question: It looks like our model is overfitting because it's performing far better on the training dataset than the validation dataset, what are some ways to prevent model overfitting in deep learning neural networks?

Note: Overfitting to begin with is a good thing! It means our model is learning!!!

Checking the TensorBoard logs

The TensorBoard magic function (%tensorboard) will access the logs directory we created earlier and visualize its contents.

%tensorboard --logdir drive/My\ Drive/Dog\ Vision/logs
<IPython.core.display.Javascript object>

Making and evaluating predictions using a trained model

val_data
<BatchDataset shapes: ((None, 224, 224, 3), (None, 120)), types: (tf.float32, tf.bool)>
# Make predictions on the validation data (not used to train on) predictions = model.predict(val_data, verbose=1) predictions
7/7 [==============================] - 1s 187ms/step
array([[9.71041620e-03, 1.11232344e-04, 1.06535514e-03, ..., 4.50575637e-04, 3.35184595e-05, 5.80411172e-03], [8.50948389e-04, 1.42635807e-04, 1.56238000e-03, ..., 2.87816278e-03, 2.63545924e-04, 5.07109835e-05], [3.96433570e-05, 7.24060461e-04, 1.94261811e-05, ..., 3.11830721e-04, 2.19546419e-04, 2.50677066e-03], ..., [7.53665299e-05, 4.13964066e-04, 6.18613412e-05, ..., 1.32920090e-04, 2.30354301e-04, 1.24172875e-04], [4.26599709e-03, 3.53021285e-04, 2.64853996e-04, ..., 2.48774071e-04, 2.81099419e-05, 1.22751212e-02], [1.18396158e-04, 1.08441767e-04, 1.21402927e-03, ..., 2.75677326e-03, 9.14665638e-04, 4.48549428e-04]], dtype=float32)
predictions[0]
array([9.71041620e-03, 1.11232344e-04, 1.06535514e-03, 1.25762273e-03, 2.44743715e-04, 8.16427564e-05, 5.52608520e-02, 5.06713986e-04, 1.72605301e-04, 3.98611941e-04, 4.78233764e-04, 4.07359825e-04, 2.36615015e-04, 8.19553534e-05, 7.88489197e-05, 1.40835508e-03, 6.44498068e-05, 2.16273233e-01, 5.48937569e-05, 7.15497663e-05, 2.84898607e-03, 3.73658811e-04, 1.24497354e-04, 1.10250211e-03, 4.23891288e-05, 6.50359027e-04, 1.23412296e-01, 7.73611246e-05, 2.69144395e-04, 4.21349687e-05, 6.75256888e-05, 7.77814654e-04, 1.29133766e-03, 8.21698559e-05, 4.56787093e-04, 4.98460531e-02, 2.01200037e-05, 8.18561064e-04, 1.29906432e-04, 9.01233288e-05, 6.16013398e-03, 1.99825154e-05, 2.31262704e-04, 2.21858194e-04, 5.94848871e-06, 6.69727960e-05, 8.64264875e-05, 9.86296945e-05, 2.90667522e-04, 2.67122377e-04, 1.16069641e-04, 4.82746138e-04, 3.33986565e-04, 3.91650792e-05, 2.03684365e-04, 6.32835145e-05, 5.78568492e-04, 6.86674053e-03, 1.20120624e-03, 6.92622811e-02, 2.79069820e-04, 4.75922425e-05, 1.01855351e-03, 3.89385714e-05, 9.03978013e-04, 2.19036937e-02, 5.74029342e-04, 6.75847346e-04, 2.31311135e-02, 5.90709387e-05, 8.39314610e-02, 1.18991686e-03, 6.78601908e-04, 7.79933557e-02, 5.96930797e-04, 6.79546429e-05, 9.71055473e-04, 3.68214920e-02, 4.54270310e-04, 4.99254093e-03, 9.49741283e-04, 3.31232836e-03, 3.85401445e-03, 8.75358284e-03, 3.48921276e-05, 5.96835453e-04, 1.96861263e-04, 6.86006446e-04, 4.91048093e-04, 1.05881202e-03, 9.78839002e-04, 8.93583929e-04, 2.59298889e-04, 3.92163824e-03, 1.54886715e-04, 3.64881521e-03, 1.20378879e-03, 8.34545400e-03, 3.50253191e-03, 6.79212753e-05, 3.35050263e-02, 5.56703235e-05, 1.68805905e-02, 1.48447547e-02, 1.13202829e-03, 6.94601738e-04, 2.65298299e-02, 9.88404281e-05, 1.07932145e-04, 3.87956090e-02, 3.39840946e-04, 2.47450778e-04, 1.25230217e-04, 3.60850536e-04, 1.24716957e-04, 1.24741462e-04, 3.41790635e-03, 4.50575637e-04, 3.35184595e-05, 5.80411172e-03], dtype=float32)
# First prediction index = 42 print(predictions[index]) print(f"Max value (probability of prediction): {np.max(predictions[index])}") print(f"Sum: {np.sum(predictions[index])}") print(f"Max index: {np.argmax(predictions[index])}") print(f"Predicted label: {unique_breeds[np.argmax(predictions[index])]}")
[3.93188850e-04 1.07863947e-04 4.46250351e-06 2.60892866e-05 2.46077194e-04 2.97792467e-05 7.20272801e-05 3.95773910e-04 3.64765595e-03 5.11217974e-02 2.26338234e-05 1.39528738e-05 3.13435477e-04 2.23997841e-03 6.32500392e-04 1.79633230e-03 9.79685319e-06 2.89146381e-04 1.81319512e-04 8.14478553e-05 4.98793233e-05 3.45429202e-04 1.57097893e-04 3.45537592e-05 6.35696109e-04 4.78322108e-05 5.67347524e-05 2.17653851e-05 1.01170233e-04 1.69328850e-05 1.53154033e-04 7.74674554e-05 7.50654872e-05 8.28759294e-05 1.48052541e-05 1.00477992e-05 8.96311903e-05 1.16170768e-03 3.00705669e-05 1.60114363e-01 1.07582353e-04 1.46348611e-05 3.43107688e-03 3.19329665e-05 8.44281021e-05 1.35687924e-05 1.73864144e-04 3.12021206e-04 2.90576099e-05 8.03464063e-05 5.47980198e-05 7.21009419e-05 6.48214191e-05 1.31834217e-03 2.51896072e-05 1.45005106e-04 3.09110510e-05 2.43755785e-04 7.71240157e-05 7.60102921e-06 8.11443970e-05 5.45067945e-04 7.65130699e-06 3.06037109e-05 1.40500226e-04 4.08106098e-05 9.61981059e-05 9.27404544e-05 1.04137536e-04 2.99544604e-06 1.36659692e-05 2.15889831e-05 2.67908599e-05 1.45302605e-04 7.58328679e-05 3.19889587e-05 1.03890838e-04 4.04736747e-05 1.28405864e-05 3.17286067e-05 1.40954785e-06 1.97352580e-04 1.28395026e-04 1.03811163e-03 2.86407740e-04 3.94346898e-05 1.72229687e-04 1.29189639e-05 1.02533932e-05 7.73833541e-04 7.91739585e-05 1.94886270e-05 1.20114349e-03 1.43104335e-04 8.63297919e-06 1.33877620e-05 3.20555919e-05 8.01471324e-05 5.78756553e-05 5.83394249e-05 1.09248911e-04 2.20263930e-04 7.11565663e-05 2.37053009e-05 4.87697107e-05 5.92881515e-05 4.33753521e-05 2.65578947e-05 2.93630892e-05 2.28035671e-04 2.49874538e-05 6.13011187e-04 1.96751600e-04 7.57556081e-01 4.18836426e-04 1.40041055e-03 9.60089801e-06 4.12007648e-05 7.14138150e-04 9.45805572e-04] Max value (probability of prediction): 0.7575560808181763 Sum: 0.9999999403953552 Max index: 113 Predicted label: walker_hound
unique_breeds[113]
'walker_hound'

Having the the above functionality is great but we want to be able to do it at scale.

And it would be even better if we could see the image the prediction is being made on!

Note: Prediction probabilities are also known as confidence levels.

# Turn prediction probabilities into their respective label (easier to understand) def get_pred_label(prediction_probabilities): """ Turns an array of prediction probabilities into a label. """ return unique_breeds[np.argmax(prediction_probabilities)] # Get a predicted label based on an array of prediction probabilities pred_label = get_pred_label(predictions[81]) pred_label
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-55-29272b678cf2> in <module>() 6 7 # Get a predicted label based on an array of prediction probabilities ----> 8 pred_label = get_pred_label(predictions[81]) 9 pred_label NameError: name 'predictions' is not defined

Now since our validation data is still in a batch dataset, we'll have to unbatchify it to make predictions on the validation images and then compare those predictions to the validation labels (truth labels).

val_data
<BatchDataset shapes: ((None, 224, 224, 3), (None, 120)), types: (tf.float32, tf.bool)>
# Create a function to unbatch a batch dataset def unbatchify(data): """ Takes a batched dataset of (image, label) Tensors and reutrns separate arrays of images and labels. """ images = [] labels = [] # Loop through unbatched data for image, label in data.unbatch().as_numpy_iterator(): images.append(image) labels.append(unique_breeds[np.argmax(label)]) return images, labels # Unbatchify the validation data val_images, val_labels = unbatchify(val_data) val_images[0], val_labels[0]
(array([[[0.29599646, 0.43284872, 0.3056691 ], [0.26635826, 0.32996926, 0.22846507], [0.31428418, 0.2770141 , 0.22934894], ..., [0.77614343, 0.82320225, 0.8101595 ], [0.81291157, 0.8285351 , 0.8406944 ], [0.8209297 , 0.8263737 , 0.8423668 ]], [[0.2344871 , 0.31603682, 0.19543913], [0.3414841 , 0.36560842, 0.27241898], [0.45016077, 0.40117094, 0.33964607], ..., [0.7663987 , 0.8134138 , 0.81350833], [0.7304248 , 0.75012016, 0.76590735], [0.74518913, 0.76002574, 0.7830809 ]], [[0.30157745, 0.3082587 , 0.21018331], [0.2905954 , 0.27066195, 0.18401104], [0.4138316 , 0.36170745, 0.2964005 ], ..., [0.79871625, 0.8418535 , 0.8606443 ], [0.7957738 , 0.82859945, 0.8605655 ], [0.75181633, 0.77904975, 0.8155256 ]], ..., [[0.9746779 , 0.9878955 , 0.9342279 ], [0.99153054, 0.99772066, 0.9427856 ], [0.98925114, 0.9792082 , 0.9137934 ], ..., [0.0987601 , 0.0987601 , 0.0987601 ], [0.05703771, 0.05703771, 0.05703771], [0.03600177, 0.03600177, 0.03600177]], [[0.98197854, 0.9820659 , 0.9379411 ], [0.9811992 , 0.97015417, 0.9125648 ], [0.9722316 , 0.93666023, 0.8697186 ], ..., [0.09682598, 0.09682598, 0.09682598], [0.07196062, 0.07196062, 0.07196062], [0.0361607 , 0.0361607 , 0.0361607 ]], [[0.97279435, 0.9545954 , 0.92389745], [0.963602 , 0.93199134, 0.88407487], [0.9627158 , 0.9125331 , 0.8460338 ], ..., [0.08394483, 0.08394483, 0.08394483], [0.0886985 , 0.0886985 , 0.0886985 ], [0.04514172, 0.04514172, 0.04514172]]], dtype=float32), 'cairn')

Now we've got ways to get get:

  • Prediction labels

  • Validation labels (truth labels)

  • Validation images

Let's make some function to make these all a bit more visaulize.

We'll create a function which:

  • Takes an array of prediction probabilities, an array of truth labels and an array of images and an integer. ✅

  • Convert the prediction probabilities to a predicted label. ✅

  • Plot the predicted label, its predicted probability, the truth label and the target image on a single plot. ✅

def plot_pred(prediction_probabilities, labels, images, n=1): """ View the prediction, ground truth and image for sample n """ pred_prob, true_label, image = prediction_probabilities[n], labels[n], images[n] # Get the pred label pred_label = get_pred_label(pred_prob) # Plot image & remove ticks plt.imshow(image) plt.xticks([]) plt.yticks([]) # Change the colour of the title depending on if the prediction is right or wrong if pred_label == true_label: color = "green" else: color = "red" # Change plot title to be predicted, probability of prediction and truth label plt.title("{} {:2.0f}% {}".format(pred_label, np.max(pred_prob)*100, true_label), color=color)
plot_pred(prediction_probabilities=predictions, labels=val_labels, images=val_images, n=77)
Image in a Jupyter notebook

Now we've got one function to visualize our models top prediction, let's make another to view our models top 10 predictions.

This function will:

  • Take an input of prediction probabilities array and a ground truth array and an integer ✅

  • Find the prediction using get_pred_label()

  • Find the top 10:

    • Prediction probabilities indexes ✅

    • Prediction probabilities values ✅

    • Prediction labels ✅

  • Plot the top 10 prediction probability values and labels, coloring the true label green ✅

def plot_pred_conf(prediction_probabilities, labels, n=1): """ Plus the top 10 highest prediction confidences along with the truth label for sample n. """ pred_prob, true_label = prediction_probabilities[n], labels[n] # Get the predicted label pred_label = get_pred_label(pred_prob) # Find the top 10 prediction confidence indexes top_10_pred_indexes = pred_prob.argsort()[-10:][::-1] # Find the top 10 prediction confidence values top_10_pred_values = pred_prob[top_10_pred_indexes] # Find the top 10 prediction labels top_10_pred_labels = unique_breeds[top_10_pred_indexes] # Setup plot top_plot = plt.bar(np.arange(len(top_10_pred_labels)), top_10_pred_values, color="grey") plt.xticks(np.arange(len(top_10_pred_labels)), labels=top_10_pred_labels, rotation="vertical") # Change color of true label if np.isin(true_label, top_10_pred_labels): top_plot[np.argmax(top_10_pred_labels == true_label)].set_color("green") else: pass
plot_pred_conf(prediction_probabilities=predictions, labels=val_labels, n=9)
Image in a Jupyter notebook

Now we've got some function to help us visualize our predictions and evaluate our modle, let's check out a few.

# Let's check out a few predictions and their different values i_multiplier = 20 num_rows = 3 num_cols = 2 num_images = num_rows*num_cols plt.figure(figsize=(10*num_cols, 5*num_rows)) for i in range(num_images): plt.subplot(num_rows, 2*num_cols, 2*i+1) plot_pred(prediction_probabilities=predictions, labels=val_labels, images=val_images, n=i+i_multiplier) plt.subplot(num_rows, 2*num_cols, 2*i+2) plot_pred_conf(prediction_probabilities=predictions, labels=val_labels, n=i+i_multiplier) plt.tight_layout(h_pad=1.0) plt.show()
Image in a Jupyter notebook

Challenge: How would you create a confusion matrix with our models predictions and true labels?

Saving and reloading a trained model

# Create a function to save a model def save_model(model, suffix=None): """ Saves a given model in a models directory and appends a suffix (string). """ # Create a model directory pathname with current time modeldir = os.path.join("drive/My Drive/Dog Vision/models", datetime.datetime.now().strftime("%Y%m%d-%H%M%s")) model_path = modeldir + "-" + suffix + ".h5" # save format of model print(f"Saving model to: {model_path}...") model.save(model_path) return model_path
# Create a function to load a trained model def load_model(model_path): """ Loads a saved model from a specified path. """ print(f"Loading saved model from: {model_path}") model = tf.keras.models.load_model(model_path, custom_objects={"KerasLayer":hub.KerasLayer}) return model

Now we've got functions to save and load a trained model, let's make sure they work!

# Save our model trained on 1000 images save_model(model, suffix="1000-images-mobilenetv2-Adam")
Saving model to: drive/My Drive/Dog Vision/models/20200205-03121580872363-1000-images-mobilenetv2-Adam.h5...
'drive/My Drive/Dog Vision/models/20200205-03121580872363-1000-images-mobilenetv2-Adam.h5'
# Load a trained model loaded_1000_image_model = load_model('drive/My Drive/Dog Vision/models/20200205-03121580872363-1000-images-mobilenetv2-Adam.h5')
Loading saved model from: drive/My Drive/Dog Vision/models/20200205-03121580872363-1000-images-mobilenetv2-Adam.h5 WARNING:tensorflow:Sequential models without an `input_shape` passed to the first layer cannot reload their optimizer state. As a result, your model isstarting with a freshly initialized optimizer.
WARNING:tensorflow:Sequential models without an `input_shape` passed to the first layer cannot reload their optimizer state. As a result, your model isstarting with a freshly initialized optimizer.
# Evaluate the pre-saved model model.evaluate(val_data)
7/7 [==============================] - 1s 130ms/step - loss: 1.2607 - accuracy: 0.6300
[1.2607423237391882, 0.63]
# Evaluate the loaded model loaded_1000_image_model.evaluate(val_data)
7/7 [==============================] - 9s 1s/step - loss: 1.2607 - accuracy: 0.6300
[1.2607423237391882, 0.63]

Training a big dog model 🐶 (on the full data)

len(X), len(y)
(10222, 10222)
# Create a data batch with the full data set full_data = create_data_batches(X, y)
Creating training data batches...
full_data
<BatchDataset shapes: ((None, 224, 224, 3), (None, 120)), types: (tf.float32, tf.bool)>
# Create a model for full model full_model = create_model()
Building model with: https://tfhub.dev/google/imagenet/mobilenet_v2_130_224/classification/4
# Create full model callbacks full_model_tensorboard = create_tensorboard_callback() # No validation set when training on all the data, so we can't monitor validation accuracy full_model_early_stopping = tf.keras.callbacks.EarlyStopping(monitor="accuracy", patience=3)

Note: Running the cell below will take a little while (maybe up to 30 minutes for the first epoch) because the GPU we're using in the runtime has to load all of the images into memory.

# Fit the full model to the full data full_model.fit(x=full_data, epochs=NUM_EPOCHS, callbacks=[full_model_tensorboard, full_model_early_stopping])
Train for 320 steps Epoch 1/100 320/320 [==============================] - 4730s 15s/step - loss: 1.3120 - accuracy: 0.6787 Epoch 2/100 320/320 [==============================] - 54s 168ms/step - loss: 0.4011 - accuracy: 0.8795 Epoch 3/100 320/320 [==============================] - 52s 164ms/step - loss: 0.2411 - accuracy: 0.9337 Epoch 4/100 320/320 [==============================] - 54s 168ms/step - loss: 0.1565 - accuracy: 0.9635 Epoch 5/100 320/320 [==============================] - 50s 155ms/step - loss: 0.1082 - accuracy: 0.9782 Epoch 6/100 320/320 [==============================] - 47s 146ms/step - loss: 0.0783 - accuracy: 0.9857 Epoch 7/100 320/320 [==============================] - 47s 146ms/step - loss: 0.0589 - accuracy: 0.9922 Epoch 8/100 320/320 [==============================] - 47s 146ms/step - loss: 0.0457 - accuracy: 0.9942 Epoch 9/100 320/320 [==============================] - 47s 146ms/step - loss: 0.0366 - accuracy: 0.9968 Epoch 10/100 320/320 [==============================] - 47s 146ms/step - loss: 0.0304 - accuracy: 0.9969 Epoch 11/100 320/320 [==============================] - 46s 145ms/step - loss: 0.0274 - accuracy: 0.9978 Epoch 12/100 320/320 [==============================] - 47s 147ms/step - loss: 0.0234 - accuracy: 0.9977 Epoch 13/100 320/320 [==============================] - 48s 149ms/step - loss: 0.0194 - accuracy: 0.9984 Epoch 14/100 320/320 [==============================] - 49s 152ms/step - loss: 0.0177 - accuracy: 0.9988 Epoch 15/100 320/320 [==============================] - 49s 153ms/step - loss: 0.0157 - accuracy: 0.9987 Epoch 16/100 320/320 [==============================] - 49s 153ms/step - loss: 0.0152 - accuracy: 0.9987 Epoch 17/100 320/320 [==============================] - 49s 154ms/step - loss: 0.0135 - accuracy: 0.9987
<tensorflow.python.keras.callbacks.History at 0x7fdaa6bf5128>
save_model(full_model, suffix="full-image-set-mobilenetv2-Adam")
Saving model to: drive/My Drive/Dog Vision/models/20200205-07041580886291-full-image-set-mobilenetv2-Adam.h5...
'drive/My Drive/Dog Vision/models/20200205-07041580886291-full-image-set-mobilenetv2-Adam.h5'
# Load in the full model loaded_full_model = load_model('drive/My Drive/Dog Vision/models/20200205-07041580886291-full-image-set-mobilenetv2-Adam.h5')
Loading saved model from: drive/My Drive/Dog Vision/models/20200205-07041580886291-full-image-set-mobilenetv2-Adam.h5 WARNING:tensorflow:Sequential models without an `input_shape` passed to the first layer cannot reload their optimizer state. As a result, your model isstarting with a freshly initialized optimizer.
WARNING:tensorflow:Sequential models without an `input_shape` passed to the first layer cannot reload their optimizer state. As a result, your model isstarting with a freshly initialized optimizer.

Making predictions on the test dataset

Since our model has been trained on images in the form of Tensor batches, to make predictions on the test data, we'll have to get it into the same format.

Luckily we created create_data_batches() earlier which can take a list of filenames as input and conver them into Tensor batches.

To make predictions on the test data, we'll:

  • Get the test image filenames. ✅

  • Convert the filenames into test data batches using create_data_batches() and setting the test_data parameter to True (since the test data doesn't have labels). ✅

  • Make a predictions array by passing the test batches to the predict() method called on our model.

# Load test image filenames test_path = "drive/My Drive/Dog Vision/test/" test_filenames = [test_path + fname for fname in os.listdir(test_path)] test_filenames[:10]
['drive/My Drive/Dog Vision/test/ea5ae600ca3996528b6548eceac767cf.jpg', 'drive/My Drive/Dog Vision/test/eb61a828e939eb633c1811f283d15647.jpg', 'drive/My Drive/Dog Vision/test/efb4e5dc776a639a140b022cf0835eb5.jpg', 'drive/My Drive/Dog Vision/test/f16f47570b2ef2c21c54eb1478518e8f.jpg', 'drive/My Drive/Dog Vision/test/eb3849def6fa245bcaae9ab9c64f024f.jpg', 'drive/My Drive/Dog Vision/test/eb2e408930e0873deb85484ef46f07fd.jpg', 'drive/My Drive/Dog Vision/test/f2e4e4f1ab9d156443682dc4653f23e5.jpg', 'drive/My Drive/Dog Vision/test/eb2f2ad0f785a7c85f8c62969be71e2a.jpg', 'drive/My Drive/Dog Vision/test/ec0ed65df0e457182b62ee172783c574.jpg', 'drive/My Drive/Dog Vision/test/ee878152e29a6b6ce288b44b358f9d05.jpg']
len(test_filenames)
10357
# Create test data batch test_data = create_data_batches(test_filenames, test_data=True)
Creating test data batches...
test_data
<BatchDataset shapes: (None, 224, 224, 3), types: tf.float32>

Note: Calling predict() on our full model and passing it the test data batch will take a long time to run (about a ~1hr). This is because we have to process ~10,000+ images and get our model to find patterns in those images and generate predictions based on what its learned in the training dataset.

# Make predictions on test data batch using the loaded full model test_predictions = loaded_full_model.predict(test_data, verbose=1)
97/324 [=======>......................] - ETA: 54:40
--------------------------------------------------------------------------- KeyboardInterrupt Traceback (most recent call last) <ipython-input-82-9f5a139687b9> in <module>() 1 test_predictions = loaded_full_model.predict(test_data, ----> 2 verbose=1) /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training.py in predict(self, x, batch_size, verbose, steps, callbacks, max_queue_size, workers, use_multiprocessing) 1011 max_queue_size=max_queue_size, 1012 workers=workers, -> 1013 use_multiprocessing=use_multiprocessing) 1014 1015 def reset_metrics(self): /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training_v2.py in predict(self, model, x, batch_size, verbose, steps, callbacks, max_queue_size, workers, use_multiprocessing, **kwargs) 496 model, ModeKeys.PREDICT, x=x, batch_size=batch_size, verbose=verbose, 497 steps=steps, callbacks=callbacks, max_queue_size=max_queue_size, --> 498 workers=workers, use_multiprocessing=use_multiprocessing, **kwargs) 499 500 /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training_v2.py in _model_iteration(self, model, mode, x, y, batch_size, verbose, sample_weight, steps, callbacks, max_queue_size, workers, use_multiprocessing, **kwargs) 473 mode=mode, 474 training_context=training_context, --> 475 total_epochs=1) 476 cbks.make_logs(model, epoch_logs, result, mode) 477 /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training_v2.py in run_one_epoch(model, iterator, execution_function, dataset_size, batch_size, strategy, steps_per_epoch, num_samples, mode, training_context, total_epochs) 126 step=step, mode=mode, size=current_batch_size) as batch_logs: 127 try: --> 128 batch_outs = execution_function(iterator) 129 except (StopIteration, errors.OutOfRangeError): 130 # TODO(kaftan): File bug about tf function and errors.OutOfRangeError? /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training_v2_utils.py in execution_function(input_fn) 96 # `numpy` translates Tensors to values in Eager mode. 97 return nest.map_structure(_non_none_constant_value, ---> 98 distributed_function(input_fn)) 99 100 return execution_function /tensorflow-2.1.0/python3.6/tensorflow_core/python/eager/def_function.py in __call__(self, *args, **kwds) 566 xla_context.Exit() 567 else: --> 568 result = self._call(*args, **kwds) 569 570 if tracing_count == self._get_tracing_count(): /tensorflow-2.1.0/python3.6/tensorflow_core/python/eager/def_function.py in _call(self, *args, **kwds) 604 # In this case we have not created variables on the first call. So we can 605 # run the first trace but we should fail if variables are created. --> 606 results = self._stateful_fn(*args, **kwds) 607 if self._created_variables: 608 raise ValueError("Creating variables on a non-first call to a function" /tensorflow-2.1.0/python3.6/tensorflow_core/python/eager/function.py in __call__(self, *args, **kwargs) 2361 with self._lock: 2362 graph_function, args, kwargs = self._maybe_define_function(args, kwargs) -> 2363 return graph_function._filtered_call(args, kwargs) # pylint: disable=protected-access 2364 2365 @property /tensorflow-2.1.0/python3.6/tensorflow_core/python/eager/function.py in _filtered_call(self, args, kwargs) 1609 if isinstance(t, (ops.Tensor, 1610 resource_variable_ops.BaseResourceVariable))), -> 1611 self.captured_inputs) 1612 1613 def _call_flat(self, args, captured_inputs, cancellation_manager=None): /tensorflow-2.1.0/python3.6/tensorflow_core/python/eager/function.py in _call_flat(self, args, captured_inputs, cancellation_manager) 1690 # No tape is watching; skip to running the function. 1691 return self._build_call_outputs(self._inference_function.call( -> 1692 ctx, args, cancellation_manager=cancellation_manager)) 1693 forward_backward = self._select_forward_and_backward_functions( 1694 args, /tensorflow-2.1.0/python3.6/tensorflow_core/python/eager/function.py in call(self, ctx, args, cancellation_manager) 543 inputs=args, 544 attrs=("executor_type", executor_type, "config_proto", config), --> 545 ctx=ctx) 546 else: 547 outputs = execute.execute_with_cancellation( /tensorflow-2.1.0/python3.6/tensorflow_core/python/eager/execute.py in quick_execute(op_name, num_outputs, inputs, attrs, ctx, name) 59 tensors = pywrap_tensorflow.TFE_Py_Execute(ctx._handle, device_name, 60 op_name, inputs, attrs, ---> 61 num_outputs) 62 except core._NotOkStatusException as e: 63 if name is not None: KeyboardInterrupt:
# Save predictions (NumPy array) to csv file (for access later) #np.savetxt("drive/My Drive/Dog Vision/preds_array.csv", test_predctions, delimiter=",")
# Load predictions (NumPy array) from csv file test_predictions = np.loadtxt("drive/My Drive/Dog Vision/preds_array.csv", delimiter=",")
test_predictions[:10]
array([[1.61196489e-09, 3.44086413e-12, 2.32834394e-11, ..., 1.06917716e-13, 1.58530451e-08, 1.52161670e-06], [3.17894322e-10, 3.20088262e-14, 1.85374840e-10, ..., 7.00588814e-08, 1.88822238e-08, 2.56980937e-10], [4.27301083e-09, 1.84139528e-13, 1.11784948e-09, ..., 2.71949238e-12, 2.23927123e-06, 7.41860809e-11], ..., [4.47232779e-10, 4.28004029e-07, 4.11986996e-08, ..., 4.65437893e-07, 8.21722967e-10, 8.86002116e-09], [3.50528079e-11, 1.94377336e-03, 1.44941642e-10, ..., 1.56135718e-06, 6.13228721e-08, 7.32120961e-12], [1.23221771e-08, 3.08354520e-09, 1.87174110e-10, ..., 8.16165635e-10, 9.98905063e-01, 6.73740752e-09]])
test_predictions.shape
(10357, 120)

Preparing test dataset predictions for Kaggle

Looking at the Kaggle sample submission, we find that it wants our models prediction probaiblity outputs in a DataFrame with an ID and a column for each different dog breed. https://www.kaggle.com/c/dog-breed-identification/overview/evaluation

To get the data in this format, we'll:

  • Create a pandas DataFrame with an ID column as well as a column for each dog breed. ✅

  • Add data to the ID column by extracting the test image ID's from their filepaths.

  • Add data (the prediction probabilites) to each of the dog breed columns.

  • Export the DataFrame as a CSV to submit it to Kaggle.

# ["id"] + list(unique_breeds)
# Create a pandas DataFrame with empty columns preds_df = pd.DataFrame(columns=["id"] + list(unique_breeds)) preds_df.head()
# Append test image ID's to predictions DataFrame test_ids = [os.path.splitext(path)[0] for path in os.listdir(test_path)] preds_df["id"] = test_ids
preds_df.head()
# Add the prediction probabilities to each dog breed column preds_df[list(unique_breeds)] = test_predictions preds_df.head()
# Save our predictions dataframe to CSV for submission to Kaggle preds_df.to_csv("drive/My Drive/Dog Vision/full_model_predictions_submission_1_mobilenetV2.csv", index=False)

Making predictions on custom images

To make predictions on custom images, we'll:

  • Get the filepaths of our own images.

  • Turn the filepaths into data batches using create_data_batches(). And since our custom images won't have labels, we set the test_data parameter to True.

  • Pass the custom image data batch to our model's predict() method.

  • Convert the prediction output probabilities to predictions labels.

  • Compare the predicted labels to the custom images.

# Get custom image filepaths custom_path = "drive/My Drive/Dog Vision/my-dog-photos/" custom_image_paths = [custom_path + fname for fname in os.listdir(custom_path)]
custom_image_paths
['drive/My Drive/Dog Vision/my-dog-photos/dog-photo-2.jpeg', 'drive/My Drive/Dog Vision/my-dog-photos/dog-photo-1.jpeg', 'drive/My Drive/Dog Vision/my-dog-photos/dog-photo-3.jpeg']
# Turn custom images into batch datasets custom_data = create_data_batches(custom_image_paths, test_data=True) custom_data
Creating test data batches...
<BatchDataset shapes: (None, 224, 224, 3), types: tf.float32>
# Make predictions on the custom data custom_preds = loaded_full_model.predict(custom_data)
custom_preds.shape
(3, 120)
# Get custom image prediction labels custom_pred_labels = [get_pred_label(custom_preds[i]) for i in range(len(custom_preds))] custom_pred_labels
['lakeland_terrier', 'golden_retriever', 'labrador_retriever']
# Get custom images (our unbatchify() function won't work since there aren't labels... maybe we could fix this later) custom_images = [] # Loop through unbatched data for image in custom_data.unbatch().as_numpy_iterator(): custom_images.append(image)
# Check custom image predictions plt.figure(figsize=(10, 10)) for i, image in enumerate(custom_images): plt.subplot(1, 3, i+1) plt.xticks([]) plt.yticks([]) plt.title(custom_pred_labels[i]) plt.imshow(image)
Image in a Jupyter notebook