From feff4f405bcd0b5a91b3427f25a5da6d70eae8a0 Mon Sep 17 00:00:00 2001 From: Tiago Freitas Pereira <tiagofrepereira@gmail.com> Date: Thu, 22 Apr 2021 17:07:30 +0200 Subject: [PATCH] Properly implemented resnet50 and resnet101 --- bob/learn/tensorflow/models/__init__.py | 2 + .../tensorflow/models/resnet50_modified.py | 352 ++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 bob/learn/tensorflow/models/resnet50_modified.py diff --git a/bob/learn/tensorflow/models/__init__.py b/bob/learn/tensorflow/models/__init__.py index 4265c99d..a69718e5 100644 --- a/bob/learn/tensorflow/models/__init__.py +++ b/bob/learn/tensorflow/models/__init__.py @@ -8,6 +8,8 @@ from .densenet import densenet161 # noqa: F401 from .embedding_validation import EmbeddingValidation from .mine import MineModel +from .arcface import ArcFaceLayer, ArcFaceLayer3Penalties, ArcFaceModel +from .resnet50_modified import resnet50_modified, resnet101_modified # gets sphinx autodoc done right - don't remove it def __appropriate__(*args): diff --git a/bob/learn/tensorflow/models/resnet50_modified.py b/bob/learn/tensorflow/models/resnet50_modified.py new file mode 100644 index 00000000..bd2d4bef --- /dev/null +++ b/bob/learn/tensorflow/models/resnet50_modified.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- +""" +The resnet50 from `tf.keras.applications.Resnet50` has a problem with the convolutional layers. +It basically add bias terms to such layers followed by batch normalizations, which is not correct + +https://github.com/tensorflow/tensorflow/issues/37365 + +This resnet 50 implementation provides a cleaner version +""" + +import tensorflow as tf + +from tensorflow.keras import layers +from tensorflow.keras.regularizers import l2 +from tensorflow.keras.layers import Input, Conv2D, Activation, BatchNormalization +from tensorflow.keras.layers import MaxPooling2D, AveragePooling2D, Flatten, Dense + +global weight_decay +weight_decay = 1e-4 + + +class IdentityBlock(tf.keras.layers.Layer): + def __init__( + self, kernel_size, filters, stage, block, weight_decay=1e-4, name=None, **kwargs + ): + + """Block that has no convolutianal layer as skip connection + + Parameters + ---------- + kernel_size: + The kernel size of middle conv layer at main path + + filters: + list of integers, the filterss of 3 conv layer at main path + stage: + Current stage label, used for generating layer names + + block: + 'a','b'..., current block label, used for generating layer names + + """ + super().__init__(name=name, **kwargs) + + filters1, filters2, filters3 = filters + bn_axis = 3 + + conv_name_1 = "conv" + str(stage) + "_" + str(block) + "_1x1_reduce" + bn_name_1 = "conv" + str(stage) + "_" + str(block) + "_1x1_reduce/bn" + layers = [ + Conv2D( + filters1, + (1, 1), + kernel_initializer="orthogonal", + use_bias=False, + kernel_regularizer=l2(weight_decay), + name=conv_name_1, + ) + ] + + layers += [BatchNormalization(axis=bn_axis, name=bn_name_1)] + layers += [Activation("relu")] + + conv_name_2 = "conv" + str(stage) + "_" + str(block) + "_3x3" + bn_name_2 = "conv" + str(stage) + "_" + str(block) + "_3x3/bn" + layers += [ + Conv2D( + filters2, + kernel_size, + padding="same", + kernel_initializer="orthogonal", + use_bias=False, + kernel_regularizer=l2(weight_decay), + name=conv_name_2, + ) + ] + layers += [BatchNormalization(axis=bn_axis, name=bn_name_2)] + layers += [Activation("relu")] + + conv_name_3 = "conv" + str(stage) + "_" + str(block) + "_1x1_increase" + bn_name_3 = "conv" + str(stage) + "_" + str(block) + "_1x1_increase/bn" + layers += [ + Conv2D( + filters3, + (1, 1), + kernel_initializer="orthogonal", + use_bias=False, + kernel_regularizer=l2(weight_decay), + name=conv_name_3, + ) + ] + layers += [BatchNormalization(axis=bn_axis, name=bn_name_3)] + self.layers = layers + + def call(self, input_tensor, training=None): + + x = input_tensor + for l in self.layers: + x = l(x, training=training) + + x = tf.keras.layers.add([x, input_tensor]) + x = Activation("relu")(x) + + return x + + +class ConvBlock(tf.keras.layers.Layer): + def __init__( + self, + kernel_size, + filters, + stage, + block, + strides=(2, 2), + weight_decay=1e-4, + name=None, + **kwargs, + ): + """ Block that has a conv layer AS shortcut. + Parameters + ---------- + kernel_size: + The kernel size of middle conv layer at main path + + filters: + list of integers, the filterss of 3 conv layer at main path + stage: + Current stage label, used for generating layer names + + block: + 'a','b'..., current block label, used for generating layer names + """ + super().__init__(name=name, **kwargs) + + filters1, filters2, filters3 = filters + bn_axis = 3 + + conv_name_1 = "conv" + str(stage) + "_" + str(block) + "_1x1_reduce" + bn_name_1 = "conv" + str(stage) + "_" + str(block) + "_1x1_reduce/bn" + layers = [ + Conv2D( + filters1, + (1, 1), + strides=strides, + kernel_initializer="orthogonal", + use_bias=False, + kernel_regularizer=l2(weight_decay), + name=conv_name_1, + ) + ] + layers += [BatchNormalization(axis=bn_axis, name=bn_name_1)] + layers += [Activation("relu")] + + conv_name_2 = "conv" + str(stage) + "_" + str(block) + "_3x3" + bn_name_2 = "conv" + str(stage) + "_" + str(block) + "_3x3/bn" + layers += [ + Conv2D( + filters2, + kernel_size, + padding="same", + kernel_initializer="orthogonal", + use_bias=False, + kernel_regularizer=l2(weight_decay), + name=conv_name_2, + ) + ] + layers += [BatchNormalization(axis=bn_axis, name=bn_name_2)] + layers += [Activation("relu")] + + conv_name_3 = "conv" + str(stage) + "_" + str(block) + "_1x1_increase" + bn_name_3 = "conv" + str(stage) + "_" + str(block) + "_1x1_increase/bn" + layers += [ + Conv2D( + filters3, + (1, 1), + kernel_initializer="orthogonal", + use_bias=False, + kernel_regularizer=l2(weight_decay), + name=conv_name_3, + ) + ] + layers += [BatchNormalization(axis=bn_axis, name=bn_name_3)] + + conv_name_4 = "conv" + str(stage) + "_" + str(block) + "_1x1_proj" + bn_name_4 = "conv" + str(stage) + "_" + str(block) + "_1x1_proj/bn" + shortcut = [ + Conv2D( + filters3, + (1, 1), + strides=strides, + kernel_initializer="orthogonal", + use_bias=False, + kernel_regularizer=l2(weight_decay), + name=conv_name_4, + ) + ] + shortcut += [BatchNormalization(axis=bn_axis, name=bn_name_4)] + + self.layers = layers + self.shortcut = shortcut + + def call(self, input_tensor, training=None): + x = input_tensor + for l in self.layers: + x = l(x, training=training) + + x_s = input_tensor + for l in self.shortcut: + x_s = l(x_s, training=training) + + x = tf.keras.layers.add([x, x_s]) + x = Activation("relu")(x) + return x + + +def resnet50_modified(input_tensor=None, input_shape=None, **kwargs): + """ + The resnet50 from `tf.keras.applications.Resnet50` has a problem with the convolutional layers. + It basically add bias terms to such layers followed by batch normalizations, which is not correct + + https://github.com/tensorflow/tensorflow/issues/37365 + + This resnet 50 implementation provides a cleaner version + + """ + if input_tensor is None: + input_tensor = tf.keras.Input(shape=input_shape) + else: + if not K.is_keras_tensor(input_tensor): + input_tensor = tf.keras.Input(tensor=input_tensor, shape=input_shape) + + bn_axis = 3 + # inputs are of size 224 x 224 x 3 + layers = [input_tensor] + layers += [ + Conv2D( + 64, + (7, 7), + strides=(2, 2), + kernel_initializer="orthogonal", + use_bias=False, + trainable=True, + kernel_regularizer=l2(weight_decay), + padding="same", + name="conv1/7x7_s2", + ) + ] + + # inputs are of size 112 x 112 x 64 + layers += [BatchNormalization(axis=bn_axis, name="conv1/7x7_s2/bn")] + layers += [Activation("relu")] + layers += [MaxPooling2D((3, 3), strides=(2, 2))] + + # inputs are of size 56 x 56 + layers += [ConvBlock(3, [64, 64, 256], stage=2, block=1, strides=(1, 1))] + layers += [IdentityBlock(3, [64, 64, 256], stage=2, block=2)] + layers += [IdentityBlock(3, [64, 64, 256], stage=2, block=3)] + + # inputs are of size 28 x 28 + layers += [ConvBlock(3, [128, 128, 512], stage=3, block=1)] + layers += [IdentityBlock(3, [128, 128, 512], stage=3, block=2)] + layers += [IdentityBlock(3, [128, 128, 512], stage=3, block=3)] + layers += [IdentityBlock(3, [128, 128, 512], stage=3, block=4)] + + # inputs are of size 14 x 14 + layers += [ConvBlock(3, [256, 256, 1024], stage=4, block=1)] + layers += [IdentityBlock(3, [256, 256, 1024], stage=4, block=2)] + layers += [IdentityBlock(3, [256, 256, 1024], stage=4, block=3)] + layers += [IdentityBlock(3, [256, 256, 1024], stage=4, block=4)] + layers += [IdentityBlock(3, [256, 256, 1024], stage=4, block=5)] + layers += [IdentityBlock(3, [256, 256, 1024], stage=4, block=6)] + + # inputs are of size 7 x 7 + layers += [ConvBlock(3, [512, 512, 2048], stage=5, block=1)] + layers += [IdentityBlock(3, [512, 512, 2048], stage=5, block=2)] + layers += [IdentityBlock(3, [512, 512, 2048], stage=5, block=3)] + + return tf.keras.Sequential(layers) + + +def resnet101_modified(input_tensor=None, input_shape=None, **kwargs): + """ + The resnet101 from `tf.keras.applications.Resnet101` has a problem with the convolutional layers. + It basically add bias terms to such layers followed by batch normalizations, which is not correct + + https://github.com/tensorflow/tensorflow/issues/37365 + + This resnet 10 implementation provides a cleaner version + + """ + + if input_tensor is None: + input_tensor = tf.keras.Input(shape=input_shape) + else: + if not tf.keras.backend.is_keras_tensor(input_tensor): + input_tensor = tf.keras.Input(tensor=input_tensor, shape=input_shape) + + bn_axis = 3 + # inputs are of size 224 x 224 x 3 + layers = [input_tensor] + layers += [ + Conv2D( + 64, + (7, 7), + strides=(2, 2), + kernel_initializer="orthogonal", + use_bias=False, + trainable=True, + kernel_regularizer=l2(weight_decay), + padding="same", + name="conv1/7x7_s2", + ) + ] + + # inputs are of size 112 x 112 x 64 + layers += [BatchNormalization(axis=bn_axis, name="conv1/7x7_s2/bn")] + layers += [Activation("relu")] + layers += [MaxPooling2D((3, 3), strides=(2, 2))] + + # inputs are of size 56 x 56 + layers += [ConvBlock(3, [64, 64, 256], stage=2, block=1, strides=(1, 1))] + layers += [IdentityBlock(3, [64, 64, 256], stage=2, block=2)] + layers += [IdentityBlock(3, [64, 64, 256], stage=2, block=3)] + + # inputs are of size 28 x 28 + layers += [ConvBlock(3, [128, 128, 512], stage=3, block=1)] + layers += [IdentityBlock(3, [128, 128, 512], stage=3, block=2)] + layers += [IdentityBlock(3, [128, 128, 512], stage=3, block=3)] + layers += [IdentityBlock(3, [128, 128, 512], stage=3, block=4)] + + # inputs are of size 14 x 14 + # 23 blocks here. That's the only difference from + # resnet-101 + layers += [ConvBlock(3, [256, 256, 1024], stage=4, block=1)] + for i in range(2, 24): + layers += [IdentityBlock(3, [256, 256, 1024], stage=4, block=i)] + + # inputs are of size 7 x 7 + layers += [ConvBlock(3, [512, 512, 2048], stage=5, block=1)] + layers += [IdentityBlock(3, [512, 512, 2048], stage=5, block=2)] + layers += [IdentityBlock(3, [512, 512, 2048], stage=5, block=3)] + + return tf.keras.Sequential(layers) + + +if __name__ == "__main__": + input_tensor = tf.keras.layers.InputLayer([112, 112, 3]) + model = resnet_50(input_tensor) + + print(len(model.variables)) + print(model.summary()) + -- GitLab