├── .gitignore ├── LICENSE ├── README.md ├── mobilenet_v3_block.py ├── mobilenet_v3_large.py └── mobilenet_v3_small.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 | # MobileNetV3_TensorFlow2 2 | A tensorflow2 implementation of MobileNet_V3. 3 | 4 | See https://github.com/calmisential/Basic_CNNs_TensorFlow2 for training details. 5 | 6 | ## References: 7 | 1. The original paper: [Searching for MobileNetV3](https://arxiv.org/abs/1905.02244) 8 | -------------------------------------------------------------------------------- /mobilenet_v3_block.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | 4 | def h_sigmoid(x): 5 | return tf.nn.relu6(x + 3) / 6 6 | 7 | 8 | def h_swish(x): 9 | return x * h_sigmoid(x) 10 | 11 | 12 | class SEBlock(tf.keras.layers.Layer): 13 | def __init__(self, input_channels, r=16): 14 | super(SEBlock, self).__init__() 15 | self.pool = tf.keras.layers.GlobalAveragePooling2D() 16 | self.fc1 = tf.keras.layers.Dense(units=input_channels // r) 17 | self.fc2 = tf.keras.layers.Dense(units=input_channels) 18 | 19 | def call(self, inputs, **kwargs): 20 | branch = self.pool(inputs) 21 | branch = self.fc1(branch) 22 | branch = tf.nn.relu(branch) 23 | branch = self.fc2(branch) 24 | branch = h_sigmoid(branch) 25 | branch = tf.expand_dims(input=branch, axis=1) 26 | branch = tf.expand_dims(input=branch, axis=1) 27 | output = inputs * branch 28 | return output 29 | 30 | 31 | class BottleNeck(tf.keras.layers.Layer): 32 | def __init__(self, in_size, exp_size, out_size, s, is_se_existing, NL, k): 33 | super(BottleNeck, self).__init__() 34 | self.stride = s 35 | self.in_size = in_size 36 | self.out_size = out_size 37 | self.is_se_existing = is_se_existing 38 | self.NL = NL 39 | self.conv1 = tf.keras.layers.Conv2D(filters=exp_size, 40 | kernel_size=(1, 1), 41 | strides=1, 42 | padding="same") 43 | self.bn1 = tf.keras.layers.BatchNormalization() 44 | self.dwconv = tf.keras.layers.DepthwiseConv2D(kernel_size=(k, k), 45 | strides=s, 46 | padding="same") 47 | self.bn2 = tf.keras.layers.BatchNormalization() 48 | self.se = SEBlock(input_channels=exp_size) 49 | self.conv2 = tf.keras.layers.Conv2D(filters=out_size, 50 | kernel_size=(1, 1), 51 | strides=1, 52 | padding="same") 53 | self.bn3 = tf.keras.layers.BatchNormalization() 54 | self.linear = tf.keras.layers.Activation(tf.keras.activations.linear) 55 | 56 | def call(self, inputs, training=None, **kwargs): 57 | x = self.conv1(inputs) 58 | x = self.bn1(x, training=training) 59 | if self.NL == "HS": 60 | x = h_swish(x) 61 | elif self.NL == "RE": 62 | x = tf.nn.relu6(x) 63 | x = self.dwconv(x) 64 | x = self.bn2(x, training=training) 65 | if self.NL == "HS": 66 | x = h_swish(x) 67 | elif self.NL == "RE": 68 | x = tf.nn.relu6(x) 69 | if self.is_se_existing: 70 | x = self.se(x) 71 | x = self.conv2(x) 72 | x = self.bn3(x, training=training) 73 | x = self.linear(x) 74 | 75 | if self.stride == 1 and self.in_size == self.out_size: 76 | x = tf.keras.layers.add([x, inputs]) 77 | 78 | return x 79 | -------------------------------------------------------------------------------- /mobilenet_v3_large.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from mobilenet_v3_block import BottleNeck, h_swish 3 | 4 | NUM_CLASSES = 10 5 | 6 | 7 | class MobileNetV3Large(tf.keras.Model): 8 | def __init__(self): 9 | super(MobileNetV3Large, self).__init__() 10 | self.conv1 = tf.keras.layers.Conv2D(filters=16, 11 | kernel_size=(3, 3), 12 | strides=2, 13 | padding="same") 14 | self.bn1 = tf.keras.layers.BatchNormalization() 15 | self.bneck1 = BottleNeck(in_size=16, exp_size=16, out_size=16, s=1, is_se_existing=False, NL="RE", k=3) 16 | self.bneck2 = BottleNeck(in_size=16, exp_size=64, out_size=24, s=2, is_se_existing=False, NL="RE", k=3) 17 | self.bneck3 = BottleNeck(in_size=24, exp_size=72, out_size=24, s=1, is_se_existing=False, NL="RE", k=3) 18 | self.bneck4 = BottleNeck(in_size=24, exp_size=72, out_size=40, s=2, is_se_existing=True, NL="RE", k=5) 19 | self.bneck5 = BottleNeck(in_size=40, exp_size=120, out_size=40, s=1, is_se_existing=True, NL="RE", k=5) 20 | self.bneck6 = BottleNeck(in_size=40, exp_size=120, out_size=40, s=1, is_se_existing=True, NL="RE", k=5) 21 | self.bneck7 = BottleNeck(in_size=40, exp_size=240, out_size=80, s=2, is_se_existing=False, NL="HS", k=3) 22 | self.bneck8 = BottleNeck(in_size=80, exp_size=200, out_size=80, s=1, is_se_existing=False, NL="HS", k=3) 23 | self.bneck9 = BottleNeck(in_size=80, exp_size=184, out_size=80, s=1, is_se_existing=False, NL="HS", k=3) 24 | self.bneck10 = BottleNeck(in_size=80, exp_size=184, out_size=80, s=1, is_se_existing=False, NL="HS", k=3) 25 | self.bneck11 = BottleNeck(in_size=80, exp_size=480, out_size=112, s=1, is_se_existing=True, NL="HS", k=3) 26 | self.bneck12 = BottleNeck(in_size=112, exp_size=672, out_size=112, s=1, is_se_existing=True, NL="HS", k=3) 27 | self.bneck13 = BottleNeck(in_size=112, exp_size=672, out_size=160, s=2, is_se_existing=True, NL="HS", k=5) 28 | self.bneck14 = BottleNeck(in_size=160, exp_size=960, out_size=160, s=1, is_se_existing=True, NL="HS", k=5) 29 | self.bneck15 = BottleNeck(in_size=160, exp_size=960, out_size=160, s=1, is_se_existing=True, NL="HS", k=5) 30 | 31 | self.conv2 = tf.keras.layers.Conv2D(filters=960, 32 | kernel_size=(1, 1), 33 | strides=1, 34 | padding="same") 35 | self.bn2 = tf.keras.layers.BatchNormalization() 36 | self.avgpool = tf.keras.layers.AveragePooling2D(pool_size=(7, 7), 37 | strides=1) 38 | self.conv3 = tf.keras.layers.Conv2D(filters=1280, 39 | kernel_size=(1, 1), 40 | strides=1, 41 | padding="same") 42 | self.conv4 = tf.keras.layers.Conv2D(filters=NUM_CLASSES, 43 | kernel_size=(1, 1), 44 | strides=1, 45 | padding="same", 46 | activation=tf.keras.activations.softmax) 47 | 48 | def call(self, inputs, training=None, mask=None): 49 | x = self.conv1(inputs) 50 | x = self.bn1(x, training=training) 51 | x = h_swish(x) 52 | 53 | x = self.bneck1(x, training=training) 54 | x = self.bneck2(x, training=training) 55 | x = self.bneck3(x, training=training) 56 | x = self.bneck4(x, training=training) 57 | x = self.bneck5(x, training=training) 58 | x = self.bneck6(x, training=training) 59 | x = self.bneck7(x, training=training) 60 | x = self.bneck8(x, training=training) 61 | x = self.bneck9(x, training=training) 62 | x = self.bneck10(x, training=training) 63 | x = self.bneck11(x, training=training) 64 | x = self.bneck12(x, training=training) 65 | x = self.bneck13(x, training=training) 66 | x = self.bneck14(x, training=training) 67 | x = self.bneck15(x, training=training) 68 | 69 | x = self.conv2(x) 70 | x = self.bn2(x, training=training) 71 | x = h_swish(x) 72 | x = self.avgpool(x) 73 | x = self.conv3(x) 74 | x = h_swish(x) 75 | x = self.conv4(x) 76 | 77 | return x 78 | 79 | 80 | if __name__ == '__main__': 81 | model = MobileNetV3Large() 82 | model.build(input_shape=(None, 224, 224, 3)) 83 | model.summary() 84 | -------------------------------------------------------------------------------- /mobilenet_v3_small.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from mobilenet_v3_block import BottleNeck, h_swish 3 | 4 | NUM_CLASSES = 10 5 | 6 | 7 | class MobileNetV3Small(tf.keras.Model): 8 | def __init__(self): 9 | super(MobileNetV3Small, self).__init__() 10 | self.conv1 = tf.keras.layers.Conv2D(filters=16, 11 | kernel_size=(3, 3), 12 | strides=2, 13 | padding="same") 14 | self.bn1 = tf.keras.layers.BatchNormalization() 15 | self.bneck1 = BottleNeck(in_size=16, exp_size=16, out_size=16, s=2, is_se_existing=True, NL="RE", k=3) 16 | self.bneck2 = BottleNeck(in_size=16, exp_size=72, out_size=24, s=2, is_se_existing=False, NL="RE", k=3) 17 | self.bneck3 = BottleNeck(in_size=24, exp_size=88, out_size=24, s=1, is_se_existing=False, NL="RE", k=3) 18 | self.bneck4 = BottleNeck(in_size=24, exp_size=96, out_size=40, s=2, is_se_existing=True, NL="HS", k=5) 19 | self.bneck5 = BottleNeck(in_size=40, exp_size=240, out_size=40, s=1, is_se_existing=True, NL="HS", k=5) 20 | self.bneck6 = BottleNeck(in_size=40, exp_size=240, out_size=40, s=1, is_se_existing=True, NL="HS", k=5) 21 | self.bneck7 = BottleNeck(in_size=40, exp_size=120, out_size=48, s=1, is_se_existing=True, NL="HS", k=5) 22 | self.bneck8 = BottleNeck(in_size=48, exp_size=144, out_size=48, s=1, is_se_existing=True, NL="HS", k=5) 23 | self.bneck9 = BottleNeck(in_size=48, exp_size=288, out_size=96, s=2, is_se_existing=True, NL="HS", k=5) 24 | self.bneck10 = BottleNeck(in_size=96, exp_size=576, out_size=96, s=1, is_se_existing=True, NL="HS", k=5) 25 | self.bneck11 = BottleNeck(in_size=96, exp_size=576, out_size=96, s=1, is_se_existing=True, NL="HS", k=5) 26 | 27 | self.conv2 = tf.keras.layers.Conv2D(filters=576, 28 | kernel_size=(1, 1), 29 | strides=1, 30 | padding="same") 31 | self.bn2 = tf.keras.layers.BatchNormalization() 32 | self.avgpool = tf.keras.layers.AveragePooling2D(pool_size=(7, 7), 33 | strides=1) 34 | self.conv3 = tf.keras.layers.Conv2D(filters=1280, 35 | kernel_size=(1, 1), 36 | strides=1, 37 | padding="same") 38 | self.conv4 = tf.keras.layers.Conv2D(filters=NUM_CLASSES, 39 | kernel_size=(1, 1), 40 | strides=1, 41 | padding="same", 42 | activation=tf.keras.activations.softmax) 43 | 44 | def call(self, inputs, training=None, mask=None): 45 | x = self.conv1(inputs) 46 | x = self.bn1(x, training=training) 47 | x = h_swish(x) 48 | 49 | x = self.bneck1(x, training=training) 50 | x = self.bneck2(x, training=training) 51 | x = self.bneck3(x, training=training) 52 | x = self.bneck4(x, training=training) 53 | x = self.bneck5(x, training=training) 54 | x = self.bneck6(x, training=training) 55 | x = self.bneck7(x, training=training) 56 | x = self.bneck8(x, training=training) 57 | x = self.bneck9(x, training=training) 58 | x = self.bneck10(x, training=training) 59 | x = self.bneck11(x, training=training) 60 | 61 | x = self.conv2(x) 62 | x = self.bn2(x, training=training) 63 | x = h_swish(x) 64 | x = self.avgpool(x) 65 | x = self.conv3(x) 66 | x = h_swish(x) 67 | x = self.conv4(x) 68 | 69 | return x 70 | 71 | 72 | if __name__ == '__main__': 73 | model = MobileNetV3Small() 74 | model.build(input_shape=(None, 224, 224, 3)) 75 | model.summary() 76 | 77 | --------------------------------------------------------------------------------