Data pipeline with Keras and tf.data

目录

<!DOCTYPE html>

Week_2_Programming_Assignment

Programming Assignment

Data pipeline with Keras and tf.data

Instructions

In this notebook, you will implement a data processing pipeline using tools from both Keras and the tf.data module. You will use the ImageDataGenerator class in the tf.keras module to feed a network with training and test images from a local directory containing a subset of the LSUN dataset, and train the model both with and without data augmentation. You will then use the map and filter functions of the Dataset class with the CIFAR-100 dataset to train a network to classify a processed subset of the images.

Some code cells are provided you in the notebook. You should avoid editing provided code, and make sure to execute the cells in order to avoid unexpected errors. Some cells begin with the line:

#### GRADED CELL ####

Don't move or edit this first line - this is what the automatic grader looks for to recognise graded cells. These cells require you to write your own code to complete them, and are automatically graded when you submit the notebook. Don't edit the function name or signature provided in these cells, otherwise the automatic grader might not function properly. Inside these graded cells, you can use any functions or classes that are imported below, but make sure you don't use any variables that are outside the scope of the function.

How to submit

Complete all the tasks you are asked for in the worksheet. When you have finished and are happy with your code, press the Submit Assignment button at the top of this notebook.

Let's get started!

We'll start running some imports, and loading the dataset. Do not edit the existing imports in the following cell. If you would like to make further Tensorflow imports, you should add them here.

In [2]:
#### PACKAGE IMPORTS ####

# Run this cell first to import all required packages. Do not make any imports elsewhere in the notebook

import tensorflow as tf
from tensorflow.keras.datasets import cifar100
import numpy as np
import matplotlib.pyplot as plt
import json
%matplotlib inline

# If you would like to make further imports from tensorflow, add them here

Part 1: tf.keras

Church Classroom Conference Room

The LSUN Dataset

In the first part of this assignment, you will use a subset of the LSUN dataset. This is a large-scale image dataset with 10 scene and 20 object categories. A subset of the LSUN dataset has been provided, and has already been split into training and test sets. The three classes included in the subset are church_outdoor, classroom and conference_room.

  • F. Yu, A. Seff, Y. Zhang, S. Song, T. Funkhouser and J. Xia. "LSUN: Construction of a Large-scale Image Dataset using Deep Learning with Humans in the Loop". arXiv:1506.03365, 10 Jun 2015

Your goal is to use the Keras preprocessing tools to construct a data ingestion and augmentation pipeline to train a neural network to classify the images into the three classes.

In [3]:
# Save the directory locations for the training, validation and test sets

train_dir = 'data/lsun/train'
valid_dir = 'data/lsun/valid'
test_dir = 'data/lsun/test'

Create a data generator using the ImageDataGenerator class

You should first write a function that creates an ImageDataGenerator object, which rescales the image pixel values by a factor of 1/255.

In [4]:
#### GRADED CELL ####

# Complete the following function. 
# Make sure to not change the function name or arguments.

def get_ImageDataGenerator():
    """
    This function should return an instance of the ImageDataGenerator class.
    This instance should be set up to rescale the data with the above scaling factor.
    """
    image_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255)
    return image_gen
    
    
In [5]:
# Call the function to get an ImageDataGenerator as specified

image_gen = get_ImageDataGenerator()

You should now write a function that returns a generator object that will yield batches of images and labels from the training and test set directories. The generators should:

  • Generate batches of size 20.
  • Resize the images to 64 x 64 x 3.
  • Return one-hot vectors for labels. These should be encoded as follows:
    • classroom $\rightarrow$ [1., 0., 0.]
    • conference_room $\rightarrow$ [0., 1., 0.]
    • church_outdoor $\rightarrow$ [0., 0., 1.]
  • Pass in an optional random seed for shuffling (this should be passed into the flow_from_directory method).

Hint: you may need to refer to the documentation for the ImageDataGenerator.

In [6]:
#### GRADED CELL ####

# Complete the following function.
# Make sure not to change the function name or arguments.

def get_generator(image_data_generator, directory, seed=None):
    """
    This function takes an ImageDataGenerator object in the first argument and a 
    directory path in the second argument.
    It should use the ImageDataGenerator to return a generator object according 
    to the above specifications. 
    The seed argument should be passed to the flow_from_directory method.
    """
    image_gen = image_data_generator.flow_from_directory(directory=directory, target_size=(64,64),
                                              batch_size=20, seed=seed,class_mode="categorical" )
    return image_gen
In [7]:
# Run this cell to define training and validation generators

train_generator = get_generator(image_gen, train_dir)
valid_generator = get_generator(image_gen, valid_dir)
Found 300 images belonging to 3 classes.
Found 120 images belonging to 3 classes.

We are using a small subset of the dataset for demonstrative purposes in this assignment.

Display sample images and labels from the training set

The following cell depends on your function get_generator to be implemented correctly. If it raises an error, go back and check the function specifications carefully.

In [8]:
# Display a few images and labels from the training set

batch = next(train_generator)
batch_images = np.array(batch[0])
batch_labels = np.array(batch[1])
lsun_classes = ['classroom', 'conference_room', 'church_outdoor']

plt.figure(figsize=(16,10))
for i in range(20):
    ax = plt.subplot(4, 5, i+1)
    plt.imshow(batch_images[i])
    plt.title(lsun_classes[np.where(batch_labels[i] == 1.)[0][0]])
    plt.axis('off')
In [9]:
# Reset the training generator

train_generator = get_generator(image_gen, train_dir)
Found 300 images belonging to 3 classes.

Build the neural network model

You will now build and compile a convolutional neural network classifier. Using the functional API, build your model according to the following specifications:

  • The model should use the input_shape in the function argument to define the Input layer.
  • The first hidden layer should be a Conv2D layer with 8 filters, a 8x8 kernel size.
  • The second hidden layer should be a MaxPooling2D layer with a 2x2 pooling window size.
  • The third hidden layer should be a Conv2D layer with 4 filters, a 4x4 kernel size.
  • The fourth hidden layer should be a MaxPooling2D layer with a 2x2 pooling window size.
  • This should be followed by a Flatten layer, and then a Dense layer with 16 units and ReLU activation.
  • The final layer should be a Dense layer with 3 units and softmax activation.
  • All Conv2D layers should use "SAME" padding and a ReLU activation function.

