Created
July 31, 2017 16:44
-
-
Save seovchinnikov/319303c13fa8db76da4e91705cdc9a97 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
"""ResNet50 model for Keras with fused intermediate layers | |
# Reference: | |
https://arxiv.org/pdf/1604.00133.pdf | |
Adapted from original resnet | |
""" | |
from __future__ import print_function | |
from __future__ import absolute_import | |
import warnings | |
from keras import layers | |
from keras.applications.imagenet_utils import _obtain_input_shape | |
from keras.layers import Concatenate | |
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 ZeroPadding2D | |
from keras.layers import AveragePooling2D | |
from keras.layers import GlobalAveragePooling2D | |
from keras.layers import GlobalMaxPooling2D | |
from keras.layers import BatchNormalization | |
from keras.models import Model | |
from keras import backend as K | |
from keras.engine.topology import get_source_inputs | |
from keras.utils import layer_utils | |
from keras.utils.data_utils import get_file | |
WEIGHTS_PATH = 'https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_weights_tf_dim_ordering_tf_kernels.h5' | |
WEIGHTS_PATH_NO_TOP = 'https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_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 filterss 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. | |
""" | |
filters1, filters2, filters3 = filters | |
if K.image_data_format() == 'channels_last': | |
bn_axis = 3 | |
else: | |
bn_axis = 1 | |
conv_name_base = 'res' + str(stage) + block + '_branch' | |
bn_name_base = 'bn' + str(stage) + block + '_branch' | |
x = Conv2D(filters1, (1, 1), name=conv_name_base + '2a')(input_tensor) | |
x = BatchNormalization(axis=bn_axis, name=bn_name_base + '2a')(x) | |
x = Activation('relu')(x) | |
x = Conv2D(filters2, kernel_size, | |
padding='same', name=conv_name_base + '2b')(x) | |
x = BatchNormalization(axis=bn_axis, name=bn_name_base + '2b')(x) | |
x = Activation('relu')(x) | |
x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x) | |
x = BatchNormalization(axis=bn_axis, name=bn_name_base + '2c')(x) | |
x = layers.add([x, input_tensor]) | |
x = Activation('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 filterss 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 | |
""" | |
filters1, filters2, filters3 = filters | |
if K.image_data_format() == 'channels_last': | |
bn_axis = 3 | |
else: | |
bn_axis = 1 | |
conv_name_base = 'res' + str(stage) + block + '_branch' | |
bn_name_base = 'bn' + str(stage) + block + '_branch' | |
x = Conv2D(filters1, (1, 1), strides=strides, | |
name=conv_name_base + '2a')(input_tensor) | |
x = BatchNormalization(axis=bn_axis, name=bn_name_base + '2a')(x) | |
x = Activation('relu')(x) | |
x = Conv2D(filters2, kernel_size, padding='same', | |
name=conv_name_base + '2b')(x) | |
x = BatchNormalization(axis=bn_axis, name=bn_name_base + '2b')(x) | |
x = Activation('relu')(x) | |
x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x) | |
x = BatchNormalization(axis=bn_axis, name=bn_name_base + '2c')(x) | |
shortcut = Conv2D(filters3, (1, 1), strides=strides, | |
name=conv_name_base + '1')(input_tensor) | |
shortcut = BatchNormalization(axis=bn_axis, name=bn_name_base + '1')(shortcut) | |
x = layers.add([x, shortcut]) | |
x = Activation('relu')(x) | |
return x | |
def ResNet50Fused(include_top=True, weights='imagenet', | |
input_tensor=None, input_shape=None, | |
pooling=None, | |
classes=1000): | |
"""Instantiates the fused ResNet50 architecture. | |
include_top=True is hardcoded. | |
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) | |
or "imagenet" (pre-training on ImageNet). | |
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. | |
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 weights not in {'imagenet', None}: | |
raise ValueError('The `weights` argument should be either ' | |
'`None` (random initialization) or `imagenet` ' | |
'(pre-training on ImageNet).') | |
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') | |
# Determine proper input shape | |
input_shape = _obtain_input_shape(input_shape, | |
default_size=224, | |
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 | |
if K.image_data_format() == 'channels_last': | |
bn_axis = 3 | |
else: | |
bn_axis = 1 | |
x = ZeroPadding2D((3, 3))(img_input) | |
x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1')(x) | |
x = BatchNormalization(axis=bn_axis, name='bn_conv1')(x) | |
x = Activation('relu')(x) | |
x = MaxPooling2D((3, 3), strides=(2, 2))(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') | |
x2 = identity_block(x, 3, [64, 64, 256], stage=2, block='c') | |
x = conv_block(x2, 3, [128, 128, 512], stage=3, block='a') | |
x = identity_block(x, 3, [128, 128, 512], stage=3, block='b') | |
x = identity_block(x, 3, [128, 128, 512], stage=3, block='c') | |
x1 = identity_block(x, 3, [128, 128, 512], stage=3, block='d') | |
x = conv_block(x1, 3, [256, 256, 1024], stage=4, block='a') | |
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b') | |
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='c') | |
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='d') | |
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='e') | |
x0 = identity_block(x, 3, [256, 256, 1024], stage=4, block='f') | |
#out1 = GlobalAveragePooling2D(name='out2')(x) | |
#out1 = Flatten(name='out2')(out1) | |
x = conv_block(x0, 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) | |
# last layer is flattened | |
x_fl = Flatten()(x) | |
x_ex = Dense(classes, activation='softmax', name='fc1000')(x_fl) | |
# 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 basic model. | |
model = Model(inputs, x_ex, name='resnet50') | |
# pooling for intermediate layers | |
flatten2 = GlobalAveragePooling2D(name='flatten02')(x2) | |
flatten1 = GlobalAveragePooling2D(name='flatten01')(x1) | |
flatten0 = GlobalAveragePooling2D(name='flatten00')(x0) | |
# load weights for basic model | |
if weights == 'imagenet': | |
if include_top: | |
weights_path = get_file('resnet50_weights_tf_dim_ordering_tf_kernels.h5', | |
WEIGHTS_PATH, | |
cache_subdir='models', | |
md5_hash='a7b3fe01876f51b976af0dea6bc144eb') | |
else: | |
weights_path = get_file('resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5', | |
WEIGHTS_PATH_NO_TOP, | |
cache_subdir='models', | |
md5_hash='a268eb855778b3df3c7506639542a6af') | |
model.load_weights(weights_path) | |
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.') | |
# new output with concatenated intermediate layers | |
conc = Concatenate(name='ress')([flatten2, flatten1, flatten0, x_fl]) | |
# new model | |
model_concatenated = Model(input=model.input, | |
output=conc) | |
return model_concatenated |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment