Dog Breed Classifier Using TensorFlow (MobileNetV2)

In this tutorial, we will guide you through the process of creating a dog breed classifier using Tensorflow, powered by MobileNetV2. Tensorflow is a versatile deep learning library that allows us to leverage pre-trained models like MobileNetV2, which is known for its efficiency and accuracy in image classification tasks.

By the end of this tutorial, you will have a fully functional classifier that can accurately predict dog breeds from a given image. The steps involved include loading the pre-trained model, preparing the dataset, training the classifier, and evaluating its performance.

Prerequisites

Before we begin, make sure you have the following:

Step 1: Importing the Necessary Libraries

We start by importing all the essential libraries that will be used throughout the project. Here's a breakdown of each import:

import cv2
import numpy as np
import matplotlib.pyplot as plt
import os

import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Lambda, GlobalAveragePooling2D, Dropout, Dense
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import ModelCheckpoint

Explanation:

Step 2: Loading and Exploring the Dataset

Before we train the model, we need to load and explore the dataset to ensure that it's structured correctly. In this case, we are working with a dataset of dog images that is divided into different folders, where each folder corresponds to a dog breed.

class_folder_paths = ['../input/70-dog-breedsimage-data-set/test/'+x for x in os.listdir('../input/70-dog-breedsimage-data-set/test/')]
for class_folder_path in class_folder_paths:
    print('{0}:'.format(class_folder_path), ' ', len(os.listdir(class_folder_path)))

Step 3: Defining Dataset Directories and Visualization

In this step, we define the paths to our training, validation, and test datasets. These directories contain the images that will be used to train and evaluate the dog breed classifier.

TRAIN_DIR = '../input/70-dog-breedsimage-data-set/train/'
VAL_DIR = '../input/70-dog-breedsimage-data-set/valid/'
TEST_DIR = '../input/70-dog-breedsimage-data-set/test/'

To get a better understanding of our dataset, we can visualize some sample images from the training set. This step helps us verify that the images are correctly labeled and gives us insight into the diversity of breeds in our dataset.

import matplotlib.pyplot as plt
from PIL import Image
train_dogs=os.listdir(TRAIN_DIR)
fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(15, 7))
for i, ax in enumerate(axes.flat):
    dir = TRAIN_DIR + "/" + train_dogs[i] + "/"
    ax.imshow(Image.open(dir + os.listdir(dir)[0]))
    ax.set_title(train_dogs[i])
plt.tight_layout()
plt.show()

Explanation:

  1. Importing Libraries: We import matplotlib.pyplot for plotting and Image from the PIL library for opening and manipulating images.
  2. Listing Training Dogs: train_dogs = os.listdir(TRAIN_DIR) retrieves the names of all folders (each representing a dog breed) within the training directory.
  3. Creating Subplots: fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(15, 7)) sets up a grid of subplots (2 rows and 5 columns) to display 10 images. The figsize parameter adjusts the overall size of the figure.
  4. Displaying Images: The for loop iterates through the axes of the subplots. For each subplot, it constructs the directory path to the images of the respective dog breed and displays the first image using ax.imshow(). ax.set_title(train_dogs[i]) sets the title of each subplot to the corresponding dog breed name.
  5. Final Touches: plt.tight_layout() optimizes the layout of the plots to avoid overlaps. plt.show() renders the plots on the screen.

Step 5: Data Preprocessing and Augmentation

In this step, we set up data generators for our training, validation, and test datasets. Data augmentation helps improve the robustness of our model by artificially increasing the size of the training dataset and introducing variability in the training data.

train_data_gen = ImageDataGenerator(horizontal_flip = True,
                                    rotation_range=20,
                                    width_shift_range=0.1,
                                    height_shift_range=0.1,
                                    zoom_range=0.2)

train_generator = train_data_gen.flow_from_directory(TRAIN_DIR,
                                                     target_size = (224, 224),
                                                     color_mode = 'rgb',
                                                     batch_size = 32,
                                                     class_mode ='categorical',
                                                     shuffle = True)

val_data_gen = ImageDataGenerator()

val_generator = val_data_gen.flow_from_directory(VAL_DIR,
                                                   target_size = (224, 224),
                                                   color_mode = 'rgb',
                                                   batch_size = 32,
                                                   class_mode = 'categorical',
                                                   shuffle = False)

test_generator = val_data_gen.flow_from_directory(TEST_DIR,
                                                   target_size = (224, 224),
                                                   color_mode = 'rgb',
                                                   batch_size = 32,
                                                   class_mode = 'categorical',
                                                   shuffle = False)