In total, the network should have 8 layers. The model should then be compiled with the Adam optimizer with learning rate 0.0005, categorical cross entropy loss, and categorical accuracy metric.

In [10]:
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, MaxPool2D, Flatten, Dense, BatchNormalization, Dropout
In [18]:
#### GRADED CELL ####

# Complete the following function.
# Make sure not to change the function name or arguments.

def get_model(input_shape):
    """
    This function should build and compile a CNN model according to the above specification,
    using the functional API. Your function should return the model.
    """
    inputs = Input(shape=input_shape)
    x = Conv2D(filters=8, kernel_size=(8,8), padding="same", activation="relu")(inputs)
    x = MaxPool2D(pool_size=(2, 2))(x)
    x = Conv2D(filters=4, kernel_size=(4,4), padding="same", activation="relu")(x)
    x = MaxPool2D(pool_size=(2, 2))(x)
    x = Flatten()(x)
    x = Dense(units=16, activation="relu")(x)
    output = Dense(units=3,activation="softmax")(x)
    
    model = Model(inputs=inputs, outputs=output)
    model.compile(optimizer=tf.keras.optimizers.Adam(0.0005), loss="categorical_crossentropy", metrics=["accuracy"])
    return model
In [19]:
# Build and compile the model, print the model summary

lsun_model = get_model((64, 64, 3))
lsun_model.summary()
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         [(None, 64, 64, 3)]       0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 64, 64, 8)         1544      
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 32, 32, 8)         0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 32, 32, 4)         516       
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 16, 16, 4)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1024)              0         
_________________________________________________________________
dense_2 (Dense)              (None, 16)                16400     
_________________________________________________________________
dense_3 (Dense)              (None, 3)                 51        
=================================================================
Total params: 18,511
Trainable params: 18,511
Non-trainable params: 0
_________________________________________________________________

Train the neural network model

You should now write a function to train the model for a specified number of epochs (specified in the epochs argument). The function takes a model argument, as well as train_gen and valid_gen arguments for the training and validation generators respectively, which you should use for training and validation data in the training run. You should also use the following callbacks:

  • An EarlyStopping callback that monitors the validation accuracy and has patience set to 10.
  • A ReduceLROnPlateau callback that monitors the validation loss and has the factor set to 0.5 and minimum learning set to 0.0001

Your function should return the training history.

In [20]:
#### GRADED CELL ####

# Complete the following function.
# Make sure not to change the function name or arguments.

def train_model(model, train_gen, valid_gen, epochs):
    """
    This function should define the callback objects specified above, and then use the
    train_gen and valid_gen generator object arguments to train the model for the (maximum) 
    number of epochs specified in the function argument, using the defined callbacks.
    The function should return the training history.
    """
    earlystopping = tf.keras.callbacks.EarlyStopping(monitor="val_accuracy", patience=10)
    reduce = tf.keras.callbacks.ReduceLROnPlateau(monitor="val_loss", factor=0.5, min_lr=0.0001)
    history = model.fit(train_gen, validation_data=valid_gen, epochs=epochs)
    return history
In [21]:
# Train the model for (maximum) 50 epochs

