This notebook presents ResNet50 architecutre build from scratch and applied to Oxford VGG Flowers 17 dataset.
References
import os
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
with tf.Session(config=config):
pass # init sessin with allow_growth
dataset_location = '../Datasets/vgg-flowers-17/'
datafile = os.path.join(dataset_location, '17flowers.npz')
if not os.path.isfile(datafile):
print('Run StanfordDogs.ipynb notebook first!')
raise
npzfile = np.load(datafile)
train_images_raw = npzfile['train_images']
train_labels_raw = npzfile['train_labels']
valid_images_raw = npzfile['valid_images']
valid_labels_raw = npzfile['valid_labels']
train_images = tf.keras.applications.resnet50.preprocess_input(train_images_raw.astype(np.float32))
valid_images = tf.keras.applications.resnet50.preprocess_input(valid_images_raw.astype(np.float32))
print('train_images.shape:', train_images.shape)
print('valid_images.shape:', valid_images.shape)
print('valid_images:\n', valid_images[0,:,:,0].round()) # first image, red channel
train_labels = tf.keras.utils.to_categorical(train_labels_raw)
valid_labels = tf.keras.utils.to_categorical(valid_labels_raw)
print('train_labels.shape:', train_labels.shape)
print('valid_labels.shape:', valid_labels.shape)
print('valid_labels:\n', valid_labels)
from tensorflow.keras.layers import Input
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPooling2D, Add
from tensorflow.keras.layers import GlobalAveragePooling2D, Flatten, Dense
def residual_block(X_start, filters, name, reduce=False, res_conv2d=False):
"""
Residual building block used by ResNet-50
"""
nb_filters_1, nb_filters_2, nb_filters_3 = filters
strides_1 = [2,2] if reduce else [1,1]
X = Conv2D(filters=nb_filters_1, kernel_size=[1,1], strides=strides_1, padding='same', name=name)(X_start)
X = BatchNormalization()(X) # default axis-1 is ok
X = Activation('relu')(X)
X = Conv2D(filters=nb_filters_2, kernel_size=[3,3], strides=[1,1], padding='same')(X)
X = BatchNormalization()(X)
X = Activation('relu')(X)
X = Conv2D(filters=nb_filters_3, kernel_size=[1,1], strides=[1,1], padding='same')(X)
X = BatchNormalization()(X)
if res_conv2d:
X_res = Conv2D(filters=nb_filters_3, kernel_size=[1,1], strides=strides_1, padding='same')(X_start)
X_res = BatchNormalization()(X_res)
else:
X_res = X_start
X = Add()([X, X_res])
X = Activation('relu')(X)
return X
def resnet50(input_shape, nb_classes):
assert len(input_shape) == 3
X_input = Input(shape=input_shape)
# conv1
X = Conv2D(filters=64, kernel_size=[7,7], strides=[2,2], padding='same', name='conv1')(X_input)
X = BatchNormalization(name='bn_conv1')(X)
X = Activation('relu')(X)
X = MaxPooling2D([3,3], strides=[2,2])(X)
# conv2_x
X = residual_block(X, filters=[64, 64, 256], name='conv2_a', reduce=False, res_conv2d=True)
X = residual_block(X, filters=[64, 64, 256], name='conv2_b')
X = residual_block(X, filters=[64, 64, 256], name='conv2_c')
# conv3_x
X = residual_block(X, filters=[128, 128, 512], name='conv3_a', reduce=True, res_conv2d=True)
X = residual_block(X, filters=[128, 128, 512], name='conv3_b')
X = residual_block(X, filters=[128, 128, 512], name='conv3_c')
X = residual_block(X, filters=[128, 128, 512], name='conv3_d')
# conv4_x
X = residual_block(X, filters=[256, 256, 1024], name='conv4_a', reduce=True, res_conv2d=True)
X = residual_block(X, filters=[256, 256, 1024], name='conv4_b')
X = residual_block(X, filters=[256, 256, 1024], name='conv4_c')
X = residual_block(X, filters=[256, 256, 1024], name='conv4_d')
X = residual_block(X, filters=[256, 256, 1024], name='conv4_e')
X = residual_block(X, filters=[256, 256, 1024], name='conv4_f')
# conv5_x
X = residual_block(X, filters=[512, 512, 2048], name='conv5_a', reduce=True, res_conv2d=True)
X = residual_block(X, filters=[512, 512, 2048], name='conv5_b')
X = residual_block(X, filters=[512, 512, 2048], name='conv5_c')
X = GlobalAveragePooling2D(name='avg_pool')(X)
X = Flatten()(X)
X = Dense(units=nb_classes, activation='softmax')(X)
model = tf.keras.models.Model(inputs=X_input, outputs=X)
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0001),
loss='categorical_crossentropy', metrics=['accuracy'])
return model
model = resnet50(input_shape=[224, 224, 3], nb_classes=17)
# model.summary() # this should be identical to tf.keras.applications.ResNet50()
hist = model.fit(x=train_images, y=train_labels, batch_size=34, epochs=30,
validation_data=(valid_images, valid_labels), verbose=2)
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=[16,6])
ax1.plot(hist.history['loss'], label='train_loss')
ax1.plot(hist.history['val_loss'], label='valid_loss')
ax1.legend()
ax2.plot(hist.history['acc'], label='train_acc')
ax2.plot(hist.history['val_acc'], label='valid_acc')
ax2.legend();