Found 7946 images belonging to 70 classes.
Found 700 images belonging to 70 classes.
Found 700 images belonging to 70 classes.

Explanation:

  1. ImageDataGenerator for Training:
    • train_data_gen = ImageDataGenerator(...) initializes the ImageDataGenerator for training images with several augmentation parameters:
    • horizontal_flip=True: Randomly flip images horizontally. rotation_range=20: Randomly rotate images in the range of 20 degrees. width_shift_range=0.1: Randomly shift images horizontally by up to 10% of the image width.
    • height_shift_range=0.1: Randomly shift images vertically by up to 10% of the image height.
    • zoom_range=0.2: Randomly zoom in on images by up to 20%.
  2. Creating the Training Data Generator:
    • train_generator = train_data_gen.flow_from_directory(...)creates the training data generator that will read images from the TRAIN_DIR.
    • target_size=(224, 224): Resizes all images to 224x224 pixels, which is the input size expected by MobileNetV2.
    • color_mode='rgb': Specifies that the images should be read in RGB color mode.
    • batch_size=32: Specifies the number of images to be yielded from the generator per batch.
    • class_mode='categorical': Indicates that the labels will be one-hot encoded (suitable for multi-class classification).
    • shuffle=True: Randomly shuffles the training data for each epoch.
  3. Validation Data Generator:
    • val_data_gen = ImageDataGenerator() initializes a generator for the validation set without any augmentation (to maintain the integrity of validation data).
    • val_generator = val_data_gen.flow_from_directory(...) sets up the validation data generator similarly to the training generator.
  4. Test Data Generator:
    • The same procedure is followed for the test dataset using test_generator. Like the validation generator, it does not include augmentation.

Step 6: Mapping Class Indices to Class Labels

In this step, we extract the class indices from the training generator and create a mapping from the indices to the corresponding dog breed names. This mapping will help us understand which index corresponds to which dog breed when making predictions.