history = train_model(lsun_model, train_generator, valid_generator, epochs=50)
Train for 15 steps, validate for 6 steps
Epoch 1/50
15/15 [==============================] - 9s 600ms/step - loss: 1.1032 - accuracy: 0.4100 - val_loss: 1.0751 - val_accuracy: 0.4417
Epoch 2/50
15/15 [==============================] - 9s 580ms/step - loss: 1.0709 - accuracy: 0.4000 - val_loss: 1.0585 - val_accuracy: 0.4250
Epoch 3/50
15/15 [==============================] - 9s 568ms/step - loss: 1.0363 - accuracy: 0.4900 - val_loss: 1.0192 - val_accuracy: 0.4750
Epoch 4/50
15/15 [==============================] - 9s 567ms/step - loss: 0.9903 - accuracy: 0.5033 - val_loss: 0.9858 - val_accuracy: 0.4917
Epoch 5/50
15/15 [==============================] - 9s 581ms/step - loss: 0.9450 - accuracy: 0.5200 - val_loss: 0.9486 - val_accuracy: 0.5000
Epoch 6/50
15/15 [==============================] - 9s 569ms/step - loss: 0.8876 - accuracy: 0.6067 - val_loss: 0.9014 - val_accuracy: 0.5000
Epoch 7/50
15/15 [==============================] - 9s 580ms/step - loss: 0.8000 - accuracy: 0.6800 - val_loss: 0.8376 - val_accuracy: 0.5917
Epoch 8/50
15/15 [==============================] - 9s 575ms/step - loss: 0.7131 - accuracy: 0.7167 - val_loss: 0.8496 - val_accuracy: 0.6083
Epoch 9/50
15/15 [==============================] - 9s 573ms/step - loss: 0.6608 - accuracy: 0.7133 - val_loss: 0.7973 - val_accuracy: 0.6500
Epoch 10/50
15/15 [==============================] - 9s 580ms/step - loss: 0.5937 - accuracy: 0.7733 - val_loss: 0.8233 - val_accuracy: 0.6167
Epoch 11/50
15/15 [==============================] - 8s 567ms/step - loss: 0.5602 - accuracy: 0.7967 - val_loss: 0.8127 - val_accuracy: 0.6250
Epoch 12/50
15/15 [==============================] - 9s 580ms/step - loss: 0.5102 - accuracy: 0.8100 - val_loss: 0.8139 - val_accuracy: 0.6583
Epoch 13/50
15/15 [==============================] - 9s 580ms/step - loss: 0.4595 - accuracy: 0.8433 - val_loss: 0.8383 - val_accuracy: 0.6250
Epoch 14/50
15/15 [==============================] - 9s 587ms/step - loss: 0.4367 - accuracy: 0.8333 - val_loss: 0.8479 - val_accuracy: 0.6417
Epoch 15/50
15/15 [==============================] - 9s 580ms/step - loss: 0.4220 - accuracy: 0.8267 - val_loss: 0.8695 - val_accuracy: 0.6417
Epoch 16/50
15/15 [==============================] - 9s 580ms/step - loss: 0.3819 - accuracy: 0.8567 - val_loss: 0.9151 - val_accuracy: 0.6250
Epoch 17/50
15/15 [==============================] - 9s 582ms/step - loss: 0.3519 - accuracy: 0.8767 - val_loss: 0.9348 - val_accuracy: 0.6250
Epoch 18/50
15/15 [==============================] - 9s 573ms/step - loss: 0.3217 - accuracy: 0.9033 - val_loss: 0.9027 - val_accuracy: 0.6417
Epoch 19/50
15/15 [==============================] - 9s 580ms/step - loss: 0.2927 - accuracy: 0.8867 - val_loss: 0.9485 - val_accuracy: 0.6417
Epoch 20/50
15/15 [==============================] - 9s 573ms/step - loss: 0.2689 - accuracy: 0.9200 - val_loss: 0.9961 - val_accuracy: 0.6000
Epoch 21/50
15/15 [==============================] - 9s 567ms/step - loss: 0.2876 - accuracy: 0.8933 - val_loss: 1.0663 - val_accuracy: 0.5917
Epoch 22/50
15/15 [==============================] - 9s 580ms/step - loss: 0.2412 - accuracy: 0.9067 - val_loss: 1.0413 - val_accuracy: 0.6083
Epoch 23/50
15/15 [==============================] - 9s 573ms/step - loss: 0.2202 - accuracy: 0.9333 - val_loss: 1.0477 - val_accuracy: 0.5750
Epoch 24/50
15/15 [==============================] - 9s 573ms/step - loss: 0.2213 - accuracy: 0.9233 - val_loss: 1.0914 - val_accuracy: 0.6083
Epoch 25/50
15/15 [==============================] - 8s 560ms/step - loss: 0.2181 - accuracy: 0.9267 - val_loss: 1.0826 - val_accuracy: 0.5917
Epoch 26/50
15/15 [==============================] - 8s 561ms/step - loss: 0.1827 - accuracy: 0.9467 - val_loss: 1.1177 - val_accuracy: 0.6167
Epoch 27/50
15/15 [==============================] - 9s 580ms/step - loss: 0.1730 - accuracy: 0.9533 - val_loss: 1.1339 - val_accuracy: 0.6167
Epoch 28/50
15/15 [==============================] - 9s 573ms/step - loss: 0.1545 - accuracy: 0.9500 - val_loss: 1.1355 - val_accuracy: 0.5917
Epoch 29/50
15/15 [==============================] - 9s 580ms/step - loss: 0.1215 - accuracy: 0.9800 - val_loss: 1.1267 - val_accuracy: 0.6000
Epoch 30/50
15/15 [==============================] - 9s 580ms/step - loss: 0.1145 - accuracy: 0.9667 - val_loss: 1.1475 - val_accuracy: 0.5917
Epoch 31/50
15/15 [==============================] - 9s 580ms/step - loss: 0.1026 - accuracy: 0.9833 - val_loss: 1.1891 - val_accuracy: 0.6000
Epoch 32/50
15/15 [==============================] - 8s 567ms/step - loss: 0.0966 - accuracy: 0.9800 - val_loss: 1.1930 - val_accuracy: 0.6167
Epoch 33/50
15/15 [==============================] - 8s 567ms/step - loss: 0.0880 - accuracy: 0.9833 - val_loss: 1.2163 - val_accuracy: 0.6083
Epoch 34/50
15/15 [==============================] - 9s 587ms/step - loss: 0.0849 - accuracy: 0.9867 - val_loss: 1.2635 - val_accuracy: 0.6000
Epoch 35/50
15/15 [==============================] - 9s 580ms/step - loss: 0.0859 - accuracy: 0.9800 - val_loss: 1.3062 - val_accuracy: 0.6250
Epoch 36/50
15/15 [==============================] - 9s 575ms/step - loss: 0.0650 - accuracy: 0.9900 - val_loss: 1.3436 - val_accuracy: 0.6083
Epoch 37/50
15/15 [==============================] - 9s 582ms/step - loss: 0.0577 - accuracy: 0.9967 - val_loss: 1.3460 - val_accuracy: 0.6083
Epoch 38/50
15/15 [==============================] - 9s 578ms/step - loss: 0.0497 - accuracy: 1.0000 - val_loss: 1.3644 - val_accuracy: 0.6167
Epoch 39/50
15/15 [==============================] - 9s 580ms/step - loss: 0.0443 - accuracy: 0.9967 - val_loss: 1.3887 - val_accuracy: 0.6333
Epoch 40/50
15/15 [==============================] - 9s 582ms/step - loss: 0.0440 - accuracy: 1.0000 - val_loss: 1.4441 - val_accuracy: 0.6000
Epoch 41/50
15/15 [==============================] - 9s 591ms/step - loss: 0.0396 - accuracy: 0.9967 - val_loss: 1.4799 - val_accuracy: 0.5917
Epoch 42/50
15/15 [==============================] - 9s 580ms/step - loss: 0.0351 - accuracy: 1.0000 - val_loss: 1.4496 - val_accuracy: 0.6083
Epoch 43/50
15/15 [==============================] - 9s 573ms/step - loss: 0.0316 - accuracy: 1.0000 - val_loss: 1.4658 - val_accuracy: 0.5833
Epoch 44/50
15/15 [==============================] - 9s 573ms/step - loss: 0.0265 - accuracy: 1.0000 - val_loss: 1.5549 - val_accuracy: 0.5917
Epoch 45/50
15/15 [==============================] - 9s 568ms/step - loss: 0.0247 - accuracy: 1.0000 - val_loss: 1.5733 - val_accuracy: 0.6083
Epoch 46/50
15/15 [==============================] - 8s 560ms/step - loss: 0.0224 - accuracy: 1.0000 - val_loss: 1.5878 - val_accuracy: 0.6083
Epoch 47/50
15/15 [==============================] - 8s 567ms/step - loss: 0.0207 - accuracy: 1.0000 - val_loss: 1.6142 - val_accuracy: 0.6000
Epoch 48/50
15/15 [==============================] - 9s 593ms/step - loss: 0.0184 - accuracy: 1.0000 - val_loss: 1.6514 - val_accuracy: 0.6000
Epoch 49/50
15/15 [==============================] - 9s 573ms/step - loss: 0.0173 - accuracy: 1.0000 - val_loss: 1.6929 - val_accuracy: 0.6000
Epoch 50/50
15/15 [==============================] - 9s 580ms/step - loss: 0.0151 - accuracy: 1.0000 - val_loss: 1.7230 - val_accuracy: 0.6000

