In this exercise, you will build a class that implements a VGG network that can be trained to classify images. The model will look something like this:
It is primarily made up of a series of Conv2D layers followed by a softmax activated layers to classify the image. As you can see, this will be a handful and the code will look huge if you specify each layer individually. As shown in the lectures, you can instead use model subclassing to build complex architectures. You can encapsulate repeating parts of a network then reuse that code when building the final model. You will get to practice that in this exercise. Let’s get started!
import tensorflow as tf
import tensorflow_datasets as tfds
import utils
In this assignment, you will see the use of the Python function vars()
. This will allow you to use a for loop to define and set multiple variables with a similar name, such as var1, var2, var3.
Please go through the following examples to get familiar with vars()
, as you will use it when building the VGG model.
MyClass
var1
.MyClass
.# Define a small class MyClass
class MyClass:
def __init__(self):
# One class variable 'a' is set to 1
self.var1 = 1
# Create an object of type MyClass()
my_obj = MyClass()
Python classes have an attribute called __dict__
.
__dict__
is a Python dictionary that contains the object’s instance variables and values as key value pairs.my_obj.__dict__
{'var1': 1}
If you call vars()
and pass in an object, it will call the object’s __dict__
attribute, which is a Python dictionary containing the object’s instance variables and their values as ke
vars(my_obj)
{'var1': 1}
You may be familiar with adding new variable like this:
# Add a new instance variable and give it a value
my_obj.var2 = 2
# Calls vars() again to see the object's instance variables
vars(my_obj)
{'var1': 1, 'var2': 2}
Here is another way that you can add an instance variable to an object, using vars()
.
__dict__
of the object using vars(my_obj).__dict__
dictionary using square bracket notation and passing in the variable’s name as a string: ['var3'] = 3
# Call vars, passing in the object. Then access the __dict__ dictionary using square brackets
vars(my_obj)['var3'] = 3
# Call vars() to see the object's instance variables
vars(my_obj)
{'var1': 1, 'var2': 2, 'var3': 3}
You may be wondering why you would need another way to access an object’s instance variables.
vars()
, you can now pass in the name of the variable var3
as a string.var4
, var5
… var9
) and wanted a convenient way to access them by incrementing a number?Try this!
# Use a for loop to increment the index 'i'
for i in range(4,10):
# Format a string that is var
vars(my_obj)[f'var{i}'] = 0
# View the object's instance variables!
vars(my_obj)
{'var1': 1,
'var2': 2,
'var3': 3,
'var4': 0,
'var5': 0,
'var6': 0,
'var7': 0,
'var8': 0,
'var9': 0}
There are couple equivalent ways in Python to format a string. Here are two of those ways:
# Format a string using f-string notation
i=1
print(f"var{i}")
# Format a string using .format notation
i=2
print("var{}".format(i))
var1
var2
You can access the variables of a class inside the class definition using vars(self)
# Define a small class MyClass
class MyClass:
def __init__(self):
# Use vars(self) to access the class's dictionary of variables
vars(self)['var1'] = 1
# Create an object of type MyClass()
my_obj = MyClass()
vars(my_obj)
{'var1': 1}
You’ll see this in the upcoming code. Now you’ll start building the VGG network!
The VGG Network has blocks of layers, where each block has a varied number of layers.
Block
, which can generate a customizable block of layers__init__
In the constructor __init__
, store the conv2D parameters and also define the number of conv2D layers using the parameters passed into __init__
.
Store the filters, kernel_size, and repetitions as class variables so that they can be used later in the call
function.
Using a for loop, define a number of Conv2D Conv2D layers, based on the number of repetitions
desired for this block.
vars
and string formatting to create conv2D_0, conv2D_1, conv2D_3 etc.Define the MaxPool2D layer that follows these Conv2D layers.
call
In call
, you will connect the layers together.
conv2D_0
, immediately follows the inputs
.# Please uncomment all lines in this cell and replace those marked with `# YOUR CODE HERE`.
# You can select all lines in this code cell with Ctrl+A (Windows/Linux) or Cmd+A (Mac), then press Ctrl+/ (Windows/Linux) or Cmd+/ (Mac) to uncomment.
class Block(tf.keras.Model):
def __init__(self, filters, kernel_size, repetitions, pool_size=2, strides=2):
super(Block, self).__init__()
self.filters = filters
self.kernel_size = kernel_size
self.repetitions = repetitions
self.pool_size = pool_size
self.strides = strides
# Define a conv2D_0, conv2D_1, etc based on the number of repetitions
for i in range(repetitions):
# Define a Conv2D layer, specifying filters, kernel_size, activation and padding.
vars(self)[f'conv2D_{i}'] = tf.keras.layers.Conv2D(
filters=self.filters,
kernel_size=self.kernel_size,
activation="relu",
padding="same"
)
# Define the max pool layer that will be added after the Conv2D blocks
self.max_pool = tf.keras.layers.MaxPool2D(
pool_size=(self.pool_size, self.pool_size),
strides=(self.strides, self.strides)
)
def call(self, inputs):
# access the class's conv2D_0 layer
conv2D_0 = vars(self)["conv2D_0"]
# Connect the conv2D_0 layer to inputs
x = conv2D_0(inputs)
# for the remaining conv2D_i layers from 1 to `repetitions` they will be connected to the previous layer
for i in range(1, self.repetitions):
# access conv2D_i by formatting the integer `i`. (hint: check how these were saved using `vars()` earlier)
conv2D_i = vars(self)[f"conv2D_{i}"]
# Use the conv2D_i and connect it to the previous layer
x = conv2D_i(x)
# Finally, add the max_pool layer
max_pool = self.max_pool(x)
return max_pool
utils.test_block_class(Block)
[92m All public tests passed
This model stack has a series of VGG blocks, which can be created using the Block
class that you defined earlier.
__init__
__init__
constructor of Block
takes several function parameters,
After block ‘e’, add the following layers:
'relu'
activation.'softmax'
activation.call
Connect these layers together using the functional API syntax:
Return the classifier layer.
# Please uncomment all lines in this cell and replace those marked with `# YOUR CODE HERE`.
# You can select all lines in this code cell with Ctrl+A (Windows/Linux) or Cmd+A (Mac), then press Ctrl+/ (Windows/Linux) or Cmd+/ (Mac) to uncomment.
class MyVGG(tf.keras.Model):
def __init__(self, num_classes):
super(MyVGG, self).__init__()
self.num_classes = num_classes
# Creating blocks of VGG with the following
# (filters, kernel_size, repetitions) configurations
self.block_a = Block(filters=64, kernel_size=3, repetitions=2)
self.block_b = Block(filters=128, kernel_size=3, repetitions=2)
self.block_c = Block(filters=256, kernel_size=3, repetitions=3)
self.block_d = Block(filters=512, kernel_size=3, repetitions=3)
self.block_e = Block(filters=512, kernel_size=3, repetitions=3)
# Classification head
# Define a Flatten layer
self.flatten = tf.keras.layers.Flatten()
# Create a Dense layer with 256 units and ReLU as the activation function
self.fc = tf.keras.layers.Dense(256, activation="relu")
# Finally add the softmax classifier using a Dense layer
self.classifier = tf.keras.layers.Dense(self.num_classes, activation="softmax")
def call(self, inputs):
# Chain all the layers one after the other
x = self.block_a(inputs)
x = self.block_b(x)
x = self.block_c(x)
x = self.block_d(x)
x = self.block_e(x)
x = self.flatten(x)
x = self.fc(x)
x = self.classifier(x)
return x
utils.test_myvgg_class(MyVGG, Block)
[92m All public tests passed
If you passed all tests above, then you’ve successfully built the model for your image classifier. Congratulations! You can submit your work now before proceeding.
The next steps in the pipeline will be loading the dataset and training your VGG network. The code is shown below but it is only for reference and is not required to complete the assignment. Please do not uncomment it because it will cause a grader timeout because of the slow training time. The grader environment does not have an accelerator enabled.
If you want to train with your VGG network, one way is to download your notebook (File -> Download As -> Notebook
), then upload to Colab. From there, you can use a GPU runtime (Runtime -> Change Runtime type
) prior to running the cells. Just make sure to comment out the imports and calls to utils.py
so you don’t get File Not Found
errors. Again, this part is only for reference and is not required for grading. For this lab, we will only grade how you built your model using subclassing. You will get to training and evaluating your models in the next courses of this Specialization.
# # For reference only. Please do not uncomment in Coursera Labs because it might cause the grader to time out.
# # You can upload your notebook to Colab instead if you want to try the code below.
# # Download the dataset
# dataset = tfds.load('cats_vs_dogs', split=tfds.Split.TRAIN, data_dir='data/')
# # Initialize VGG with the number of classes
# vgg = MyVGG(num_classes=2)
# # Compile with losses and metrics
# vgg.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# # Define preprocessing function
# def preprocess(features):
# # Resize and normalize
# image = tf.image.resize(features['image'], (224, 224))
# return tf.cast(image, tf.float32) / 255., features['label']
# # Apply transformations to dataset
# dataset = dataset.map(preprocess).batch(32)
# # Train the custom VGG model
# vgg.fit(dataset, epochs=10)