We start by importing the required libraries.
import tensorflow as tf
import numpy as np
import scipy.misc
from tensorflow.keras.applications.resnet_v2 import ResNet50V2
from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications.resnet_v2 import preprocess_input, decode_predictions
from tensorflow.keras import layers
from tensorflow.keras.layers import Input, Add, Dense, Activation, ZeroPadding2D, Flatten, Conv2D, AveragePooling2D, MaxPooling2D, GlobalMaxPooling2D
from tensorflow.keras.models import Model, load_model
from resnets_utils import *
from tensorflow.keras.initializers import random_uniform, glorot_uniform, constant, identity
from tensorflow.python.framework.ops import EagerTensor
from matplotlib.pyplot import imshow
%matplotlib.inline
np.random.seed(1)
tf.random.set_seed(2)The problem of Very Deep Neural Networks
Neural networks in the recent recent years have become much deeper. The main benefit of very deep networks is that it can represent very complex functions. It can also learn features at many different levels of abstraction.
However, using a deeper network does not always help. A huge barrier to training them is the problem of vanishing and exploding gradients.
To solve this problem we use a Residual Network. in ResNets a skip connection allows the model to skip layers. This ‘skip connection’ makes it very easy for one of the blocks to learn an identity function.
Identity block
The identity block is the standard block used in ResNets and corresponds to the case where the input activation (say ) has the same dimension as the output activation (say ). These are the individual steps for a skip connection that “skips” over 2 layers :
- The first
CONV2Dhas filters of shape (1, 1) and a stride of (1, 1). - The first
BatchNormis normalizing the ‘channels’ axis. - Then apply the
ReLUactivation function. This has no hyper-parameters. - The second
CONV2Dhas filters of shape (1, 1) and a stride of (1, 1). - The second
BatchNormis normalizing the ‘channels’ axis. - Here the
ReLUfunction is not applied first. TheX_shortcutand the output of the 2nd layerXare added together. - Then we apply the
ReLUfunction.
How we will implement a identity block for a skip connection that “skip over” 3 hidden layers rather than 2 layers.
def identity_block(X, f, filters, initializer=random_uniform):
"""
Arguments:
X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev)
f -- integer, specifying the shape of the middle CONV's window for the main path
filters -- python list of integers, defining the number of filters in the CONV layers of the main path
initializer -- to set up the initial weights of a layer. Equals to random uniform initializer
Returns:
X -- output of the identity block, tensor of shape (m, n_H, n_W, n_C)
"""
F1, F2, F3 = filters
X_shortcut = X
X = Conv2D(filters = F1,
kernel_size = 1,
strides = (1,1),
padding = 'valid',
kernel_initializer = initializer(seed=0))(X)
X = BatchNormalization(axis = 3)(X)
X = Activation('relu')(X)
X = Conv2D(filters=F2,
kernel_size=(f, f),
strides=(1,1),
padding='same',
kernel_initializer=initializer(seed=0))(X)
X = BatchNormalization(axis=3)(X)
X = Activation('relu')(X)
X = Conv2D(filters=F3,
kernel_size=(1,1),
strides=(1,1),
padding='valid',
kernel_initializer=initializer(seed=0))(X)
X = BatchNormalization(axis=3)(X)
X = Add()([X, X_shortcut])
X = Activation('relu')(X)
return XConvolution block
The ResNet “convolutional block” is the second block type. We use this block when the input and output dimensions do not match up. The difference with the identity block is that there is a CONV2D layer in the shortcut path. Here is how we can implement it :
def convolutional_block(X, f, filters, s = 2, initializer=glorot_uniform):
"""
Arguments:
X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev)
f -- integer, specifying the shape of the middle CONV's window for the main path
filters -- python list of integers, defining the number of filters in the CONV layers of the main path
s -- Integer, specifying the stride to be used
initializer -- to set up the initial weights of a layer. Equals to Glorot uniform initializer,
also called Xavier uniform initializer.
Returns:
X -- output of the convolutional block, tensor of shape (m, n_H, n_W, n_C)
"""
F1, F2, F3 = filters
X_shortcut = X
X = Conv2D(filters = F1,
kernel_size = 1,
strides = (s, s),
padding='valid',
kernel_initializer = initializer(seed=0))(X)
X = BatchNormalization(axis = 3)(X)
X = Activation('relu')(X)
X = Conv2D(filters = F2,
kernel_size = (f, f),
strides = (1, 1),
padding='same',
kernel_initializer = initializer(seed=0))(X)
X = BatchNormalization(axis = 3)(X)
X = Activation('relu')(X)
X = Conv2D(filters = F3,
kernel_size = 1,
strides = (1, 1),
padding='valid',
kernel_initializer = initializer(seed=0))(X)
X = BatchNormalization(axis = 3)(X)
X_shortcut = Conv2D(filters = F3,
kernel_size = 1,
strides = (s, s),
padding='valid',
kernel_initializer = initializer(seed=0))(X_shortcut)
X_shortcut = BatchNormalization(axis = 3)(X_shortcut)
X = Add()([X, X_shortcut])
X = Activation('relu')(X)
return XBuilding a ResNet Model
We now have the necessary blocks to build a very deep ResNet. The following figure describes in detail the architecture of this neural network. “ID BLOCK” in the diagram stands for “Identity block,” and “ID BLOCK x3” means you should stack 3 identity blocks together.