Plot the learning curves

In [22]:
# Run this cell to plot accuracy vs epoch and loss vs epoch

plt.figure(figsize=(15,5))
plt.subplot(121)
try:
    plt.plot(history.history['accuracy'])
    plt.plot(history.history['val_accuracy'])
except KeyError:
    try:
        plt.plot(history.history['acc'])
        plt.plot(history.history['val_acc'])
    except KeyError:
        plt.plot(history.history['categorical_accuracy'])
        plt.plot(history.history['val_categorical_accuracy'])
plt.title('Accuracy vs. epochs')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Training', 'Validation'], loc='lower right')

plt.subplot(122)
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Loss vs. epochs')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Training', 'Validation'], loc='upper right')
plt.show() 

You may notice overfitting in the above plots, through a growing discrepancy between the training and validation loss and accuracy. We will aim to mitigate this using data augmentation. Given our limited dataset, we may be able to improve the performance by applying random modifications to the images in the training data, effectively increasing the size of the dataset.

Create a new data generator with data augmentation

You should now write a function to create a new ImageDataGenerator object, which performs the following data preprocessing and augmentation:

  • Scales the image pixel values by a factor of 1/255.
  • Randomly rotates images by up to 30 degrees
  • Randomly alters the brightness (picks a brightness shift value) from the range (0.5, 1.5)
  • Randomly flips images horizontally

Hint: you may need to refer to the documentation for the ImageDataGenerator.

In [23]:
#### GRADED CELL ####

# Complete the following function. 
# Make sure to not change the function name or arguments.

def get_ImageDataGenerator_augmented():
    """
    This function should return an instance of the ImageDataGenerator class 
    with the above specifications.
    """
    image_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=30,brightness_range=(0.5, 1.5),
                                                                horizontal_flip=True)
    return image_gen
In [24]:
# Call the function to get an ImageDataGenerator as specified

image_gen_aug = get_ImageDataGenerator_augmented()
In [25]:
# Run this cell to define training and validation generators 

valid_generator_aug = get_generator(image_gen_aug, valid_dir)
train_generator_aug = get_generator(image_gen_aug, train_dir, seed=10)
Found 120 images belonging to 3 classes.
Found 300 images belonging to 3 classes.
In [26]:
# Reset the original train_generator with the same random seed

train_generator = get_generator(image_gen, train_dir, seed=10)
Found 300 images belonging to 3 classes.

Display sample augmented images and labels from the training set

The following cell depends on your function get_generator to be implemented correctly. If it raises an error, go back and check the function specifications carefully.

The cell will display augmented and non-augmented images (and labels) from the training dataset, using the train_generator_aug and train_generator objects defined above (if the images do not correspond to each other, check you have implemented the seed argument correctly).

In [27]:
# Display a few images and labels from the non-augmented and augmented generators

batch = next(train_generator)
batch_images = np.array(batch[0])
batch_labels = np.array(batch[1])

aug_batch = next(train_generator_aug)
aug_batch_images = np.array(aug_batch[0])
aug_batch_labels = np.array(aug_batch[1])

plt.figure(figsize=(16,5))
plt.suptitle("Unaugmented images", fontsize=16)
for n, i in enumerate(np.arange(10)):
    ax = plt.subplot(2, 5, n+1)
    plt.imshow(batch_images[i])
    plt.title(lsun_classes[np.where(batch_labels[i] == 1.)[0][0]])
    plt.axis('off')
plt.figure(figsize=(16,5))
plt.suptitle("Augmented images", fontsize=16)
for n, i in enumerate(np.arange(10)):
    ax = plt.subplot(2, 5, n+1)
    plt.imshow(aug_batch_images[i])
    plt.title(lsun_classes[np.where(aug_batch_labels[i] == 1.)[0][0]])
    plt.axis('off')
In [28]:
# Reset the augmented data generator

train_generator_aug = get_generator(image_gen_aug, train_dir)
Found 300 images belonging to 3 classes.

Train a new model on the augmented dataset

In [29]:
# Build and compile a new model

lsun_new_model = get_model((64, 64, 3))
In [30]:
# Train the model

