From b64cc0076ec650f3df232051305be158567a00d2 Mon Sep 17 00:00:00 2001 From: Grigorios Kalliatakis Date: Wed, 3 Jan 2018 12:49:53 +0000 Subject: [PATCH] Create resnet101.py --- resnet101.py | 382 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 resnet101.py diff --git a/resnet101.py b/resnet101.py new file mode 100644 index 0000000..3848f0d --- /dev/null +++ b/resnet101.py @@ -0,0 +1,382 @@ +# -*- coding: utf-8 -*- +"""ResNet101 model for Keras. + +# Reference: + +- [Deep Residual Learning for Image Recognition](https://arxiv.org/abs/1512.03385) + +Adapted from code contributed by flyyufelix, mvoelk, BigMoyan, fchollet, adamcasson +""" + +import os +import warnings + +from keras.layers import Input +from keras.layers import Dense +from keras.layers import Activation +from keras.layers import Flatten +from keras.layers import Conv2D +from keras.layers import MaxPooling2D +from keras.layers import GlobalMaxPooling2D +from keras.layers import ZeroPadding2D +from keras.layers import AveragePooling2D +from keras.layers import GlobalAveragePooling2D +from keras.layers import BatchNormalization +from keras.layers import merge +from keras.models import Model +import keras.backend as K +from keras.engine.topology import get_source_inputs +from keras.utils import layer_utils +from keras import initializers +from keras.engine import Layer, InputSpec +from keras.utils.data_utils import get_file +from keras.applications.imagenet_utils import _obtain_input_shape + +import sys + +sys.setrecursionlimit(3000) + +WEIGHTS_PATH = 'https://github.com/GKalliatakis/Keras-Application-Zoo/releases/download/0.2/resnet101_weights_tf_dim_ordering_tf_kernels.h5' +WEIGHTS_PATH_NO_TOP = 'https://github.com/GKalliatakis/Keras-Application-Zoo/releases/download/0.2/resnet101_weights_tf_dim_ordering_tf_kernels_notop.h5' + + +def identity_block(input_tensor, kernel_size, filters, stage, block): + """The identity_block is the block that has no conv layer at shortcut + + # Arguments + input_tensor: input tensor + kernel_size: default 3, the kernel size of middle conv layer at main path + filters: list of integers, the filters of 3 conv layer at main path + stage: integer, current stage label, used for generating layer names + block: 'a','b'..., current block label, used for generating layer names + + # Returns + Output tensor for the block. + """ + eps = 1.1e-5 + + if K.image_dim_ordering() == 'tf': + bn_axis = 3 + else: + bn_axis = 1 + + nb_filter1, nb_filter2, nb_filter3 = filters + conv_name_base = 'res' + str(stage) + block + '_branch' + bn_name_base = 'bn' + str(stage) + block + '_branch' + scale_name_base = 'scale' + str(stage) + block + '_branch' + + x = Conv2D(nb_filter1, (1, 1), name=conv_name_base + '2a', use_bias=False)(input_tensor) + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x) + x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x) + x = Activation('relu', name=conv_name_base + '2a_relu')(x) + + x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x) + x = Conv2D(nb_filter2, (kernel_size, kernel_size), name=conv_name_base + '2b', use_bias=False)(x) + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x) + x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x) + x = Activation('relu', name=conv_name_base + '2b_relu')(x) + + x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x) + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x) + x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x) + + x = merge([x, input_tensor], mode='sum', name='res' + str(stage) + block) + x = Activation('relu', name='res' + str(stage) + block + '_relu')(x) + return x + + +def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)): + """conv_block is the block that has a conv layer at shortcut + + # Arguments + input_tensor: input tensor + kernel_size: default 3, the kernel size of middle conv layer at main path + filters: list of integers, the filters of 3 conv layer at main path + stage: integer, current stage label, used for generating layer names + block: 'a','b'..., current block label, used for generating layer names + + # Returns + Output tensor for the block. + Note that from stage 3, the first conv layer at main path is with strides=(2,2) + And the shortcut should have strides=(2,2) as well + """ + eps = 1.1e-5 + + if K.image_dim_ordering() == 'tf': + bn_axis = 3 + else: + bn_axis = 1 + + nb_filter1, nb_filter2, nb_filter3 = filters + conv_name_base = 'res' + str(stage) + block + '_branch' + bn_name_base = 'bn' + str(stage) + block + '_branch' + scale_name_base = 'scale' + str(stage) + block + '_branch' + + x = Conv2D(nb_filter1, (1, 1), strides=strides, name=conv_name_base + '2a', use_bias=False)(input_tensor) + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x) + x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x) + x = Activation('relu', name=conv_name_base + '2a_relu')(x) + + x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x) + x = Conv2D(nb_filter2, (kernel_size, kernel_size), + name=conv_name_base + '2b', use_bias=False)(x) + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x) + x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x) + x = Activation('relu', name=conv_name_base + '2b_relu')(x) + + x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x) + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x) + x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x) + + shortcut = Conv2D(nb_filter3, (1, 1), strides=strides, + name=conv_name_base + '1', use_bias=False)(input_tensor) + shortcut = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '1')(shortcut) + shortcut = Scale(axis=bn_axis, name=scale_name_base + '1')(shortcut) + + x = merge([x, shortcut], mode='sum', name='res' + str(stage) + block) + x = Activation('relu', name='res' + str(stage) + block + '_relu')(x) + return x + + +class Scale(Layer): + """Custom Layer for ResNet used for BatchNormalization. + + Learns a set of weights and biases used for scaling the input data. + the output consists simply in an element-wise multiplication of the input + and a sum of a set of constants: + + out = in * gamma + beta, + + where 'gamma' and 'beta' are the weights and biases larned. + + Keyword arguments: + axis -- integer, axis along which to normalize in mode 0. For instance, + if your input tensor has shape (samples, channels, rows, cols), + set axis to 1 to normalize per feature map (channels axis). + momentum -- momentum in the computation of the exponential average + of the mean and standard deviation of the data, for + feature-wise normalization. + weights -- Initialization weights. + List of 2 Numpy arrays, with shapes: + `[(input_shape,), (input_shape,)]` + beta_init -- name of initialization function for shift parameter + (see [initializers](../initializers.md)), or alternatively, + Theano/TensorFlow function to use for weights initialization. + This parameter is only relevant if you don't pass a `weights` argument. + gamma_init -- name of initialization function for scale parameter (see + [initializers](../initializers.md)), or alternatively, + Theano/TensorFlow function to use for weights initialization. + This parameter is only relevant if you don't pass a `weights` argument. + + """ + + def __init__(self, weights=None, axis=-1, momentum=0.9, beta_init='zero', gamma_init='one', **kwargs): + self.momentum = momentum + self.axis = axis + self.beta_init = initializers.get(beta_init) + self.gamma_init = initializers.get(gamma_init) + self.initial_weights = weights + super(Scale, self).__init__(**kwargs) + + def build(self, input_shape): + self.input_spec = [InputSpec(shape=input_shape)] + shape = (int(input_shape[self.axis]),) + + self.gamma = K.variable(self.gamma_init(shape), name='%s_gamma' % self.name) + self.beta = K.variable(self.beta_init(shape), name='%s_beta' % self.name) + self.trainable_weights = [self.gamma, self.beta] + + if self.initial_weights is not None: + self.set_weights(self.initial_weights) + del self.initial_weights + + def call(self, x, mask=None): + input_shape = self.input_spec[0].shape + broadcast_shape = [1] * len(input_shape) + broadcast_shape[self.axis] = input_shape[self.axis] + + out = K.reshape(self.gamma, broadcast_shape) * x + K.reshape(self.beta, broadcast_shape) + return out + + def get_config(self): + config = {"momentum": self.momentum, "axis": self.axis} + base_config = super(Scale, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + + +def ResNet101(include_top=True, weights='imagenet', + input_tensor=None, input_shape=None, + large_input=False, + pooling=None, + classes=1000): + """Instantiate the ResNet101 architecture. + + Optionally loads weights pre-trained + on ImageNet. Note that when using TensorFlow, + for best performance you should set + `image_data_format='channels_last'` in your Keras config + at ~/.keras/keras.json. + + The model and the weights are compatible with both + TensorFlow and Theano. The data format + convention used by the model is the one + specified in your Keras config file. + + # Arguments + 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 197. + E.g. `(200, 200, 3)` would be one valid value. + large_input: if True, then the input shape expected will be + `(448, 448, 3)` (with `channels_last` data format) or + `(3, 448, 448)` (with `channels_first` data format). (default False) + 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 layer. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional layer, 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. + + # Returns + A Keras model instance. + + # Raises + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as imagenet with `include_top`' + ' as true, `classes` should be 1000') + + eps = 1.1e-5 + + if large_input: + img_size = 448 + else: + img_size = 224 + + # Determine proper input shape + input_shape = _obtain_input_shape(input_shape, + default_size=img_size, + min_size=197, + data_format=K.image_data_format(), + include_top=include_top) + + if input_tensor is None: + img_input = Input(shape=input_shape) + else: + if not K.is_keras_tensor(input_tensor): + img_input = Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + # handle dimension ordering for different backends + if K.image_dim_ordering() == 'tf': + bn_axis = 3 + else: + bn_axis = 1 + + x = ZeroPadding2D((3, 3), name='conv1_zeropadding')(img_input) + x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1', use_bias=False)(x) + x = BatchNormalization(epsilon=eps, axis=bn_axis, name='bn_conv1')(x) + x = Scale(axis=bn_axis, name='scale_conv1')(x) + x = Activation('relu', name='conv1_relu')(x) + x = MaxPooling2D((3, 3), strides=(2, 2), name='pool1')(x) + + x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1)) + x = identity_block(x, 3, [64, 64, 256], stage=2, block='b') + x = identity_block(x, 3, [64, 64, 256], stage=2, block='c') + + x = conv_block(x, 3, [128, 128, 512], stage=3, block='a') + for i in range(1, 3): + x = identity_block(x, 3, [128, 128, 512], stage=3, block='b' + str(i)) + + x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a') + for i in range(1, 23): + x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b' + str(i)) + + x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a') + x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b') + x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c') + + x = AveragePooling2D((7, 7), name='avg_pool')(x) + + # include classification layer by default, not included for feature extraction + if include_top: + x = Flatten()(x) + x = Dense(classes, activation='softmax', name='fc1000')(x) + else: + if pooling == 'avg': + x = GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = get_source_inputs(input_tensor) + else: + inputs = img_input + # Create model. + model = Model(inputs, x, name='resnet101') + + # load weights + if weights == 'imagenet': + if include_top: + weights_path = get_file('resnet101_weights_tf_dim_ordering_tf_kernels.h5', + WEIGHTS_PATH) + else: + weights_path = get_file('resnet101_weights_tf_dim_ordering_tf_kernels_notop.h5', + WEIGHTS_PATH_NO_TOP) + + model.load_weights(weights_path, by_name=True) + if K.backend() == 'theano': + layer_utils.convert_all_kernels_in_model(model) + if include_top: + maxpool = model.get_layer(name='avg_pool') + shape = maxpool.output_shape[1:] + dense = model.get_layer(name='fc1000') + layer_utils.convert_dense_weights_data_format(dense, shape, 'channels_first') + + if K.image_data_format() == 'channels_first' and K.backend() == 'tensorflow': + warnings.warn('You are using the TensorFlow backend, yet you ' + 'are using the Theano ' + 'image data format convention ' + '(`image_data_format="channels_first"`). ' + 'For best performance, set ' + '`image_data_format="channels_last"` in ' + 'your Keras config ' + 'at ~/.keras/keras.json.') + return model + + +if __name__ == '__main__': + model = ResNet101(include_top=True, weights='imagenet') + model.summary() + +