labels = train_generator.class_indices
class_mapping = dict((v,k) for k,v in labels.items())
class_mapping
{0: 'Afghan',
 1: 'African Wild Dog',
 2: 'Airedale',
 3: 'American Hairless',
 4: 'American Spaniel',
 5: 'Basenji',
 64 more...

Step 7: Building the MobileNetV2 Model

In this step, we define the architecture of our dog breed classifier using the MobileNetV2 model. This model is efficient for image classification tasks, especially with limited computational resources.

before_mobilenet = Sequential([Input((224,224,3)),
                             Lambda(preprocess_input)])

mobilenet = MobileNetV2(input_shape = (224,224,3), include_top = False)

after_mobilenet = Sequential([GlobalAveragePooling2D(),
                             Dropout(0.2),
                             Dense(70, activation = 'softmax')])

model = Sequential([before_mobilenet, mobilenet, after_mobilenet])
Output:
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224_no_top.h5
9412608/9406464 [==============================] - 0s 0us/step
9420800/9406464 [==============================] - 0s 0us/step

Explanation:

  1. Preprocessing Layer:
    • before_mobilenet : This sequential model includes an input layer that specifies the shape of the input images (224x224 pixels with 3 color channels for RGB).
    • Lambda(preprocess_input): This layer applies the preprocess_input function from MobileNetV2, which normalizes the image data to be compatible with the MobileNetV2 model's expectations.
  2. MobileNetV2 Base Model:
    • mobilenet = MobileNetV2(input_shape=(224, 224, 3), include_top=False): This line initializes the MobileNetV2 model without the top classification layer (hence include_top=False). This allows us to use the pre-trained model as a feature extractor for our specific classification task.
  3. Post-processing Layers:
    • after_mobilenet: This sequential model adds layers after the MobileNetV2 base:
    • GlobalAveragePooling2D(): This layer reduces the spatial dimensions of the output from the MobileNetV2 model by averaging the feature maps, resulting in a 1D tensor that can be easily processed.
    • Dropout(0.2): This layer randomly drops 20% of the neurons during training to help prevent overfitting.
    • Dense(70, activation='softmax'): This fully connected layer outputs the final predictions, where 70 corresponds to the number of dog breeds (classes), and softmax activation ensures that the output values represent probabilities that sum to 1.
  4. Combining the Models:
    • model = Sequential([before_mobilenet, mobilenet, after_mobilenet]): This line combines all the layers into a single sequential model that can be trained.

Step 8: Compiling the Model and Building it

In this step, we compile the MobileNetV2 model by specifying the optimizer, loss function, and evaluation metrics. This step prepares the model for training.

opt = Adam(learning_rate=0.00001)
model.compile(optimizer = opt, loss = 'categorical_crossentropy', metrics = ['accuracy'])

model.build(((None, 224, 224, 3)))

before_mobilenet.summary()
mobilenet.summary()
after_mobilenet.summary()

Explanation:

  1. Choosing an Optimizer:
    • opt = Adam(learning_rate=0.00001): This line initializes the Adam optimizer with a very small learning rate of 0.00001. Adam is a popular optimization algorithm that adapts the learning rate for each parameter, making it effective for a wide range of problems.
  2. Compiling the Model:
    • model.compile(...): This method configures the model for training with the following parameters:
    • optimizer=opt: Uses the Adam optimizer we defined earlier.
    • loss='categorical_crossentropy': Specifies the loss function to be used during training. Categorical crossentropy is suitable for multi-class classification tasks, where each training sample belongs to one of multiple classes (in our case, dog breeds).
    • metrics=['accuracy']: Specifies that we want to track the accuracy of the model during training and evaluation. Accuracy is a common metric for classification tasks, indicating the proportion of correct predictions.
  3. Building the Model:
    • model.build(((None, 224, 224, 3))): This line explicitly builds the model by specifying the input shape of the images. The None indicates that the batch size can vary. The shape (224, 224, 3) corresponds to the input images, which are 224x224 pixels in size with 3 color channels (RGB).
  4. Displaying Summaries:
    • before_mobilenet.summary(): Displays a summary of the before_mobilenet model, showing the layers, output shapes, and the number of parameters in each layer.
    • mobilenet.summary(): Displays a summary of the MobileNetV2 base model, providing similar details for its architecture.
    • after_mobilenet.summary(): Displays a summary of the after_mobilenet model, which includes the pooling, dropout, and dense layers.

Step 10: Training the Model

In this step, we train the MobileNetV2 model using the training data while validating its performance on a separate validation dataset. We also utilize a callback to save the best model during training.

train_cb = ModelCheckpoint('./model/', save_best_only = True)

model.fit(train_generator, validation_data = val_generator, callbacks = [train_cb], epochs = 20)
Output:
Epoch 1/20
249/249 [==============================] - 150s 556ms/step - loss: 4.1956 - accuracy: 0.0540 - val_loss: 3.5333 - val_accuracy: 0.1671
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 2/20
249/249 [==============================] - 119s 478ms/step - loss: 3.2224 - accuracy: 0.2662 - val_loss: 2.4435 - val_accuracy: 0.5057
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 3/20
249/249 [==============================] - 119s 476ms/step - loss: 2.3437 - accuracy: 0.4873 - val_loss: 1.5793 - val_accuracy: 0.6800
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 4/20
249/249 [==============================] - 119s 477ms/step - loss: 1.7103 - accuracy: 0.6311 - val_loss: 1.0961 - val_accuracy: 0.7986
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 5/20
249/249 [==============================] - 118s 472ms/step - loss: 1.2946 - accuracy: 0.7192 - val_loss: 0.8493 - val_accuracy: 0.8500
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 6/20
249/249 [==============================] - 118s 474ms/step - loss: 1.0649 - accuracy: 0.7570 - val_loss: 0.7184 - val_accuracy: 0.8686
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 7/20
249/249 [==============================] - 119s 476ms/step - loss: 0.8976 - accuracy: 0.7892 - val_loss: 0.6450 - val_accuracy: 0.8814
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 8/20
249/249 [==============================] - 118s 475ms/step - loss: 0.7812 - accuracy: 0.8131 - val_loss: 0.5898 - val_accuracy: 0.8971
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 9/20
249/249 [==============================] - 118s 473ms/step - loss: 0.6945 - accuracy: 0.8276 - val_loss: 0.5611 - val_accuracy: 0.9014
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 10/20
249/249 [==============================] - 118s 475ms/step - loss: 0.6254 - accuracy: 0.8370 - val_loss: 0.5310 - val_accuracy: 0.9086
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 11/20
249/249 [==============================] - 118s 473ms/step - loss: 0.5733 - accuracy: 0.8528 - val_loss: 0.5126 - val_accuracy: 0.9100
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 12/20
249/249 [==============================] - 119s 477ms/step - loss: 0.5330 - accuracy: 0.8621 - val_loss: 0.5036 - val_accuracy: 0.9157
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 13/20
249/249 [==============================] - 118s 475ms/step - loss: 0.5022 - accuracy: 0.8681 - val_loss: 0.4853 - val_accuracy: 0.9200
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 14/20
249/249 [==============================] - 120s 480ms/step - loss: 0.4662 - accuracy: 0.8755 - val_loss: 0.4751 - val_accuracy: 0.9214
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 15/20
249/249 [==============================] - 118s 474ms/step - loss: 0.4325 - accuracy: 0.8803 - val_loss: 0.4653 - val_accuracy: 0.9257
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 16/20
249/249 [==============================] - 119s 476ms/step - loss: 0.4106 - accuracy: 0.8898 - val_loss: 0.4611 - val_accuracy: 0.9214
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 17/20
249/249 [==============================] - 119s 475ms/step - loss: 0.3779 - accuracy: 0.9028 - val_loss: 0.4560 - val_accuracy: 0.9271
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 18/20
249/249 [==============================] - 119s 476ms/step - loss: 0.3522 - accuracy: 0.9056 - val_loss: 0.4498 - val_accuracy: 0.9314
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 19/20
249/249 [==============================] - 118s 474ms/step - loss: 0.3476 - accuracy: 0.9057 - val_loss: 0.4458 - val_accuracy: 0.9257
/opt/conda/lib/python3.7/site-packages/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
Epoch 20/20
249/249 [==============================] - 118s 474ms/step - loss: 0.3208 - accuracy: 0.9134 - val_loss: 0.4465 - val_accuracy: 0.9257
<keras.callbacks.History at 0x7fc74bd914d0>

Explanation:

  1. Model Checkpoint Callback:
    • train_cb = ModelCheckpoint('./model/', save_best_only=True): This line creates a callback that saves the model to the specified directory (./model/). The save_best_only=True argument ensures that only the model with the best validation performance (lowest validation loss) is saved, preventing the storage of inferior models during training.
  2. Fitting the Model:
    • model.fit(...): This method trains the model on the training data (train_generator) and evaluates it on the validation data (val_generator). The training process involves the following parameters:
    • train_generator: Provides the training data in batches.
    • validation_data=val_generator: Evaluates the model on the validation dataset after each epoch, helping to monitor performance.
    • callbacks=[train_cb]: Includes the train_cb to save the best model during training based on validation loss.
    • epochs=20: Specifies that the model will be trained for 20 epochs. An epoch is one complete pass through the training dataset.

Step 11: Saving and Evaluating the Model

In this final step, we save the trained MobileNetV2 model to a file and evaluate its performance on the test dataset.

model.save('MobileNetV2_model.h5')
model.evaluate(test_generator)
22/22 [==============================] - 4s 161ms/step - loss: 0.1654 - accuracy: 0.9629
[0.16537855565547943, 0.9628571271896362]

Explanation:

  1. Saving the Model:
    • model.save('MobileNetV2_model.h5'): This line saves the entire trained model to a file named MobileNetV2_model.h5. This file format (HDF5) allows us to store the model architecture, weights, and training configuration in a single file, making it easy to load and use later without the need for retraining.
  2. Evaluating the Model:
    • model.evaluate(test_generator): This method evaluates the saved model on the test dataset provided by test_generator. The evaluation returns the loss value (0.16) and any metrics specified during the model compilation (in our case, accuracy i.e 0.96 ). This is crucial for assessing how well the model performs on completely unseen data.

Conclusion

In this tutorial, we successfully built a dog breed classifier using the MobileNetV2 model, leveraging transfer learning techniques to achieve effective image classification. Here’s a summary of what we covered:

  1. Data Preparation: We started by preparing the dataset, including training, validation, and test sets, and visualized some sample images to understand the data better.

  2. Data Augmentation: We implemented data augmentation techniques to enhance the training dataset, helping the model generalize better and reduce the risk of overfitting.

  3. Model Architecture: We constructed the model using MobileNetV2, combining it with additional layers to adapt it for our specific classification task.

  4. Model Compilation: We compiled the model with the Adam optimizer and categorical crossentropy loss function, preparing it for the training phase.

  5. Training the Model: The model was trained on the training data, with validation data used to monitor its performance and save the best version of the model.

  6. Model Evaluation: Finally, we saved the trained model and evaluated its performance on the test dataset, giving us a final accuracy score.

This tutorial demonstrates the complete workflow of building an image classification model using deep learning. You can now take this knowledge and apply it to other classification tasks or further refine the model with more advanced techniques.

Feel free to explore and experiment with the model, adjust hyperparameters, and try different architectures to improve performance. Happy coding and best of luck with your machine learning journey!