history_augmented = train_model(lsun_new_model, train_generator_aug, valid_generator_aug, epochs=50)
Train for 15 steps, validate for 6 steps
Epoch 1/50
15/15 [==============================] - 10s 683ms/step - loss: 1.0968 - accuracy: 0.3533 - val_loss: 1.0811 - val_accuracy: 0.3333
Epoch 2/50
15/15 [==============================] - 10s 640ms/step - loss: 1.0427 - accuracy: 0.4533 - val_loss: 1.0488 - val_accuracy: 0.3833
Epoch 3/50
15/15 [==============================] - 10s 647ms/step - loss: 1.0356 - accuracy: 0.4800 - val_loss: 0.9791 - val_accuracy: 0.5000
Epoch 4/50
15/15 [==============================] - 9s 620ms/step - loss: 0.9480 - accuracy: 0.5233 - val_loss: 0.8958 - val_accuracy: 0.5500
Epoch 5/50
15/15 [==============================] - 10s 640ms/step - loss: 0.9019 - accuracy: 0.6133 - val_loss: 0.8577 - val_accuracy: 0.6083
Epoch 6/50
15/15 [==============================] - 10s 640ms/step - loss: 0.8598 - accuracy: 0.5867 - val_loss: 0.8270 - val_accuracy: 0.5583
Epoch 7/50
15/15 [==============================] - 10s 647ms/step - loss: 0.8303 - accuracy: 0.6167 - val_loss: 0.8271 - val_accuracy: 0.6000
Epoch 8/50
15/15 [==============================] - 9s 633ms/step - loss: 0.7845 - accuracy: 0.6367 - val_loss: 0.8109 - val_accuracy: 0.6083
Epoch 9/50
15/15 [==============================] - 10s 640ms/step - loss: 0.7580 - accuracy: 0.6733 - val_loss: 0.8501 - val_accuracy: 0.6000
Epoch 10/50
15/15 [==============================] - 10s 640ms/step - loss: 0.7506 - accuracy: 0.6767 - val_loss: 0.7739 - val_accuracy: 0.6333
Epoch 11/50
15/15 [==============================] - 10s 647ms/step - loss: 0.7595 - accuracy: 0.6533 - val_loss: 0.7758 - val_accuracy: 0.6667
Epoch 12/50
15/15 [==============================] - 9s 633ms/step - loss: 0.7210 - accuracy: 0.6833 - val_loss: 0.7325 - val_accuracy: 0.6167
Epoch 13/50
15/15 [==============================] - 9s 627ms/step - loss: 0.6804 - accuracy: 0.7000 - val_loss: 0.7436 - val_accuracy: 0.6833
Epoch 14/50
15/15 [==============================] - 10s 635ms/step - loss: 0.6689 - accuracy: 0.7067 - val_loss: 0.7282 - val_accuracy: 0.6500
Epoch 15/50
15/15 [==============================] - 10s 633ms/step - loss: 0.6702 - accuracy: 0.7267 - val_loss: 0.8489 - val_accuracy: 0.6583
Epoch 16/50
15/15 [==============================] - 10s 640ms/step - loss: 0.6452 - accuracy: 0.7267 - val_loss: 0.7062 - val_accuracy: 0.6750
Epoch 17/50
15/15 [==============================] - 9s 633ms/step - loss: 0.6432 - accuracy: 0.7333 - val_loss: 0.7663 - val_accuracy: 0.6583
Epoch 18/50
15/15 [==============================] - 10s 640ms/step - loss: 0.6475 - accuracy: 0.7233 - val_loss: 0.7588 - val_accuracy: 0.6667
Epoch 19/50
15/15 [==============================] - 9s 627ms/step - loss: 0.6550 - accuracy: 0.7167 - val_loss: 0.7067 - val_accuracy: 0.6917
Epoch 20/50
15/15 [==============================] - 10s 640ms/step - loss: 0.6361 - accuracy: 0.7200 - val_loss: 0.8760 - val_accuracy: 0.5833
Epoch 21/50
15/15 [==============================] - 10s 640ms/step - loss: 0.6357 - accuracy: 0.7167 - val_loss: 0.7297 - val_accuracy: 0.6167
Epoch 22/50
15/15 [==============================] - 10s 640ms/step - loss: 0.6147 - accuracy: 0.7300 - val_loss: 0.7332 - val_accuracy: 0.6750
Epoch 23/50
15/15 [==============================] - 10s 647ms/step - loss: 0.6128 - accuracy: 0.7100 - val_loss: 0.7305 - val_accuracy: 0.7083
Epoch 24/50
15/15 [==============================] - 10s 653ms/step - loss: 0.6070 - accuracy: 0.7367 - val_loss: 0.7216 - val_accuracy: 0.6583
Epoch 25/50
15/15 [==============================] - 10s 640ms/step - loss: 0.5791 - accuracy: 0.7533 - val_loss: 0.7276 - val_accuracy: 0.6583
Epoch 26/50
15/15 [==============================] - 9s 627ms/step - loss: 0.5890 - accuracy: 0.7600 - val_loss: 0.7410 - val_accuracy: 0.6917
Epoch 27/50
15/15 [==============================] - 10s 647ms/step - loss: 0.5896 - accuracy: 0.7633 - val_loss: 0.7583 - val_accuracy: 0.6750
Epoch 28/50
15/15 [==============================] - 10s 640ms/step - loss: 0.5600 - accuracy: 0.7533 - val_loss: 0.8289 - val_accuracy: 0.6667
Epoch 29/50
15/15 [==============================] - 10s 640ms/step - loss: 0.6011 - accuracy: 0.7333 - val_loss: 0.7229 - val_accuracy: 0.6667
Epoch 30/50
15/15 [==============================] - 9s 627ms/step - loss: 0.5855 - accuracy: 0.7567 - val_loss: 0.7968 - val_accuracy: 0.6750
Epoch 31/50
15/15 [==============================] - 9s 620ms/step - loss: 0.5367 - accuracy: 0.7600 - val_loss: 0.7319 - val_accuracy: 0.7333
Epoch 32/50
15/15 [==============================] - 10s 634ms/step - loss: 0.5272 - accuracy: 0.8033 - val_loss: 0.7015 - val_accuracy: 0.6750
Epoch 33/50
15/15 [==============================] - 10s 640ms/step - loss: 0.5515 - accuracy: 0.7700 - val_loss: 0.8843 - val_accuracy: 0.6833
Epoch 34/50
15/15 [==============================] - 9s 633ms/step - loss: 0.5971 - accuracy: 0.7800 - val_loss: 0.7874 - val_accuracy: 0.6583
Epoch 35/50
15/15 [==============================] - 9s 633ms/step - loss: 0.5937 - accuracy: 0.7433 - val_loss: 0.7862 - val_accuracy: 0.6333
Epoch 36/50
15/15 [==============================] - 10s 640ms/step - loss: 0.5795 - accuracy: 0.7367 - val_loss: 0.6989 - val_accuracy: 0.7500
Epoch 37/50
15/15 [==============================] - 9s 633ms/step - loss: 0.5429 - accuracy: 0.7600 - val_loss: 0.7536 - val_accuracy: 0.6667
Epoch 38/50
15/15 [==============================] - 9s 613ms/step - loss: 0.5432 - accuracy: 0.7800 - val_loss: 0.7538 - val_accuracy: 0.6583
Epoch 39/50
15/15 [==============================] - 9s 620ms/step - loss: 0.5081 - accuracy: 0.7733 - val_loss: 0.7646 - val_accuracy: 0.6750
Epoch 40/50
15/15 [==============================] - 9s 620ms/step - loss: 0.5133 - accuracy: 0.7967 - val_loss: 0.7551 - val_accuracy: 0.6750
Epoch 41/50
15/15 [==============================] - 9s 633ms/step - loss: 0.5090 - accuracy: 0.7800 - val_loss: 0.7253 - val_accuracy: 0.7000
Epoch 42/50
15/15 [==============================] - 10s 640ms/step - loss: 0.5023 - accuracy: 0.7900 - val_loss: 0.7858 - val_accuracy: 0.6500
Epoch 43/50
15/15 [==============================] - 10s 647ms/step - loss: 0.5031 - accuracy: 0.7533 - val_loss: 0.7887 - val_accuracy: 0.6833
Epoch 44/50
15/15 [==============================] - 9s 613ms/step - loss: 0.5097 - accuracy: 0.7800 - val_loss: 0.7871 - val_accuracy: 0.6667
Epoch 45/50
15/15 [==============================] - 9s 613ms/step - loss: 0.5085 - accuracy: 0.8033 - val_loss: 0.7420 - val_accuracy: 0.6667
Epoch 46/50
15/15 [==============================] - 9s 627ms/step - loss: 0.5086 - accuracy: 0.7800 - val_loss: 0.7440 - val_accuracy: 0.7167
Epoch 47/50
15/15 [==============================] - 9s 633ms/step - loss: 0.4802 - accuracy: 0.8000 - val_loss: 0.7489 - val_accuracy: 0.6917
Epoch 48/50
15/15 [==============================] - 9s 620ms/step - loss: 0.4746 - accuracy: 0.8000 - val_loss: 0.7661 - val_accuracy: 0.6833
Epoch 49/50
15/15 [==============================] - 9s 620ms/step - loss: 0.5070 - accuracy: 0.7800 - val_loss: 0.8112 - val_accuracy: 0.6833
Epoch 50/50
15/15 [==============================] - 9s 633ms/step - loss: 0.4593 - accuracy: 0.8067 - val_loss: 0.7202 - val_accuracy: 0.6750

