Importing all the necessary libraries
import os
import pickle
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
# Mixed precision reduces memory usage and speeds up training
from tensorflow.keras import mixed_precision
mixed_precision.set_global_policy('mixed_float16')
# I am using my GPU
gpus = tf.config.experimental.list_physical_devices('GPU')
if gpus:
try:
for gpu in gpus:
tf.config.experimental.set_memory_growth(gpu, True)
print('Using GPU - ', gpu)
except RuntimeError as e:
print(e)
Physical devices cannot be modified after being initialized
Loading the data
# Directory that has data
DATA_DIR = "cats_and_dogs_filtered/train"
# Subdirectories for each class
data_dir_dogs = os.path.join(DATA_DIR, 'dogs')
data_dir_cats = os.path.join(DATA_DIR, 'cats')
# os.listdir returns a list containing all files under the given dir
print(f"There are {len(os.listdir(data_dir_dogs))} images of dogs.")
print(f"There are {len(os.listdir(data_dir_cats))} images of cats.")
There are 1500 images of dogs. There are 1500 images of cats.
# Get the filenames for cats and dogs images
cats_filenames = [os.path.join(data_dir_cats, filename) for filename in os.listdir(data_dir_cats)]
dogs_filenames = [os.path.join(data_dir_dogs, filename) for filename in os.listdir(data_dir_dogs)]
fig, axes = plt.subplots(2, 4, figsize=(14, 7))
fig.suptitle('Cat and Dog Images', fontsize=16)
# Plot the first 4 images of each class
for i, cat_image in enumerate(cats_filenames[:4]):
img = tf.keras.utils.load_img(cat_image)
axes[0, i].imshow(img)
axes[0, i].set_title(f'Example Cat {i}')
for i, dog_image in enumerate(dogs_filenames[:4]):
img = tf.keras.utils.load_img(dog_image)
axes[1, i].imshow(img)
axes[1, i].set_title(f'Example Dog {i}')
plt.show()
Getting training and validation dataset -
def train_val_datasets():
"""Creates datasets for training and validation.
Returns:
(tf.data.Dataset, tf.data.Dataset): Training and validation datasets.
"""
training_dataset, validation_dataset = tf.keras.utils.image_dataset_from_directory(
directory=DATA_DIR,
image_size=(120,120),
batch_size=20,
label_mode='binary',
validation_split=0.15,
subset='both',
seed=42
)
return training_dataset, validation_dataset
# Create the datasets
training_dataset, validation_dataset = train_val_datasets()
Found 3000 files belonging to 2 classes. Using 2550 files for training. Using 450 files for validation.
# Get the first batch of images and labels
for images, labels in training_dataset.take(1):
example_batch_images = images
example_batch_labels = labels
print(f"Maximum pixel value of images: {np.max(example_batch_images)}\n")
print(f"Shape of batch of images: {example_batch_images.shape}")
print(f"Shape of batch of labels: {example_batch_labels.shape}")
Maximum pixel value of images: 255.0 Shape of batch of images: (20, 120, 120, 3) Shape of batch of labels: (20, 1)
Creating Augmentation Model
def create_augmentation_model():
"""Creates a model (layers stacked on top of each other) for augmenting images of cats and dogs.
Returns:
tf.keras.Model: The model made up of the layers that will be used to augment the images of cats and dogs.
"""
augmentation_model = tf.keras.Sequential([
tf.keras.Input(shape=(120, 120, 3)),
tf.keras.layers.RandomFlip('horizontal'),
tf.keras.layers.RandomRotation(0.2, fill_mode='nearest'),
tf.keras.layers.RandomTranslation(0.2, 0.2, fill_mode='nearest'),
tf.keras.layers.RandomZoom(0.2, fill_mode='nearest')
])
return augmentation_model
# Load your model for augmentation
data_augmentor = create_augmentation_model()
# Take a sample image
sample_image = tf.convert_to_tensor(np.array(sample_image), dtype=tf.int32)
images = [sample_image]
# Apply random augmentation 3 times
for _ in range(3):
image_aug = data_augmentor(tf.expand_dims(sample_image, axis=0))
image_aug = tf.keras.utils.array_to_img(tf.squeeze(image_aug))
images.append(image_aug)
fig, axs = plt.subplots(1, 4, figsize=(14, 7))
for ax, image, title in zip(axs, images, ['Original Image', 'Augmented 1', 'Augmented 2', 'Augmented 3']):
ax.imshow(image, cmap='gray')
ax.set_title(title)
ax.axis('off')
plt.show()
Creating the model -
def create_model():
"""Creates the untrained model for classifying cats and dogs.
Returns:
tf.keras.Model: The model that will be trained to classify cats and dogs.
"""
# Get the augmentation layers (or model) from your earlier function
augmentation_layers = create_augmentation_model()
# Don't forget to add the augmentation layers! These usually go right after the Input!
model = tf.keras.models.Sequential([
tf.keras.Input(shape=(120,120,3)),
augmentation_layers,
tf.keras.layers.Rescaling(1./255),
# Improved convolutional base with increased filters
tf.keras.layers.Conv2D(32, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(2, 2),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(2, 2),
tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(2, 2),
tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(2, 2),
tf.keras.layers.Conv2D(256, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(2, 2),
# Flatten and add fully connected layers with dropout
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(256, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(
optimizer=tf.keras.optimizers.RMSprop(learning_rate=0.0001),
loss='binary_crossentropy',
metrics=['accuracy']
)
return model
# Create the compiled but untrained model
model = create_model()
Check that the architecture you used is compatible with the dataset (you can ignore the warnings prompted by using the GPU):
try:
model.evaluate(example_batch_images, example_batch_labels, verbose=False)
except:
print("Your model is not compatible with the dataset you defined earlier. Check that the loss function, last layer and label_mode are compatible with one another.")
else:
predictions = model.predict(example_batch_images, verbose=False)
print(f"predictions have shape: {predictions.shape}")
predictions have shape: (20, 1)
Creating EarlyStoppingCallback -
class EarlyStoppingCallback(tf.keras.callbacks.Callback):
# Define the correct function signature for on_epoch_end method
def on_epoch_end(self, epoch, logs):
# Check if the accuracy is greater or equal to 0.8 and validation accuracy is greater or equal to 0.8
if logs['accuracy'] >= 0.80 and logs['val_accuracy'] >= 0.80:
self.model.stop_training = True
print("\nReached 80% train accuracy and 80% validation accuracy, so cancelling training!")
Training the Model -
history = model.fit(
training_dataset,
epochs=35,
validation_data=validation_dataset,
callbacks = [EarlyStoppingCallback()]
)
Epoch 1/35 128/128 [==============================] - 59s 410ms/step - loss: 0.8867 - accuracy: 0.5294 - val_loss: 0.7866 - val_accuracy: 0.4911 Epoch 2/35 128/128 [==============================] - 47s 362ms/step - loss: 0.8065 - accuracy: 0.5573 - val_loss: 0.9798 - val_accuracy: 0.4911 Epoch 3/35 128/128 [==============================] - 47s 365ms/step - loss: 0.7473 - accuracy: 0.5663 - val_loss: 0.7946 - val_accuracy: 0.4956 Epoch 4/35 128/128 [==============================] - 48s 372ms/step - loss: 0.7400 - accuracy: 0.5808 - val_loss: 0.6592 - val_accuracy: 0.5889 Epoch 5/35 128/128 [==============================] - 46s 360ms/step - loss: 0.7047 - accuracy: 0.5941 - val_loss: 0.6387 - val_accuracy: 0.6356 Epoch 6/35 128/128 [==============================] - 46s 356ms/step - loss: 0.6911 - accuracy: 0.6004 - val_loss: 0.6898 - val_accuracy: 0.6178 Epoch 7/35 128/128 [==============================] - 45s 354ms/step - loss: 0.6741 - accuracy: 0.6231 - val_loss: 0.8166 - val_accuracy: 0.5444 Epoch 8/35 128/128 [==============================] - 45s 354ms/step - loss: 0.6686 - accuracy: 0.6212 - val_loss: 0.7677 - val_accuracy: 0.5467 Epoch 9/35 128/128 [==============================] - 45s 355ms/step - loss: 0.6562 - accuracy: 0.6322 - val_loss: 0.6416 - val_accuracy: 0.6778 Epoch 10/35 128/128 [==============================] - 46s 355ms/step - loss: 0.6483 - accuracy: 0.6384 - val_loss: 0.6720 - val_accuracy: 0.6489 Epoch 11/35 128/128 [==============================] - 45s 354ms/step - loss: 0.6388 - accuracy: 0.6447 - val_loss: 0.6432 - val_accuracy: 0.6422 Epoch 12/35 128/128 [==============================] - 45s 348ms/step - loss: 0.6427 - accuracy: 0.6404 - val_loss: 0.6293 - val_accuracy: 0.6822 Epoch 13/35 128/128 [==============================] - 45s 353ms/step - loss: 0.6429 - accuracy: 0.6506 - val_loss: 0.6205 - val_accuracy: 0.6933 Epoch 14/35 128/128 [==============================] - 46s 356ms/step - loss: 0.6161 - accuracy: 0.6682 - val_loss: 0.6310 - val_accuracy: 0.6489 Epoch 15/35 128/128 [==============================] - 46s 356ms/step - loss: 0.6278 - accuracy: 0.6600 - val_loss: 0.5882 - val_accuracy: 0.7089 Epoch 16/35 128/128 [==============================] - 46s 355ms/step - loss: 0.6324 - accuracy: 0.6549 - val_loss: 0.6302 - val_accuracy: 0.6489 Epoch 17/35 128/128 [==============================] - 45s 354ms/step - loss: 0.6038 - accuracy: 0.6773 - val_loss: 0.6234 - val_accuracy: 0.6733 Epoch 18/35 128/128 [==============================] - 46s 355ms/step - loss: 0.6020 - accuracy: 0.6812 - val_loss: 0.5564 - val_accuracy: 0.7244 Epoch 19/35 128/128 [==============================] - 46s 356ms/step - loss: 0.6151 - accuracy: 0.6804 - val_loss: 0.5713 - val_accuracy: 0.7244 Epoch 20/35 128/128 [==============================] - 46s 356ms/step - loss: 0.6024 - accuracy: 0.6839 - val_loss: 0.5928 - val_accuracy: 0.7067 Epoch 21/35 128/128 [==============================] - 46s 360ms/step - loss: 0.5903 - accuracy: 0.6800 - val_loss: 0.5868 - val_accuracy: 0.7200 Epoch 22/35 128/128 [==============================] - 45s 350ms/step - loss: 0.5993 - accuracy: 0.6808 - val_loss: 0.6682 - val_accuracy: 0.6089 Epoch 23/35 128/128 [==============================] - 46s 356ms/step - loss: 0.5794 - accuracy: 0.6918 - val_loss: 0.5354 - val_accuracy: 0.7267 Epoch 24/35 128/128 [==============================] - 45s 351ms/step - loss: 0.5848 - accuracy: 0.6992 - val_loss: 0.5405 - val_accuracy: 0.7556 Epoch 25/35 128/128 [==============================] - 45s 347ms/step - loss: 0.5793 - accuracy: 0.7008 - val_loss: 0.5484 - val_accuracy: 0.7156 Epoch 26/35 128/128 [==============================] - 46s 356ms/step - loss: 0.5667 - accuracy: 0.7047 - val_loss: 0.5128 - val_accuracy: 0.7689 Epoch 27/35 128/128 [==============================] - 45s 354ms/step - loss: 0.5734 - accuracy: 0.7071 - val_loss: 0.5520 - val_accuracy: 0.7089 Epoch 28/35 128/128 [==============================] - 2377s 19s/step - loss: 0.5773 - accuracy: 0.6957 - val_loss: 0.5123 - val_accuracy: 0.7667 Epoch 29/35 128/128 [==============================] - 23s 177ms/step - loss: 0.5751 - accuracy: 0.7063 - val_loss: 0.5178 - val_accuracy: 0.7578 Epoch 30/35 128/128 [==============================] - 22s 174ms/step - loss: 0.5579 - accuracy: 0.7224 - val_loss: 0.5333 - val_accuracy: 0.7200 Epoch 31/35 128/128 [==============================] - 23s 181ms/step - loss: 0.5526 - accuracy: 0.7208 - val_loss: 0.5062 - val_accuracy: 0.7689 Epoch 32/35 128/128 [==============================] - 25s 197ms/step - loss: 0.5575 - accuracy: 0.7243 - val_loss: 0.6160 - val_accuracy: 0.6689 Epoch 33/35 128/128 [==============================] - 25s 196ms/step - loss: 0.5589 - accuracy: 0.7184 - val_loss: 0.5557 - val_accuracy: 0.7200 Epoch 34/35 128/128 [==============================] - 25s 198ms/step - loss: 0.5564 - accuracy: 0.7286 - val_loss: 0.5011 - val_accuracy: 0.7667 Epoch 35/35 128/128 [==============================] - 25s 199ms/step - loss: 0.5463 - accuracy: 0.7302 - val_loss: 0.5754 - val_accuracy: 0.7044
In the above case, as you can see the accuracy has not reached to 80% since I have used a very small batch size of 20 due to GPU memory resource limitations. If you use batch size of 128 (as suggested by the course), you will attain very high training and validation accuracy
Accuracy and Loss check -
# Get training and validation accuracies
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
# Get number of epochs
epochs = range(len(acc))
fig, ax = plt.subplots(1, 2, figsize=(10, 5))
fig.suptitle('Training and validation accuracy')
for i, (data, label) in enumerate(zip([(acc, val_acc), (loss, val_loss)], ["Accuracy", "Loss"])):
ax[i].plot(epochs, data[0], 'r', label="Training " + label)
ax[i].plot(epochs, data[1], 'b', label="Validation " + label)
ax[i].legend()
ax[i].set_xlabel('epochs')
plt.show()
Unlike the previous lab, this time the model should not be overfitting, which means that it is doing a great job at classifying the images in the training and validation sets.