├── .gitignore ├── LICENSE ├── README.md ├── resnext.py └── resnext_block.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 calmisential 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ResNeXt_TensorFlow2 2 | A tensorflow2 implementation of ResNeXt(ResNeXt50, ResNeXt101). 3 | 4 | See https://github.com/calmisential/Basic_CNNs_TensorFlow2 for training details. 5 | 6 | ## References 7 | 1. The original paper: [Aggregated Residual Transformations for Deep Neural Networks](https://arxiv.org/abs/1611.05431) 8 | -------------------------------------------------------------------------------- /resnext.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from resnext_block import build_ResNeXt_block 3 | 4 | NUM_CLASSES = 10 5 | 6 | 7 | class ResNeXt(tf.keras.Model): 8 | def __init__(self, repeat_num_list, cardinality): 9 | if len(repeat_num_list) != 4: 10 | raise ValueError("The length of repeat_num_list must be four.") 11 | super(ResNeXt, self).__init__() 12 | self.conv1 = tf.keras.layers.Conv2D(filters=64, 13 | kernel_size=(7, 7), 14 | strides=2, 15 | padding="same") 16 | self.bn1 = tf.keras.layers.BatchNormalization() 17 | self.pool1 = tf.keras.layers.MaxPool2D(pool_size=(3, 3), 18 | strides=2, 19 | padding="same") 20 | self.block1 = build_ResNeXt_block(filters=128, 21 | strides=1, 22 | groups=cardinality, 23 | repeat_num=repeat_num_list[0]) 24 | self.block2 = build_ResNeXt_block(filters=256, 25 | strides=2, 26 | groups=cardinality, 27 | repeat_num=repeat_num_list[1]) 28 | self.block3 = build_ResNeXt_block(filters=512, 29 | strides=2, 30 | groups=cardinality, 31 | repeat_num=repeat_num_list[2]) 32 | self.block4 = build_ResNeXt_block(filters=1024, 33 | strides=2, 34 | groups=cardinality, 35 | repeat_num=repeat_num_list[3]) 36 | self.pool2 = tf.keras.layers.GlobalAveragePooling2D() 37 | self.fc = tf.keras.layers.Dense(units=NUM_CLASSES, 38 | activation=tf.keras.activations.softmax) 39 | 40 | def call(self, inputs, training=None, mask=None): 41 | x = self.conv1(inputs) 42 | x = self.bn1(x, training=training) 43 | x = tf.nn.relu(x) 44 | x = self.pool1(x) 45 | 46 | x = self.block1(x, training=training) 47 | x = self.block2(x, training=training) 48 | x = self.block3(x, training=training) 49 | x = self.block4(x, training=training) 50 | 51 | x = self.pool2(x) 52 | x = self.fc(x) 53 | 54 | return x 55 | 56 | 57 | def ResNeXt50(): 58 | return ResNeXt(repeat_num_list=[3, 4, 6, 3], 59 | cardinality=32) 60 | 61 | 62 | def ResNeXt101(): 63 | return ResNeXt(repeat_num_list=[3, 4, 23, 3], 64 | cardinality=32) -------------------------------------------------------------------------------- /resnext_block.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from tensorflow.keras import initializers, regularizers, constraints 3 | from tensorflow.keras import activations 4 | 5 | 6 | class GroupConv2D(tf.keras.layers.Layer): 7 | def __init__(self, 8 | input_channels, 9 | output_channels, 10 | kernel_size, 11 | strides=(1, 1), 12 | padding='valid', 13 | data_format=None, 14 | dilation_rate=(1, 1), 15 | activation=None, 16 | groups=1, 17 | use_bias=True, 18 | kernel_initializer='glorot_uniform', 19 | bias_initializer='zeros', 20 | kernel_regularizer=None, 21 | bias_regularizer=None, 22 | activity_regularizer=None, 23 | kernel_constraint=None, 24 | bias_constraint=None, 25 | **kwargs): 26 | super(GroupConv2D, self).__init__() 27 | 28 | if not input_channels % groups == 0: 29 | raise ValueError("The value of input_channels must be divisible by the value of groups.") 30 | if not output_channels % groups == 0: 31 | raise ValueError("The value of output_channels must be divisible by the value of groups.") 32 | 33 | self.kernel_size = kernel_size 34 | self.strides = strides 35 | self.padding = padding 36 | self.data_format = data_format 37 | self.dilation_rate = dilation_rate 38 | self.activation = activation 39 | self.groups = groups 40 | self.use_bias = use_bias 41 | self.kernel_initializer = kernel_initializer 42 | self.bias_initializer = bias_initializer 43 | self.kernel_regularizer = kernel_regularizer 44 | self.bias_regularizer = bias_regularizer 45 | self.activity_regularizer = activity_regularizer 46 | self.kernel_constraint = kernel_constraint 47 | self.bias_constraint = bias_constraint 48 | 49 | self.group_in_num = input_channels // groups 50 | self.group_out_num = output_channels // groups 51 | self.conv_list = [] 52 | for i in range(self.groups): 53 | self.conv_list.append(tf.keras.layers.Conv2D(filters=self.group_out_num, 54 | kernel_size=kernel_size, 55 | strides=strides, 56 | padding=padding, 57 | data_format=data_format, 58 | dilation_rate=dilation_rate, 59 | activation=activations.get(activation), 60 | use_bias=use_bias, 61 | kernel_initializer=initializers.get(kernel_initializer), 62 | bias_initializer=initializers.get(bias_initializer), 63 | kernel_regularizer=regularizers.get(kernel_regularizer), 64 | bias_regularizer=regularizers.get(bias_regularizer), 65 | activity_regularizer=regularizers.get(activity_regularizer), 66 | kernel_constraint=constraints.get(kernel_constraint), 67 | bias_constraint=constraints.get(bias_constraint), 68 | **kwargs)) 69 | 70 | def call(self, inputs, **kwargs): 71 | feature_map_list = [] 72 | for i in range(self.groups): 73 | x_i = self.conv_list[i](inputs[:, :, :, i*self.group_in_num: (i + 1) * self.group_in_num]) 74 | feature_map_list.append(x_i) 75 | out = tf.concat(feature_map_list, axis=-1) 76 | return out 77 | 78 | 79 | class ResNeXt_BottleNeck(tf.keras.layers.Layer): 80 | def __init__(self, filters, strides, groups): 81 | super(ResNeXt_BottleNeck, self).__init__() 82 | self.conv1 = tf.keras.layers.Conv2D(filters=filters, 83 | kernel_size=(1, 1), 84 | strides=1, 85 | padding="same") 86 | self.bn1 = tf.keras.layers.BatchNormalization() 87 | self.group_conv = GroupConv2D(input_channels=filters, 88 | output_channels=filters, 89 | kernel_size=(3, 3), 90 | strides=strides, 91 | padding="same", 92 | groups=groups) 93 | self.bn2 = tf.keras.layers.BatchNormalization() 94 | self.conv2 = tf.keras.layers.Conv2D(filters=2 * filters, 95 | kernel_size=(1, 1), 96 | strides=1, 97 | padding="same") 98 | self.bn3 = tf.keras.layers.BatchNormalization() 99 | self.shortcut_conv = tf.keras.layers.Conv2D(filters=2 * filters, 100 | kernel_size=(1, 1), 101 | strides=strides, 102 | padding="same") 103 | self.shortcut_bn = tf.keras.layers.BatchNormalization() 104 | 105 | def call(self, inputs, training=None, **kwargs): 106 | x = self.conv1(inputs) 107 | x = self.bn1(x, training=training) 108 | x = tf.nn.relu(x) 109 | x = self.group_conv(x) 110 | x = self.bn2(x, training=training) 111 | x = tf.nn.relu(x) 112 | x = self.conv2(x) 113 | x = self.bn3(x, training=training) 114 | x = tf.nn.relu(x) 115 | 116 | shortcut = self.shortcut_conv(inputs) 117 | shortcut = self.shortcut_bn(shortcut, training=training) 118 | 119 | output = tf.nn.relu(tf.keras.layers.add([x, shortcut])) 120 | return output 121 | 122 | 123 | def build_ResNeXt_block(filters, strides, groups, repeat_num): 124 | block = tf.keras.Sequential() 125 | block.add(ResNeXt_BottleNeck(filters=filters, 126 | strides=strides, 127 | groups=groups)) 128 | for _ in range(1, repeat_num): 129 | block.add(ResNeXt_BottleNeck(filters=filters, 130 | strides=1, 131 | groups=groups)) 132 | 133 | return block --------------------------------------------------------------------------------