Plot the learning curves

In [31]:
# Run this cell to plot accuracy vs epoch and loss vs epoch

plt.figure(figsize=(15,5))
plt.subplot(121)
try:
    plt.plot(history_augmented.history['accuracy'])
    plt.plot(history_augmented.history['val_accuracy'])
except KeyError:
    try:
        plt.plot(history_augmented.history['acc'])
        plt.plot(history_augmented.history['val_acc'])
    except KeyError:
        plt.plot(history_augmented.history['categorical_accuracy'])
        plt.plot(history_augmented.history['val_categorical_accuracy'])
plt.title('Accuracy vs. epochs')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Training', 'Validation'], loc='lower right')

plt.subplot(122)
plt.plot(history_augmented.history['loss'])
plt.plot(history_augmented.history['val_loss'])
plt.title('Loss vs. epochs')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Training', 'Validation'], loc='upper right')
plt.show() 

Do you see an improvement in the overfitting? This will of course vary based on your particular run and whether you have altered the hyperparameters.

Get predictions using the trained model

In [32]:
# Get model predictions for the first 3 batches of test data

num_batches = 3
seed = 25
test_generator = get_generator(image_gen_aug, test_dir, seed=seed)
predictions = lsun_new_model.predict_generator(test_generator, steps=num_batches)
Found 300 images belonging to 3 classes.
In [33]:
# Run this cell to view randomly selected images and model predictions

# Get images and ground truth labels
test_generator = get_generator(image_gen_aug, test_dir, seed=seed)
batches = []
for i in range(num_batches):
    batches.append(next(test_generator))
    
batch_images = np.vstack([b[0] for b in batches])
batch_labels = np.concatenate([b[1].astype(np.int32) for b in batches])

# Randomly select images from the batch
inx = np.random.choice(predictions.shape[0], 4, replace=False)
print(inx)

fig, axes = plt.subplots(4, 2, figsize=(16, 12))
fig.subplots_adjust(hspace=0.4, wspace=-0.2)

for n, i in enumerate(inx):
    axes[n, 0].imshow(batch_images[i])
    axes[n, 0].get_xaxis().set_visible(False)
    axes[n, 0].get_yaxis().set_visible(False)
    axes[n, 0].text(30., -3.5, lsun_classes[np.where(batch_labels[i] == 1.)[0][0]], 
                    horizontalalignment='center')
    axes[n, 1].bar(np.arange(len(predictions[i])), predictions[i])
    axes[n, 1].set_xticks(np.arange(len(predictions[i])))
    axes[n, 1].set_xticklabels(lsun_classes)
    axes[n, 1].set_title(f"Categorical distribution. Model prediction: {lsun_classes[np.argmax(predictions[i])]}")
    
plt.show()
Found 300 images belonging to 3 classes.
[26 14 27 55]

Congratulations! This completes the first part of the programming assignment using the tf.keras image data processing tools.

Part 2: tf.data

CIFAR-100 overview image

The CIFAR-100 Dataset

In the second part of this assignment, you will use the CIFAR-100 dataset. This image dataset has 100 classes with 500 training images and 100 test images per class.

  • A. Krizhevsky. "Learning Multiple Layers of Features from Tiny Images". April 2009

Your goal is to use the tf.data module preprocessing tools to construct a data ingestion pipeline including filtering and function mapping over the dataset to train a neural network to classify the images.

Load the dataset

In [107]:
# Load the data, along with the labels

(train_data, train_labels), (test_data, test_labels) = cifar100.load_data(label_mode='fine')
with open('data/cifar100/cifar100_labels.json', 'r') as j:
    cifar_labels = json.load(j)