def ResNet50(input_shape = (64, 64, 3), classes = 6, training=False):
"""
Stage-wise implementation of the architecture of the popular ResNet50:
CONV2D -> BATCHNORM -> RELU -> MAXPOOL -> CONVBLOCK -> IDBLOCK*2 -> CONVBLOCK -> IDBLOCK*3
-> CONVBLOCK -> IDBLOCK*5 -> CONVBLOCK -> IDBLOCK*2 -> AVGPOOL -> FLATTEN -> DENSE
Arguments:
input_shape -- shape of the images of the dataset
classes -- integer, number of classes
Returns:
model -- a Model() instance in Keras
"""
X_input = Input(input_shape)
X = ZeroPadding2D((3, 3))(X_input)
X = Conv2D(64, (7, 7),
strides = (2, 2),
kernel_initializer = glorot_uniform(seed=0))(X)
X = BatchNormalization(axis = 3)(X)
X = Activation('relu')(X)
X = MaxPooling2D((3, 3), strides=(2, 2))(X)
X = convolutional_block(X, f = 3, filters = [64, 64, 256], s = 1)
X = identity_block(X, 3, [64, 64, 256])
X = identity_block(X, 3, [64, 64, 256])
X = convolutional_block(X, f = 3, filters = [128,128,512], s = 2)
X = identity_block(X, 3, [128,128,512])
X = identity_block(X, 3, [128,128,512])
X = identity_block(X, 3, [128,128,512])
X = convolutional_block(X, f = 3, filters = [256, 256, 1024], s = 2)
X = identity_block(X, 3, [256, 256, 1024])
X = identity_block(X, 3, [256, 256, 1024])
X = identity_block(X, 3, [256, 256, 1024])
X = identity_block(X, 3, [256, 256, 1024])
X = identity_block(X, 3, [256, 256, 1024])
X = convolutional_block(X, f = 3, filters = [512, 512, 2048], s = 2)
X = identity_block(X, 3, [512, 512, 2048])
X = identity_block(X, 3, [512, 512, 2048])
X = AveragePooling2D((2, 2), strides=(1, 1))(X)
X = Flatten()(X)
X = Dense(classes,
activation='softmax',
kernel_initializer = glorot_uniform(seed=0))(X)
model = Model(inputs = X_input, outputs = X)
return modelNow we will compile the model.
np.random.seed(1)
tf.random.set_seed(2)
opt = tf.keras.optimizers.Adam(learning_rate=0.00015)
model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])The model is now ready to be trained. The only thing we need now is a dataset. We train the model on the SIGNS dataset.

X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()
X_train = X_train_orig / 255.
X_test = X_test_orig / 255.
Y_train = convert_to_one_hot(Y_train_orig, 6).T
Y_test = convert_to_one_hot(Y_test_orig, 6).T
print ("number of training examples = " + str(X_train.shape[0]))
print ("number of test examples = " + str(X_test.shape[0]))
print ("X_train shape: " + str(X_train.shape))
print ("Y_train shape: " + str(Y_train.shape))
print ("X_test shape: " + str(X_test.shape))
print ("Y_test shape: " + str(Y_test.shape))Output :
number of training examples = 1080
number of test examples = 120
X_train shape: (1080, 64, 64, 3)
Y_train shape: (1080, 6)
X_test shape: (120, 64, 64, 3)
Y_test shape: (120, 6)
We trained the model as follows.
model.fit(X_train, Y_train, epochs = 10, batch_size = 32)Output :
Epoch 1/10
34/34 [==============================] - 11s 43ms/step - loss: 1.8407 - accuracy: 0.3241
Epoch 2/10
34/34 [==============================] - 1s 43ms/step - loss: 1.1585 - accuracy: 0.5481
Epoch 3/10
34/34 [==============================] - 1s 42ms/step - loss: 0.8954 - accuracy: 0.6648
Epoch 4/10
34/34 [==============================] - 1s 41ms/step - loss: 0.6625 - accuracy: 0.7593
Epoch 5/10
34/34 [==============================] - 1s 41ms/step - loss: 0.4561 - accuracy: 0.8398
Epoch 6/10
34/34 [==============================] - 1s 42ms/step - loss: 0.2727 - accuracy: 0.9028
Epoch 7/10
34/34 [==============================] - 1s 41ms/step - loss: 0.2396 - accuracy: 0.9130
Epoch 8/10
34/34 [==============================] - 1s 41ms/step - loss: 0.2236 - accuracy: 0.9176
Epoch 9/10
34/34 [==============================] - 1s 40ms/step - loss: 0.2433 - accuracy: 0.9157
Epoch 10/10
34/34 [==============================] - 1s 40ms/step - loss: 0.2135 - accuracy: 0.938
preds = model.evaluate(X_test, Y_test)
print ("Loss = " + str(preds[0]))
print ("Test Accuracy = " + str(preds[1]))Output :
4/4 [==============================] - 1s 10ms/step - loss: 0.5922 - accuracy: 0.8000
Loss = 0.5922172665596008
Test Accuracy = 0.800000011920929
We get a test accuracy of 80 %. When train the complete ResNet50 on a GPU we achieve test accuracy to as close as 94 %.