From 090112323cf12f5da6b6c7532cb0331bdf4330ec Mon Sep 17 00:00:00 2001 From: Aakash Kumar Nain Date: Wed, 24 May 2023 01:06:51 +0530 Subject: [PATCH] More resnet models (#208) * add ResNet50 model * add resnet to the models list for testing * fix docctsrings * add Resnet101, Resnet152, and blocks for v2 * add Resnet_v2 models * add tests for resnets --- keras_core/applications/applications_test.py | 6 + keras_core/applications/resnet.py | 192 ++++++++++++++++++ keras_core/applications/resnet_v2.py | 201 +++++++++++++++++++ 3 files changed, 399 insertions(+) create mode 100644 keras_core/applications/resnet_v2.py diff --git a/keras_core/applications/applications_test.py b/keras_core/applications/applications_test.py index 67c32c91c..fb29c2ac5 100644 --- a/keras_core/applications/applications_test.py +++ b/keras_core/applications/applications_test.py @@ -15,6 +15,7 @@ from keras_core.applications import mobilenet_v2 from keras_core.applications import mobilenet_v3 from keras_core.applications import nasnet from keras_core.applications import resnet +from keras_core.applications import resnet_v2 from keras_core.applications import vgg16 from keras_core.applications import vgg19 from keras_core.applications import xception @@ -72,6 +73,11 @@ MODEL_LIST = [ (nasnet.NASNetLarge, 4032, nasnet), # resnet (resnet.ResNet50, 2048, resnet), + (resnet.ResNet101, 2048, resnet), + (resnet.ResNet152, 2048, resnet), + (resnet_v2.ResNet50V2, 2048, resnet_v2), + (resnet_v2.ResNet101V2, 2048, resnet_v2), + (resnet_v2.ResNet152V2, 2048, resnet_v2), ] # Add names for `named_parameters`. MODEL_LIST = [(e[0].__name__, *e) for e in MODEL_LIST] diff --git a/keras_core/applications/resnet.py b/keras_core/applications/resnet.py index 8c6916021..49494d1d7 100644 --- a/keras_core/applications/resnet.py +++ b/keras_core/applications/resnet.py @@ -16,6 +16,34 @@ WEIGHTS_HASHES = { "2cb95161c43110f7111970584f804107", "4d473c1dd8becc155b73f8504c6f6626", ), + "resnet101": ( + "f1aeb4b969a6efcfb50fad2f0c20cfc5", + "88cf7a10940856eca736dc7b7e228a21", + ), + "resnet152": ( + "100835be76be38e30d865e96f2aaae62", + "ee4c566cf9a93f14d82f913c2dc6dd0c", + ), + "resnet50v2": ( + "3ef43a0b657b3be2300d5770ece849e0", + "fac2f116257151a9d068a22e544a4917", + ), + "resnet101v2": ( + "6343647c601c52e1368623803854d971", + "c0ed64b8031c3730f411d2eb4eea35b5", + ), + "resnet152v2": ( + "a49b44d1979771252814e80f8ec446f9", + "ed17cf2e0169df9d443503ef94b23b33", + ), + "resnext50": ( + "67a5b30d522ed92f75a1f16eef299d1a", + "62527c363bdd9ec598bed41947b379fc", + ), + "resnext101": ( + "34fb605428fcc7aa4d62f44404c11509", + "0f678c91647380debd923963594981b3", + ), } @@ -267,6 +295,92 @@ def stack_residual_blocks_v1(x, filters, blocks, stride1=2, name=None): return x +def residual_block_v2( + x, filters, kernel_size=3, stride=1, conv_shortcut=False, name=None +): + """A residual block for ResNet*_v2. + + Args: + x: Input tensor. + filters: No of filters in the bottleneck layer. + kernel_size: Kernel size of the bottleneck layer. Defaults to 3 + stride: Stride of the first layer. Defaults to 1 + conv_shortcut: Use convolution shortcut if `True`, otherwise + use identity shortcut. Defaults to `True` + name(optional): Name of the block + + Returns: + Output tensor for the residual block. + """ + + if backend.image_data_format() == "channels_last": + bn_axis = 3 + else: + bn_axis = 1 + + preact = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + "_preact_bn" + )(x) + preact = layers.Activation("relu", name=name + "_preact_relu")(preact) + + if conv_shortcut: + shortcut = layers.Conv2D( + 4 * filters, 1, strides=stride, name=name + "_0_conv" + )(preact) + else: + shortcut = ( + layers.MaxPooling2D(1, strides=stride)(x) if stride > 1 else x + ) + + x = layers.Conv2D( + filters, 1, strides=1, use_bias=False, name=name + "_1_conv" + )(preact) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + "_1_bn" + )(x) + x = layers.Activation("relu", name=name + "_1_relu")(x) + + x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name=name + "_2_pad")(x) + x = layers.Conv2D( + filters, + kernel_size, + strides=stride, + use_bias=False, + name=name + "_2_conv", + )(x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + "_2_bn" + )(x) + x = layers.Activation("relu", name=name + "_2_relu")(x) + + x = layers.Conv2D(4 * filters, 1, name=name + "_3_conv")(x) + x = layers.Add(name=name + "_out")([shortcut, x]) + return x + + +def stack_residual_blocks_v2(x, filters, blocks, stride1=2, name=None): + """A set of stacked residual blocks. + + Args: + x: Input tensor. + filters: Number of filters in the bottleneck layer in a block. + blocks: Number of blocks in the stacked blocks. + stride1: Stride of the first layer in the first block. Defaults to 2. + name: Stack label. + + Returns: + Output tensor for the stacked blocks. + """ + + x = residual_block_v2(x, filters, conv_shortcut=True, name=name + "_block1") + for i in range(2, blocks): + x = residual_block_v2(x, filters, name=name + "_block" + str(i)) + x = residual_block_v2( + x, filters, stride=stride1, name=name + "_block" + str(blocks) + ) + return x + + @keras_core_export( [ "keras_core.applications.resnet50.ResNet50", @@ -306,6 +420,82 @@ def ResNet50( ) +@keras_core_export( + [ + "keras_core.applications.resnet.ResNet101", + "keras_core.applications.ResNet101", + ] +) +def ResNet101( + include_top=True, + weights="imagenet", + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + classifier_activation="softmax", +): + """Instantiates the ResNet101 architecture.""" + + def stack_fn(x): + x = stack_residual_blocks_v1(x, 64, 3, stride1=1, name="conv2") + x = stack_residual_blocks_v1(x, 128, 4, name="conv3") + x = stack_residual_blocks_v1(x, 256, 23, name="conv4") + return stack_residual_blocks_v1(x, 512, 3, name="conv5") + + return ResNet( + stack_fn, + False, + True, + "resnet101", + include_top, + weights, + input_tensor, + input_shape, + pooling, + classes, + classifier_activation=classifier_activation, + ) + + +@keras_core_export( + [ + "keras_core.applications.resnet.ResNet152", + "keras_core.applications.ResNet152", + ] +) +def ResNet152( + include_top=True, + weights="imagenet", + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + classifier_activation="softmax", +): + """Instantiates the ResNet152 architecture.""" + + def stack_fn(x): + x = stack_residual_blocks_v1(x, 64, 3, stride1=1, name="conv2") + x = stack_residual_blocks_v1(x, 128, 8, name="conv3") + x = stack_residual_blocks_v1(x, 256, 36, name="conv4") + return stack_residual_blocks_v1(x, 512, 3, name="conv5") + + return ResNet( + stack_fn, + False, + True, + "resnet152", + include_top, + weights, + input_tensor, + input_shape, + pooling, + classes, + classifier_activation=classifier_activation, + ) + + @keras_core_export( [ "keras_core.applications.resnet50.preprocess_input", @@ -390,3 +580,5 @@ Returns: """ setattr(ResNet50, "__doc__", ResNet50.__doc__ + DOC) +setattr(ResNet101, "__doc__", ResNet101.__doc__ + DOC) +setattr(ResNet152, "__doc__", ResNet152.__doc__ + DOC) diff --git a/keras_core/applications/resnet_v2.py b/keras_core/applications/resnet_v2.py new file mode 100644 index 000000000..f1f1ac6ac --- /dev/null +++ b/keras_core/applications/resnet_v2.py @@ -0,0 +1,201 @@ +from keras_core.api_export import keras_core_export +from keras_core.applications import imagenet_utils +from keras_core.applications import resnet + + +@keras_core_export( + [ + "keras_core.applications.resnet_v2.ResNet50V2", + "keras_core.applications.resnet_v2.ResNet50V2", + ] +) +def ResNet50V2( + include_top=True, + weights="imagenet", + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + classifier_activation="softmax", +): + """Instantiates the ResNet50V2 architecture.""" + + def stack_fn(x): + x = resnet.stack_residual_blocks_v2(x, 64, 3, name="conv2") + x = resnet.stack_residual_blocks_v2(x, 128, 4, name="conv3") + x = resnet.stack_residual_blocks_v2(x, 256, 6, name="conv4") + return resnet.stack_residual_blocks_v2( + x, 512, 3, stride1=1, name="conv5" + ) + + return resnet.ResNet( + stack_fn, + True, + True, + "resnet50v2", + include_top, + weights, + input_tensor, + input_shape, + pooling, + classes, + classifier_activation=classifier_activation, + ) + + +@keras_core_export( + [ + "keras_core.applications.resnet_v2.ResNet101V2", + "keras_core.applications.resnet_v2.ResNet101V2", + ] +) +def ResNet101V2( + include_top=True, + weights="imagenet", + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + classifier_activation="softmax", +): + """Instantiates the ResNet101V2 architecture.""" + + def stack_fn(x): + x = resnet.stack_residual_blocks_v2(x, 64, 3, name="conv2") + x = resnet.stack_residual_blocks_v2(x, 128, 4, name="conv3") + x = resnet.stack_residual_blocks_v2(x, 256, 23, name="conv4") + return resnet.stack_residual_blocks_v2( + x, 512, 3, stride1=1, name="conv5" + ) + + return resnet.ResNet( + stack_fn, + True, + True, + "resnet101v2", + include_top, + weights, + input_tensor, + input_shape, + pooling, + classes, + classifier_activation=classifier_activation, + ) + + +@keras_core_export( + [ + "keras_core.applications.resnet_v2.ResNet152V2", + "keras_core.applications.resnet_v2.ResNet152V2", + ] +) +def ResNet152V2( + include_top=True, + weights="imagenet", + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + classifier_activation="softmax", +): + """Instantiates the ResNet152V2 architecture.""" + + def stack_fn(x): + x = resnet.stack_residual_blocks_v2(x, 64, 3, name="conv2") + x = resnet.stack_residual_blocks_v2(x, 128, 8, name="conv3") + x = resnet.stack_residual_blocks_v2(x, 256, 36, name="conv4") + return resnet.stack_residual_blocks_v2( + x, 512, 3, stride1=1, name="conv5" + ) + + return resnet.ResNet( + stack_fn, + True, + True, + "resnet152v2", + include_top, + weights, + input_tensor, + input_shape, + pooling, + classes, + classifier_activation=classifier_activation, + ) + + +@keras_core_export("keras_core.applications.resnet_v2.preprocess_input") +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input( + x, data_format=data_format, mode="tf" + ) + + +@keras_core_export("keras_core.applications.resnet_v2.decode_predictions") +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) + + +preprocess_input.__doc__ = imagenet_utils.PREPROCESS_INPUT_DOC.format( + mode="", + ret=imagenet_utils.PREPROCESS_INPUT_RET_DOC_TF, + error=imagenet_utils.PREPROCESS_INPUT_ERROR_DOC, +) +decode_predictions.__doc__ = imagenet_utils.decode_predictions.__doc__ + + +DOC = """ + +Reference: +- [Identity Mappings in Deep Residual Networks]( + https://arxiv.org/abs/1603.05027) (CVPR 2016) + +For image classification use cases, see [this page for detailed examples]( + https://keras.io/api/applications/#usage-examples-for-image-classification-models). + +For transfer learning use cases, make sure to read the +[guide to transfer learning & fine-tuning]( + https://keras.io/guides/transfer_learning/). + +Note: each Keras Application expects a specific kind of input preprocessing. +For ResNet, call `keras_core.applications.resnet_v2.preprocess_input` on your +inputs before passing them to the model. `resnet_v2.preprocess_input` will +scale input pixels between -1 and 1. + +Args: + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + `"imagenet"` (pre-training on ImageNet), or the path to the weights + file to be loaded. + input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified if `include_top` + is `False` (otherwise the input shape has to be `(224, 224, 3)` + (with `"channels_last"` data format) or `(3, 224, 224)` + (with `"channels_first"` data format). It should have exactly 3 + inputs channels, and width and height should be no smaller than 32. + E.g. `(200, 200, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction when `include_top` + is `False`. + - `None` means that the output of the model will be the 4D tensor + output of the last convolutional block. + - `avg` means that global average pooling will be applied to the output + of the last convolutional block, and thus the output of the + model will be a 2D tensor. + - `max` means that global max pooling will be applied. + classes: optional number of classes to classify images into, only to be + specified if `include_top` is `True`, and if no `weights` argument is + specified. + classifier_activation: A `str` or callable. The activation function to + use on the "top" layer. Ignored unless `include_top=True`. Set + `classifier_activation=None` to return the logits of the "top" layer. + When loading pretrained weights, `classifier_activation` can only + be `None` or `"softmax"`. + +Returns: + A Model instance. +""" + +setattr(ResNet50V2, "__doc__", ResNet50V2.__doc__ + DOC) +setattr(ResNet101V2, "__doc__", ResNet101V2.__doc__ + DOC) +setattr(ResNet152V2, "__doc__", ResNet152V2.__doc__ + DOC)