Display sample images and labels from the training set

In [108]:
# Display a few images and labels

plt.figure(figsize=(15,8))
inx = np.random.choice(train_data.shape[0], 32, replace=False)
for n, i in enumerate(inx):
    ax = plt.subplot(4, 8, n+1)
    plt.imshow(train_data[i])
    plt.title(cifar_labels[int(train_labels[i])])
    plt.axis('off')

Create Dataset objects for the train and test images

You should now write a function to create a tf.data.Dataset object for each of the training and test images and labels. This function should take a numpy array of images in the first argument and a numpy array of labels in the second argument, and create a Dataset object.

Your function should then return the Dataset object. Do not batch or shuffle the Dataset (this will be done later).

In [109]:
#### GRADED CELL ####

# Complete the following function. 
# Make sure to not change the function name or arguments.

def create_dataset(data, labels):
    """
    This function takes a numpy array batch of images in the first argument, and
    a corresponding array containing the labels in the second argument.
    The function should then create a tf.data.Dataset object with these inputs
    and outputs, and return it.
    """
    dataset = tf.data.Dataset.from_tensor_slices((data, labels))
    return dataset
In [110]:
# Run the below cell to convert the training and test data and labels into datasets

train_dataset = create_dataset(train_data, train_labels)
test_dataset = create_dataset(test_data, test_labels)
In [111]:
# Check the element_spec of your datasets

print(train_dataset.element_spec)
print(test_dataset.element_spec)
(TensorSpec(shape=(32, 32, 3), dtype=tf.uint8, name=None), TensorSpec(shape=(1,), dtype=tf.int64, name=None))
(TensorSpec(shape=(32, 32, 3), dtype=tf.uint8, name=None), TensorSpec(shape=(1,), dtype=tf.int64, name=None))

Filter the Dataset

Write a function to filter the train and test datasets so that they only generate images that belong to a specified set of classes.

The function should take a Dataset object in the first argument, and a list of integer class indices in the second argument. Inside your function you should define an auxiliary function that you will use with the filter method of the Dataset object. This auxiliary function should take image and label arguments (as in the element_spec) for a single element in the batch, and return a boolean indicating if the label is one of the allowed classes.

Your function should then return the filtered dataset.

Hint: you may need to use the tf.equal, tf.cast and tf.math.reduce_any functions in your auxiliary function.

In [112]:
#### GRADED CELL ####

# Complete the following function. 
# Make sure to not change the function name or arguments.

def filter_classes(dataset, classes):
    """
    This function should filter the dataset by only retaining dataset elements whose
    label belongs to one of the integers in the classes list.
    The function should then return the filtered Dataset object.
    """
    def filter_aux(image, label):
        return tf.math.reduce_any(tf.equal(label, classes))
    
    return dataset.filter(filter_aux)
In [113]:
# Run the below cell to filter the datasets using your function

cifar_classes = [0, 29, 99] # Your datasets should contain only classes in this list

train_dataset = filter_classes(train_dataset, cifar_classes)
test_dataset = filter_classes(test_dataset, cifar_classes)

Apply map functions to the Dataset

You should now write two functions that use the map method to process the images and labels in the filtered dataset.

The first function should one-hot encode the remaining labels so that we can train the network using a categorical cross entropy loss.

The function should take a Dataset object as an argument. Inside your function you should define an auxiliary function that you will use with the map method of the Dataset object. This auxiliary function should take image and label arguments (as in the element_spec) for a single element in the batch, and return a tuple of two elements, with the unmodified image in the first element, and a one-hot vector in the second element. The labels should be encoded according to the following:

  • Class 0 maps to [1., 0., 0.]
  • Class 29 maps to [0., 1., 0.]
  • Class 99 maps to [0., 0., 1.]

Your function should then return the mapped dataset.

In [114]:
#### GRADED CELL ####

# Complete the following function. 
# Make sure to not change the function name or arguments.

def map_labels(dataset):
    """
    This function should map over the dataset to convert the label to a 
    one-hot vector. The encoding should be done according to the above specification.
    The function should then return the mapped Dataset object.
    """
    def map_aux(image, label):
        if label == 0:
            return image, tf.constant([1., 0., 0.])
        elif label == 29:
            return image, tf.constant([0., 1., 0.])
        else:
            return image, tf.constant([0., 0., 1.])
    return dataset.map(map_aux)
In [115]:
# Run the below cell to one-hot encode the training and test labels.

train_dataset = map_labels(train_dataset)
test_dataset = map_labels(test_dataset)

The second function should process the images according to the following specification:

  • Rescale the image pixel values by a factor of 1/255.
  • Convert the colour images (3 channels) to black and white images (single channel) by computing the average pixel value across all channels.

The function should take a Dataset object as an argument. Inside your function you should again define an auxiliary function that you will use with the map method of the Dataset object. This auxiliary function should take image and label arguments (as in the element_spec) for a single element in the batch, and return a tuple of two elements, with the processed image in the first element, and the unmodified label in the second argument.

Your function should then return the mapped dataset.

Hint: you may find it useful to use tf.reduce_mean since the black and white image is the colour-average of the colour images. You can also use the keepdims keyword in tf.reduce_mean to retain the single colour channel.

In [116]:
#### GRADED CELL ####

# Complete the following function. 
# Make sure to not change the function name or arguments.

def map_images(dataset):
    """
    This function should map over the dataset to process the image according to the 
    above specification. The function should then return the mapped Dataset object.
    """
    def map_aux(image, label):
        image = image / 255
        image = tf.reduce_mean(image, axis=2,keepdims=True)
        return image, label
    return dataset.map(map_aux)
    
In [117]:
# Run the below cell to apply your mapping function to the datasets

train_dataset_bw = map_images(train_dataset)
test_dataset_bw = map_images(test_dataset)

Display a batch of processed images

In [118]:
# Run this cell to view a selection of images before and after processing

