Spaces:
Build error
Build error
# BSD 3-Clause License | |
# Copyright (c) Soumith Chintala 2016, | |
# All rights reserved. | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions are met: | |
# * Redistributions of source code must retain the above copyright notice, this | |
# list of conditions and the following disclaimer. | |
# * Redistributions in binary form must reproduce the above copyright notice, | |
# this list of conditions and the following disclaimer in the documentation | |
# and/or other materials provided with the distribution. | |
# * Neither the name of the copyright holder nor the names of its | |
# contributors may be used to endorse or promote products derived from | |
# this software without specific prior written permission. | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
# Adaptation of the PyTorch torchvision MobileNetV2 without a classifier. | |
# See source here: https://pytorch.org/vision/0.8/_modules/torchvision/models/mobilenet.html#mobilenet_v2 | |
from torch import nn | |
def _make_divisible(v, divisor, min_value=None): | |
""" | |
This function is taken from the original tf repo. | |
It ensures that all layers have a channel number that is divisible by 8 | |
It can be seen here: | |
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py | |
:param v: | |
:param divisor: | |
:param min_value: | |
:return: | |
""" | |
if min_value is None: | |
min_value = divisor | |
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) | |
# Make sure that round down does not go down by more than 10%. | |
if new_v < 0.9 * v: | |
new_v += divisor | |
return new_v | |
class ConvBNReLU(nn.Sequential): | |
def __init__( | |
self, in_planes, out_planes, kernel_size=3, stride=1, groups=1, norm_layer=None | |
): | |
padding = (kernel_size - 1) // 2 | |
if norm_layer is None: | |
norm_layer = nn.BatchNorm2d | |
super(ConvBNReLU, self).__init__( | |
nn.Conv2d( | |
in_planes, | |
out_planes, | |
kernel_size, | |
stride, | |
padding, | |
groups=groups, | |
bias=False, | |
), | |
norm_layer(out_planes), | |
nn.ReLU6(inplace=True), | |
) | |
class InvertedResidual(nn.Module): | |
def __init__(self, inp, oup, stride, expand_ratio, norm_layer=None): | |
super(InvertedResidual, self).__init__() | |
self.stride = stride | |
assert stride in [1, 2] | |
if norm_layer is None: | |
norm_layer = nn.BatchNorm2d | |
hidden_dim = int(round(inp * expand_ratio)) | |
self.use_res_connect = self.stride == 1 and inp == oup | |
layers = [] | |
if expand_ratio != 1: | |
# pw | |
layers.append( | |
ConvBNReLU(inp, hidden_dim, kernel_size=1, norm_layer=norm_layer) | |
) | |
layers.extend( | |
[ | |
# dw | |
ConvBNReLU( | |
hidden_dim, | |
hidden_dim, | |
stride=stride, | |
groups=hidden_dim, | |
norm_layer=norm_layer, | |
), | |
# pw-linear | |
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), | |
norm_layer(oup), | |
] | |
) | |
self.conv = nn.Sequential(*layers) | |
def forward(self, x): | |
if self.use_res_connect: | |
return x + self.conv(x) | |
else: | |
return self.conv(x) | |
class MobileNetV2(nn.Module): | |
def __init__( | |
self, | |
embed_dim=1028, | |
width_mult=1.0, | |
inverted_residual_setting=None, | |
round_nearest=8, | |
block=None, | |
norm_layer=None, | |
): | |
""" | |
MobileNet V2 main class | |
Args: | |
embed_dim (int): Number of channels in the final output. | |
width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount | |
inverted_residual_setting: Network structure | |
round_nearest (int): Round the number of channels in each layer to be a multiple of this number | |
Set to 1 to turn off rounding | |
block: Module specifying inverted residual building block for mobilenet | |
norm_layer: Module specifying the normalization layer to use | |
""" | |
super(MobileNetV2, self).__init__() | |
if block is None: | |
block = InvertedResidual | |
if norm_layer is None: | |
norm_layer = nn.BatchNorm2d | |
input_channel = 32 | |
last_channel = embed_dim / width_mult | |
if inverted_residual_setting is None: | |
inverted_residual_setting = [ | |
# t, c, n, s | |
[1, 16, 1, 1], | |
[6, 24, 2, 2], | |
[6, 32, 3, 2], | |
[6, 64, 4, 2], | |
[6, 96, 3, 1], | |
[6, 160, 3, 2], | |
[6, 320, 1, 1], | |
] | |
# only check the first element, assuming user knows t,c,n,s are required | |
if ( | |
len(inverted_residual_setting) == 0 | |
or len(inverted_residual_setting[0]) != 4 | |
): | |
raise ValueError( | |
"inverted_residual_setting should be non-empty " | |
"or a 4-element list, got {}".format(inverted_residual_setting) | |
) | |
# building first layer | |
input_channel = _make_divisible(input_channel * width_mult, round_nearest) | |
self.last_channel = _make_divisible( | |
last_channel * max(1.0, width_mult), round_nearest | |
) | |
features = [ConvBNReLU(3, input_channel, stride=2, norm_layer=norm_layer)] | |
# building inverted residual blocks | |
for t, c, n, s in inverted_residual_setting: | |
output_channel = _make_divisible(c * width_mult, round_nearest) | |
for i in range(n): | |
stride = s if i == 0 else 1 | |
features.append( | |
block( | |
input_channel, | |
output_channel, | |
stride, | |
expand_ratio=t, | |
norm_layer=norm_layer, | |
) | |
) | |
input_channel = output_channel | |
# building last several layers | |
features.append( | |
ConvBNReLU( | |
input_channel, self.last_channel, kernel_size=1, norm_layer=norm_layer | |
) | |
) | |
# make it nn.Sequential | |
self.features = nn.Sequential(*features) | |
# weight initialization | |
for m in self.modules(): | |
if isinstance(m, nn.Conv2d): | |
nn.init.kaiming_normal_(m.weight, mode="fan_out") | |
if m.bias is not None: | |
nn.init.zeros_(m.bias) | |
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): | |
nn.init.ones_(m.weight) | |
nn.init.zeros_(m.bias) | |
elif isinstance(m, nn.Linear): | |
nn.init.normal_(m.weight, 0, 0.01) | |
nn.init.zeros_(m.bias) | |
def _forward_impl(self, x): | |
# This exists since TorchScript doesn't support inheritance, so the superclass method | |
# (this one) needs to have a name other than `forward` that can be accessed in a subclass | |
return self.features(x) | |
# return the features directly, no classifier or pooling | |
def forward(self, x): | |
return self._forward_impl(x) | |