Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port MobileNet #2049

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions keras_hub/api/layers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
EfficientNetImageConverter,
)
from keras_hub.src.models.mit.mit_image_converter import MiTImageConverter
from keras_hub.src.models.mobilenet.mobilenet_image_converter import (
MobileNetImageConverter,
)
from keras_hub.src.models.pali_gemma.pali_gemma_image_converter import (
PaliGemmaImageConverter,
)
Expand Down
3 changes: 3 additions & 0 deletions keras_hub/api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@
from keras_hub.src.models.mobilenet.mobilenet_image_classifier import (
MobileNetImageClassifier,
)
from keras_hub.src.models.mobilenet.mobilenet_image_classifier_preprocessor import (
MobileNetImageClassifierPreprocessor,
)
from keras_hub.src.models.opt.opt_backbone import OPTBackbone
from keras_hub.src.models.opt.opt_causal_lm import OPTCausalLM
from keras_hub.src.models.opt.opt_causal_lm_preprocessor import (
Expand Down
57 changes: 57 additions & 0 deletions keras_hub/src/models/mobilenet/conv_bn_act_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import keras

BN_EPSILON = 1e-5
BN_MOMENTUM = 0.9
BN_AXIS = 3


class ConvBnActBlock(keras.layers.Layer):
def __init__(
self,
filter,
activation,
name=None,
**kwargs,
):
super().__init__(**kwargs)
self.filter = filter
self.activation = activation
self.name = name

channel_axis = (
-1 if keras.config.image_data_format() == "channels_last" else 1
)
self.conv = keras.layers.Conv2D(
filter,
kernel_size=1,
data_format=keras.config.image_data_format(),
use_bias=False,
name=f"{name}_conv",
)
self.bn = keras.layers.BatchNormalization(
axis=channel_axis,
epsilon=BN_EPSILON,
momentum=BN_MOMENTUM,
name=f"{name}_bn",
)
self.act = keras.layers.Activation(activation)

def build(self, input_shape):
if self.name is None:
self.name = keras.backend.get_uid("block0")

def call(self, inputs):
x = self.conv(inputs)
x = self.bn(x)
x = self.act(x)
return x

def get_config(self):
config = {
"filter": self.filter,
"activation": self.activation,
"name": self.name,
}

base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
134 changes: 134 additions & 0 deletions keras_hub/src/models/mobilenet/depthwise_conv_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import keras

from keras_hub.src.models.mobilenet.squeeze_and_excite_2d import (
SqueezeAndExcite2D,
)
from keras_hub.src.models.mobilenet.util import adjust_channels

BN_EPSILON = 1e-5
BN_MOMENTUM = 0.9
BN_AXIS = 3


class DepthwiseConvBlock(keras.layers.Layer):
"""
A depthwise convolution block consists of a depthwise conv,
batch normalization, relu6, pointwise convolution,
batch normalization and relu6 activation.

Args:
x: Input tensor of shape `(rows, cols, channels)
filters: Integer, the dimensionality of the output space
(i.e. the number of output filters in the pointwise convolution).
strides: An integer or tuple/list of 2 integers, specifying the strides
of the convolution along the width and height.
Can be a single integer to specify the same value for
all spatial dimensions. Specifying any stride value != 1 is
incompatible with specifying any `dilation_rate` value != 1.
block_id: Integer, a unique identification designating the block number.

Input shape:
4D tensor with shape: `(batch, rows, cols, channels)` in "channels_last"
4D tensor with shape: `(batch, channels, rows, cols)` in
"channels_first"
Returns:
Output tensor of block.
"""

def __init__(
self,
infilters,
filters,
kernel_size=3,
stride=2,
se=None,
name=None,
**kwargs,
):
super().__init__(**kwargs)
self.infilters = infilters
self.filters = filters
self.kernel_size = kernel_size
self.stride = stride
self.se = se
self.name = name

channel_axis = (
-1 if keras.config.image_data_format() == "channels_last" else 1
)
self.name = name = f"{name}_0"

self.pad = keras.layers.ZeroPadding2D(
padding=(1, 1),
name=f"{name}_pad",
)
self.conv1 = keras.layers.Conv2D(
infilters,
kernel_size,
strides=stride,
padding="valid",
data_format=keras.config.image_data_format(),
groups=infilters,
use_bias=False,
name=f"{name}_conv1",
)
self.bn1 = keras.layers.BatchNormalization(
axis=channel_axis,
epsilon=BN_EPSILON,
momentum=BN_MOMENTUM,
name=f"{name}_bn1",
)
self.act1 = keras.layers.ReLU()

if se:
self.se_layer = SqueezeAndExcite2D(
filters=infilters,
bottleneck_filters=adjust_channels(infilters * se),
squeeze_activation="relu",
excite_activation=keras.activations.hard_sigmoid,
name=f"{name}_se",
)

self.conv2 = keras.layers.Conv2D(
filters,
kernel_size=1,
data_format=keras.config.image_data_format(),
use_bias=False,
name=f"{name}_conv2",
)
self.bn2 = keras.layers.BatchNormalization(
axis=channel_axis,
epsilon=BN_EPSILON,
momentum=BN_MOMENTUM,
name=f"{name}_bn2",
)

def build(self, input_shape):
if self.name is None:
self.name = keras.backend.get_uid("block0")

def call(self, inputs):
x = self.pad(inputs)
x = self.conv1(x)
x = self.bn1(x)
x = self.act1(x)

if self.se_layer:
x = self.se_layer(x)

x = self.conv2(x)
x = self.bn2(x)
return x

def get_config(self):
config = {
"infilters": self.infilters,
"filters": self.filters,
"kernel_size": self.kernel_size,
"stride": self.stride,
"se": self.se,
"name": self.name,
}

base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
162 changes: 162 additions & 0 deletions keras_hub/src/models/mobilenet/inverted_residual_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import keras

from keras_hub.src.models.mobilenet.squeeze_and_excite_2d import (
SqueezeAndExcite2D,
)
from keras_hub.src.models.mobilenet.util import adjust_channels

BN_EPSILON = 1e-5
BN_MOMENTUM = 0.9
BN_AXIS = 3


class InvertedResidualBlock(keras.layers.Layer):
"""An Inverted Residual Block.

Args:
expansion: integer, the expansion ratio, multiplied with infilters to
get the minimum value passed to adjust_channels.
filters: integer, number of filters for convolution layer.
kernel_size: integer, the kernel size for DepthWise Convolutions.
stride: integer, the stride length for DepthWise Convolutions.
se_ratio: float, ratio for bottleneck filters. Number of bottleneck
filters = filters * se_ratio.
activation: the activation layer to use.
padding: padding in the conv2d layer
name: string, block label.

Returns:
the updated input tensor.
"""

def __init__(
self,
expansion,
infilters,
filters,
kernel_size,
stride,
se_ratio,
activation,
padding,
name=None,
**kwargs,
):
super().__init__(**kwargs)
self.expansion = expansion
self.infilters = infilters
self.filters = filters
self.kernel_size = kernel_size
self.stride = stride
self.se_ratio = se_ratio
self.activation = activation
self.padding = padding
self.name = name

channel_axis = (
-1 if keras.config.image_data_format() == "channels_last" else 1
)
expanded_channels = adjust_channels(expansion)

self.conv1 = keras.layers.Conv2D(
expanded_channels,
kernel_size=1,
data_format=keras.config.image_data_format(),
use_bias=False,
name=f"{name}_conv1",
)

self.bn1 = keras.layers.BatchNormalization(
axis=channel_axis,
epsilon=BN_EPSILON,
momentum=BN_MOMENTUM,
name=f"{name}_bn1",
)

self.act1 = keras.layers.Activation(activation=activation)

self.pad = keras.layers.ZeroPadding2D(
padding=(padding, padding),
name=f"{name}_pad",
)

self.conv2 = keras.layers.Conv2D(
expanded_channels,
kernel_size,
strides=stride,
padding="valid",
groups=expanded_channels,
data_format=keras.config.image_data_format(),
use_bias=False,
name=f"{name}_conv2",
)
self.bn2 = keras.layers.BatchNormalization(
axis=channel_axis,
epsilon=BN_EPSILON,
momentum=BN_MOMENTUM,
name=f"{name}_bn2",
)

self.act2 = keras.layers.Activation(activation=activation)

self.se = None
if self.se_ratio:
se_filters = expanded_channels
self.se = SqueezeAndExcite2D(
filters=se_filters,
bottleneck_filters=adjust_channels(se_filters * se_ratio),
squeeze_activation="relu",
excite_activation=keras.activations.hard_sigmoid,
name=f"{name}_se",
)

self.conv3 = keras.layers.Conv2D(
filters,
kernel_size=1,
data_format=keras.config.image_data_format(),
use_bias=False,
name=f"{name}_conv3",
)
self.bn3 = keras.layers.BatchNormalization(
axis=channel_axis,
epsilon=BN_EPSILON,
momentum=BN_MOMENTUM,
name=f"{name}_bn3",
)

def build(self, input_shape):
if self.name is None:
self.name = keras.backend.get_uid("block0")

def call(self, inputs):
x = inputs
x = self.conv1(x)
x = self.bn1(x)
x = self.act1(x)
x = self.pad(x)
x = self.conv2(x)
x = self.bn2(x)
x = self.act2(x)
if self.se:
x = self.se(x)
x = self.conv3(x)
x = self.bn3(x)
if self.stride == 1 and self.infilters == self.filters:
x = inputs + x
return x

def get_config(self):
config = {
"expansion": self.expansion,
"infilters": self.infilters,
"filters": self.filters,
"kernel_size": self.kernel_size,
"stride": self.stride,
"se_ratio": self.se_ratio,
"activation": self.activation,
"padding": self.padding,
"name": self.name,
}

base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
Loading
Loading