plt.figure(figsize=(16,5))
plt.suptitle("Unprocessed images", fontsize=16)
for n, elem in enumerate(train_dataset.take(10)):
    images, labels = elem
    ax = plt.subplot(2, 5, n+1)
    plt.title(cifar_labels[cifar_classes[np.where(labels == 1.)[0][0]]])
    plt.imshow(np.squeeze(images), cmap='gray')
    plt.axis('off')
    
plt.figure(figsize=(16,5))
plt.suptitle("Processed images", fontsize=16)
for n, elem in enumerate(train_dataset_bw.take(10)):
    images_bw, labels_bw = elem
    ax = plt.subplot(2, 5, n+1)
    plt.title(cifar_labels[cifar_classes[np.where(labels_bw == 1.)[0][0]]])
    plt.imshow(np.squeeze(images_bw), cmap='gray')
    plt.axis('off')

We will now batch and shuffle the Dataset objects.

In [119]:
# Run the below cell to batch the training dataset and expand the final dimensinos

train_dataset_bw = train_dataset_bw.batch(10)
train_dataset_bw = train_dataset_bw.shuffle(100)

test_dataset_bw = test_dataset_bw.batch(10)
test_dataset_bw = test_dataset_bw.shuffle(100)

Train a neural network model

Now we will train a model using the Dataset objects. We will use the model specification and function from the first part of this assignment, only modifying the size of the input images.

In [120]:
# Build and compile a new model with our original spec, using the new image size
    
cifar_model = get_model((32, 32, 1))
In [121]:
# Train the model for 15 epochs

history = cifar_model.fit(train_dataset_bw, validation_data=test_dataset_bw, epochs=15)
Epoch 1/15
150/150 [==============================] - 20s 131ms/step - loss: 1.0625 - accuracy: 0.3920 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/15
150/150 [==============================] - 18s 122ms/step - loss: 0.8941 - accuracy: 0.6113 - val_loss: 0.8153 - val_accuracy: 0.6600
Epoch 3/15
150/150 [==============================] - 18s 122ms/step - loss: 0.7704 - accuracy: 0.6907 - val_loss: 0.7239 - val_accuracy: 0.7033
Epoch 4/15
150/150 [==============================] - 18s 122ms/step - loss: 0.6863 - accuracy: 0.7327 - val_loss: 0.6563 - val_accuracy: 0.7533
Epoch 5/15
150/150 [==============================] - 19s 125ms/step - loss: 0.6473 - accuracy: 0.7533 - val_loss: 0.6042 - val_accuracy: 0.7767
Epoch 6/15
150/150 [==============================] - 19s 129ms/step - loss: 0.5998 - accuracy: 0.7660 - val_loss: 0.5572 - val_accuracy: 0.7900
Epoch 7/15
150/150 [==============================] - 19s 128ms/step - loss: 0.5595 - accuracy: 0.7920 - val_loss: 0.5429 - val_accuracy: 0.7867
Epoch 8/15
150/150 [==============================] - 19s 130ms/step - loss: 0.5215 - accuracy: 0.8120 - val_loss: 0.5105 - val_accuracy: 0.7867
Epoch 9/15
150/150 [==============================] - 19s 130ms/step - loss: 0.4998 - accuracy: 0.8227 - val_loss: 0.4964 - val_accuracy: 0.7933
Epoch 10/15
150/150 [==============================] - 19s 130ms/step - loss: 0.4716 - accuracy: 0.8280 - val_loss: 0.4989 - val_accuracy: 0.7800
Epoch 11/15
150/150 [==============================] - 20s 130ms/step - loss: 0.4562 - accuracy: 0.8253 - val_loss: 0.4664 - val_accuracy: 0.7967
Epoch 12/15
150/150 [==============================] - 20s 132ms/step - loss: 0.4555 - accuracy: 0.8327 - val_loss: 0.4745 - val_accuracy: 0.8100
Epoch 13/15
150/150 [==============================] - 20s 134ms/step - loss: 0.4430 - accuracy: 0.8393 - val_loss: 0.4639 - val_accuracy: 0.8233
Epoch 14/15
150/150 [==============================] - 20s 132ms/step - loss: 0.4097 - accuracy: 0.8473 - val_loss: 0.4802 - val_accuracy: 0.8033
Epoch 15/15
150/150 [==============================] - 20s 131ms/step - loss: 0.4211 - accuracy: 0.8353 - val_loss: 0.4348 - val_accuracy: 0.8300

Plot the learning curves

In [122]:
# Run this cell to plot accuracy vs epoch and loss vs epoch

plt.figure(figsize=(15,5))
plt.subplot(121)
try:
    plt.plot(history.history['accuracy'])
    plt.plot(history.history['val_accuracy'])
except KeyError:
    try:
        plt.plot(history.history['acc'])
        plt.plot(history.history['val_acc'])
    except KeyError:
        plt.plot(history.history['categorical_accuracy'])
        plt.plot(history.history['val_categorical_accuracy'])
plt.title('Accuracy vs. epochs')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Training', 'Validation'], loc='lower right')

plt.subplot(122)
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Loss vs. epochs')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Training', 'Validation'], loc='upper right')
plt.show() 
In [123]:
# Create an iterable from the batched test dataset

test_dataset = test_dataset.batch(10)
iter_test_dataset = iter(test_dataset)
In [126]:
# Display model predictions for a sample of test images

plt.figure(figsize=(15,8))
inx = np.random.choice(test_data.shape[0], 18, replace=False)
images, labels = next(iter_test_dataset)
probs = cifar_model(tf.reduce_mean(tf.cast(images, tf.float32), axis=-1, keepdims=True) / 255.)
preds = np.argmax(probs, axis=1)
for n in range(10):
    ax = plt.subplot(2, 5, n+1)
    plt.imshow(images[n])
    plt.title(cifar_labels[cifar_classes[np.where(labels[n].numpy() == 1.0)[0][0]]])
    plt.text(0, 35, "Model prediction: {}".format(cifar_labels[cifar_classes[preds[n]]]))
    plt.axis('off')

Congratulations for completing this programming assignment! In the next week of the course we will learn to develop models for sequential data.