first commit
This commit is contained in:
commit
54ccfb11fe
218 files changed
+19525
No files matched your search
Executable
+10
@@ -0,0 +1,10 @@
|
||||
from ssd.modeling import registry
|
||||
from .vgg import VGG
|
||||
from .mobilenet import MobileNetV2
|
||||
from .efficient_net import EfficientNet
|
||||
|
||||
__all__ = ['build_backbone', 'VGG', 'MobileNetV2', 'EfficientNet']
|
||||
|
||||
|
||||
def build_backbone(cfg):
|
||||
return registry.BACKBONES[cfg.MODEL.BACKBONE.NAME](cfg, cfg.MODEL.BACKBONE.PRETRAINED)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
from ssd.modeling import registry
|
||||
from .efficient_net import EfficientNet
|
||||
|
||||
__all__ = ['efficient_net_b3', 'EfficientNet']
|
||||
|
||||
|
||||
@registry.BACKBONES.register('efficient_net-b3')
|
||||
def efficient_net_b3(cfg, pretrained=True):
|
||||
if pretrained:
|
||||
model = EfficientNet.from_pretrained('efficientnet-b3')
|
||||
else:
|
||||
model = EfficientNet.from_name('efficientnet-b3')
|
||||
|
||||
return model
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
from .utils import (
|
||||
relu_fn,
|
||||
round_filters,
|
||||
round_repeats,
|
||||
drop_connect,
|
||||
Conv2dSamePadding,
|
||||
get_model_params,
|
||||
efficientnet_params,
|
||||
load_pretrained_weights,
|
||||
)
|
||||
|
||||
INDICES = {
|
||||
'efficientnet-b3': [7, 17, 25]
|
||||
}
|
||||
|
||||
EXTRAS = {
|
||||
'efficientnet-b3': [
|
||||
# in, out, k, s, p
|
||||
[(384, 128, 1, 1, 0), (128, 256, 3, 2, 1)], # 5 x 5
|
||||
[(256, 128, 1, 1, 0), (128, 256, 3, 1, 0)], # 3 x 3
|
||||
[(256, 128, 1, 1, 0), (128, 256, 3, 1, 0)], # 1 x 1
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def add_extras(cfgs):
|
||||
extras = nn.ModuleList()
|
||||
for cfg in cfgs:
|
||||
extra = []
|
||||
for prams in cfg:
|
||||
in_channels, out_channels, kernel_size, stride, padding = prams
|
||||
extra.append(nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding))
|
||||
extra.append(nn.ReLU())
|
||||
extras.append(nn.Sequential(*extra))
|
||||
return extras
|
||||
|
||||
|
||||
class MBConvBlock(nn.Module):
|
||||
"""
|
||||
Mobile Inverted Residual Bottleneck Block
|
||||
|
||||
Args:
|
||||
block_args (namedtuple): BlockArgs, see above
|
||||
global_params (namedtuple): GlobalParam, see above
|
||||
|
||||
Attributes:
|
||||
has_se (bool): Whether the block contains a Squeeze and Excitation layer.
|
||||
"""
|
||||
|
||||
def __init__(self, block_args, global_params):
|
||||
super().__init__()
|
||||
self._block_args = block_args
|
||||
self._bn_mom = 1 - global_params.batch_norm_momentum
|
||||
self._bn_eps = global_params.batch_norm_epsilon
|
||||
self.has_se = (self._block_args.se_ratio is not None) and (0 < self._block_args.se_ratio <= 1)
|
||||
self.id_skip = block_args.id_skip # skip connection and drop connect
|
||||
|
||||
# Expansion phase
|
||||
inp = self._block_args.input_filters # number of input channels
|
||||
oup = self._block_args.input_filters * self._block_args.expand_ratio # number of output channels
|
||||
if self._block_args.expand_ratio != 1:
|
||||
self._expand_conv = Conv2dSamePadding(in_channels=inp, out_channels=oup, kernel_size=1, bias=False)
|
||||
self._bn0 = nn.BatchNorm2d(num_features=oup, momentum=self._bn_mom, eps=self._bn_eps)
|
||||
|
||||
# Depthwise convolution phase
|
||||
k = self._block_args.kernel_size
|
||||
s = self._block_args.stride
|
||||
self._depthwise_conv = Conv2dSamePadding(
|
||||
in_channels=oup, out_channels=oup, groups=oup, # groups makes it depthwise
|
||||
kernel_size=k, stride=s, bias=False)
|
||||
self._bn1 = nn.BatchNorm2d(num_features=oup, momentum=self._bn_mom, eps=self._bn_eps)
|
||||
|
||||
# Squeeze and Excitation layer, if desired
|
||||
if self.has_se:
|
||||
num_squeezed_channels = max(1, int(self._block_args.input_filters * self._block_args.se_ratio))
|
||||
self._se_reduce = Conv2dSamePadding(in_channels=oup, out_channels=num_squeezed_channels, kernel_size=1)
|
||||
self._se_expand = Conv2dSamePadding(in_channels=num_squeezed_channels, out_channels=oup, kernel_size=1)
|
||||
|
||||
# Output phase
|
||||
final_oup = self._block_args.output_filters
|
||||
self._project_conv = Conv2dSamePadding(in_channels=oup, out_channels=final_oup, kernel_size=1, bias=False)
|
||||
self._bn2 = nn.BatchNorm2d(num_features=final_oup, momentum=self._bn_mom, eps=self._bn_eps)
|
||||
|
||||
def forward(self, inputs, drop_connect_rate=None):
|
||||
"""
|
||||
:param inputs: input tensor
|
||||
:param drop_connect_rate: drop connect rate (float, between 0 and 1)
|
||||
:return: output of block
|
||||
"""
|
||||
|
||||
# Expansion and Depthwise Convolution
|
||||
x = inputs
|
||||
if self._block_args.expand_ratio != 1:
|
||||
x = relu_fn(self._bn0(self._expand_conv(inputs)))
|
||||
x = relu_fn(self._bn1(self._depthwise_conv(x)))
|
||||
|
||||
# Squeeze and Excitation
|
||||
if self.has_se:
|
||||
x_squeezed = F.adaptive_avg_pool2d(x, 1)
|
||||
x_squeezed = self._se_expand(relu_fn(self._se_reduce(x_squeezed)))
|
||||
x = torch.sigmoid(x_squeezed) * x
|
||||
|
||||
x = self._bn2(self._project_conv(x))
|
||||
|
||||
# Skip connection and drop connect
|
||||
input_filters, output_filters = self._block_args.input_filters, self._block_args.output_filters
|
||||
if self.id_skip and self._block_args.stride == 1 and input_filters == output_filters:
|
||||
if drop_connect_rate:
|
||||
x = drop_connect(x, p=drop_connect_rate, training=self.training)
|
||||
x = x + inputs # skip connection
|
||||
return x
|
||||
|
||||
|
||||
class EfficientNet(nn.Module):
|
||||
"""
|
||||
An EfficientNet model. Most easily loaded with the .from_name or .from_pretrained methods
|
||||
|
||||
Args:
|
||||
blocks_args (list): A list of BlockArgs to construct blocks
|
||||
global_params (namedtuple): A set of GlobalParams shared between blocks
|
||||
|
||||
Example:
|
||||
model = EfficientNet.from_pretrained('efficientnet-b0')
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, model_name, blocks_args=None, global_params=None):
|
||||
super().__init__()
|
||||
self.indices = INDICES[model_name]
|
||||
self.extras = add_extras(EXTRAS[model_name])
|
||||
assert isinstance(blocks_args, list), 'blocks_args should be a list'
|
||||
assert len(blocks_args) > 0, 'block args must be greater than 0'
|
||||
self._global_params = global_params
|
||||
self._blocks_args = blocks_args
|
||||
|
||||
# Batch norm parameters
|
||||
bn_mom = 1 - self._global_params.batch_norm_momentum
|
||||
bn_eps = self._global_params.batch_norm_epsilon
|
||||
|
||||
# Stem
|
||||
in_channels = 3 # rgb
|
||||
out_channels = round_filters(32, self._global_params) # number of output channels
|
||||
self._conv_stem = Conv2dSamePadding(in_channels, out_channels, kernel_size=3, stride=2, bias=False)
|
||||
self._bn0 = nn.BatchNorm2d(num_features=out_channels, momentum=bn_mom, eps=bn_eps)
|
||||
|
||||
# Build blocks
|
||||
self._blocks = nn.ModuleList([])
|
||||
for block_args in self._blocks_args:
|
||||
|
||||
# Update block input and output filters based on depth multiplier.
|
||||
block_args = block_args._replace(
|
||||
input_filters=round_filters(block_args.input_filters, self._global_params),
|
||||
output_filters=round_filters(block_args.output_filters, self._global_params),
|
||||
num_repeat=round_repeats(block_args.num_repeat, self._global_params)
|
||||
)
|
||||
|
||||
# The first block needs to take care of stride and filter size increase.
|
||||
self._blocks.append(MBConvBlock(block_args, self._global_params))
|
||||
if block_args.num_repeat > 1:
|
||||
block_args = block_args._replace(input_filters=block_args.output_filters, stride=1)
|
||||
for _ in range(block_args.num_repeat - 1):
|
||||
self._blocks.append(MBConvBlock(block_args, self._global_params))
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
for m in self.extras.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
nn.init.xavier_uniform_(m.weight)
|
||||
nn.init.zeros_(m.bias)
|
||||
|
||||
def extract_features(self, inputs):
|
||||
""" Returns output of the final convolution layer """
|
||||
|
||||
# Stem
|
||||
x = relu_fn(self._bn0(self._conv_stem(inputs)))
|
||||
|
||||
features = []
|
||||
|
||||
# Blocks
|
||||
for idx, block in enumerate(self._blocks):
|
||||
drop_connect_rate = self._global_params.drop_connect_rate
|
||||
if drop_connect_rate:
|
||||
drop_connect_rate *= float(idx) / len(self._blocks)
|
||||
x = block(x, drop_connect_rate)
|
||||
if idx in self.indices:
|
||||
features.append(x)
|
||||
|
||||
return x, features
|
||||
|
||||
def forward(self, inputs):
|
||||
""" Calls extract_features to extract features, applies final linear layer, and returns logits. """
|
||||
|
||||
# Convolution layers
|
||||
x, features = self.extract_features(inputs)
|
||||
|
||||
for layer in self.extras:
|
||||
x = layer(x)
|
||||
features.append(x)
|
||||
|
||||
return tuple(features)
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, model_name, override_params=None):
|
||||
cls._check_model_name_is_valid(model_name)
|
||||
blocks_args, global_params = get_model_params(model_name, override_params)
|
||||
return EfficientNet(model_name, blocks_args, global_params)
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(cls, model_name):
|
||||
model = EfficientNet.from_name(model_name)
|
||||
load_pretrained_weights(model, model_name)
|
||||
return model
|
||||
|
||||
@classmethod
|
||||
def get_image_size(cls, model_name):
|
||||
cls._check_model_name_is_valid(model_name)
|
||||
_, _, res, _ = efficientnet_params(model_name)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _check_model_name_is_valid(cls, model_name, also_need_pretrained_weights=False):
|
||||
""" Validates model name. None that pretrained weights are only available for
|
||||
the first four models (efficientnet-b{i} for i in 0,1,2,3) at the moment. """
|
||||
num_models = 4 if also_need_pretrained_weights else 8
|
||||
valid_models = ['efficientnet_b' + str(i) for i in range(num_models)]
|
||||
if model_name.replace('-', '_') not in valid_models:
|
||||
raise ValueError('model_name should be one of: ' + ', '.join(valid_models))
|
||||
Executable
+247
@@ -0,0 +1,247 @@
|
||||
"""
|
||||
This file contains helper functions for building the model and for loading model parameters.
|
||||
These helper functions are built to mirror those in the official TensorFlow implementation.
|
||||
"""
|
||||
|
||||
import re
|
||||
import math
|
||||
import collections
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
from ssd.utils.model_zoo import load_state_dict_from_url
|
||||
|
||||
########################################################################
|
||||
############### HELPERS FUNCTIONS FOR MODEL ARCHITECTURE ###############
|
||||
########################################################################
|
||||
|
||||
|
||||
# Parameters for the entire model (stem, all blocks, and head)
|
||||
|
||||
GlobalParams = collections.namedtuple('GlobalParams', [
|
||||
'batch_norm_momentum', 'batch_norm_epsilon', 'dropout_rate',
|
||||
'num_classes', 'width_coefficient', 'depth_coefficient',
|
||||
'depth_divisor', 'min_depth', 'drop_connect_rate', ])
|
||||
|
||||
# Parameters for an individual model block
|
||||
BlockArgs = collections.namedtuple('BlockArgs', [
|
||||
'kernel_size', 'num_repeat', 'input_filters', 'output_filters',
|
||||
'expand_ratio', 'id_skip', 'stride', 'se_ratio'])
|
||||
|
||||
# Change namedtuple defaults
|
||||
GlobalParams.__new__.__defaults__ = (None,) * len(GlobalParams._fields)
|
||||
BlockArgs.__new__.__defaults__ = (None,) * len(BlockArgs._fields)
|
||||
|
||||
|
||||
def relu_fn(x):
|
||||
""" Swish activation function """
|
||||
return x * torch.sigmoid(x)
|
||||
|
||||
|
||||
def round_filters(filters, global_params):
|
||||
""" Calculate and round number of filters based on depth multiplier. """
|
||||
multiplier = global_params.width_coefficient
|
||||
if not multiplier:
|
||||
return filters
|
||||
divisor = global_params.depth_divisor
|
||||
min_depth = global_params.min_depth
|
||||
filters *= multiplier
|
||||
min_depth = min_depth or divisor
|
||||
new_filters = max(min_depth, int(filters + divisor / 2) // divisor * divisor)
|
||||
if new_filters < 0.9 * filters: # prevent rounding by more than 10%
|
||||
new_filters += divisor
|
||||
return int(new_filters)
|
||||
|
||||
|
||||
def round_repeats(repeats, global_params):
|
||||
""" Round number of filters based on depth multiplier. """
|
||||
multiplier = global_params.depth_coefficient
|
||||
if not multiplier:
|
||||
return repeats
|
||||
return int(math.ceil(multiplier * repeats))
|
||||
|
||||
|
||||
def drop_connect(inputs, p, training):
|
||||
""" Drop connect. """
|
||||
if not training: return inputs
|
||||
batch_size = inputs.shape[0]
|
||||
keep_prob = 1 - p
|
||||
random_tensor = keep_prob
|
||||
random_tensor += torch.rand([batch_size, 1, 1, 1], dtype=inputs.dtype, device=inputs.device)
|
||||
binary_tensor = torch.floor(random_tensor)
|
||||
output = inputs / keep_prob * binary_tensor
|
||||
return output
|
||||
|
||||
|
||||
class Conv2dSamePadding(nn.Conv2d):
|
||||
""" 2D Convolutions like TensorFlow """
|
||||
|
||||
def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1, groups=1, bias=True):
|
||||
super().__init__(in_channels, out_channels, kernel_size, stride, 0, dilation, groups, bias)
|
||||
self.stride = self.stride if len(self.stride) == 2 else [self.stride[0]] * 2
|
||||
|
||||
def forward(self, x):
|
||||
ih, iw = x.size()[-2:]
|
||||
kh, kw = self.weight.size()[-2:]
|
||||
sh, sw = self.stride
|
||||
oh, ow = math.ceil(ih / sh), math.ceil(iw / sw)
|
||||
pad_h = max((oh - 1) * self.stride[0] + (kh - 1) * self.dilation[0] + 1 - ih, 0)
|
||||
pad_w = max((ow - 1) * self.stride[1] + (kw - 1) * self.dilation[1] + 1 - iw, 0)
|
||||
if pad_h > 0 or pad_w > 0:
|
||||
x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2])
|
||||
return F.conv2d(x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)
|
||||
|
||||
|
||||
########################################################################
|
||||
############## HELPERS FUNCTIONS FOR LOADING MODEL PARAMS ##############
|
||||
########################################################################
|
||||
|
||||
|
||||
def efficientnet_params(model_name):
|
||||
""" Map EfficientNet model name to parameter coefficients. """
|
||||
params_dict = {
|
||||
# Coefficients: width,depth,res,dropout
|
||||
'efficientnet-b0': (1.0, 1.0, 224, 0.2),
|
||||
'efficientnet-b1': (1.0, 1.1, 240, 0.2),
|
||||
'efficientnet-b2': (1.1, 1.2, 260, 0.3),
|
||||
'efficientnet-b3': (1.2, 1.4, 300, 0.3),
|
||||
'efficientnet-b4': (1.4, 1.8, 380, 0.4),
|
||||
'efficientnet-b5': (1.6, 2.2, 456, 0.4),
|
||||
'efficientnet-b6': (1.8, 2.6, 528, 0.5),
|
||||
'efficientnet-b7': (2.0, 3.1, 600, 0.5),
|
||||
}
|
||||
return params_dict[model_name]
|
||||
|
||||
|
||||
class BlockDecoder(object):
|
||||
""" Block Decoder for readability, straight from the official TensorFlow repository """
|
||||
|
||||
@staticmethod
|
||||
def _decode_block_string(block_string):
|
||||
""" Gets a block through a string notation of arguments. """
|
||||
assert isinstance(block_string, str)
|
||||
|
||||
ops = block_string.split('_')
|
||||
options = {}
|
||||
for op in ops:
|
||||
splits = re.split(r'(\d.*)', op)
|
||||
if len(splits) >= 2:
|
||||
key, value = splits[:2]
|
||||
options[key] = value
|
||||
|
||||
# Check stride
|
||||
assert (('s' in options and len(options['s']) == 1) or
|
||||
(len(options['s']) == 2 and options['s'][0] == options['s'][1]))
|
||||
|
||||
return BlockArgs(
|
||||
kernel_size=int(options['k']),
|
||||
num_repeat=int(options['r']),
|
||||
input_filters=int(options['i']),
|
||||
output_filters=int(options['o']),
|
||||
expand_ratio=int(options['e']),
|
||||
id_skip=('noskip' not in block_string),
|
||||
se_ratio=float(options['se']) if 'se' in options else None,
|
||||
stride=[int(options['s'][0])])
|
||||
|
||||
@staticmethod
|
||||
def _encode_block_string(block):
|
||||
"""Encodes a block to a string."""
|
||||
args = [
|
||||
'r%d' % block.num_repeat,
|
||||
'k%d' % block.kernel_size,
|
||||
's%d%d' % (block.strides[0], block.strides[1]),
|
||||
'e%s' % block.expand_ratio,
|
||||
'i%d' % block.input_filters,
|
||||
'o%d' % block.output_filters
|
||||
]
|
||||
if 0 < block.se_ratio <= 1:
|
||||
args.append('se%s' % block.se_ratio)
|
||||
if block.id_skip is False:
|
||||
args.append('noskip')
|
||||
return '_'.join(args)
|
||||
|
||||
@staticmethod
|
||||
def decode(string_list):
|
||||
"""
|
||||
Decodes a list of string notations to specify blocks inside the network.
|
||||
|
||||
:param string_list: a list of strings, each string is a notation of block
|
||||
:return: a list of BlockArgs namedtuples of block args
|
||||
"""
|
||||
assert isinstance(string_list, list)
|
||||
blocks_args = []
|
||||
for block_string in string_list:
|
||||
blocks_args.append(BlockDecoder._decode_block_string(block_string))
|
||||
return blocks_args
|
||||
|
||||
@staticmethod
|
||||
def encode(blocks_args):
|
||||
"""
|
||||
Encodes a list of BlockArgs to a list of strings.
|
||||
|
||||
:param blocks_args: a list of BlockArgs namedtuples of block args
|
||||
:return: a list of strings, each string is a notation of block
|
||||
"""
|
||||
block_strings = []
|
||||
for block in blocks_args:
|
||||
block_strings.append(BlockDecoder._encode_block_string(block))
|
||||
return block_strings
|
||||
|
||||
|
||||
def efficientnet(width_coefficient=None, depth_coefficient=None,
|
||||
dropout_rate=0.2, drop_connect_rate=0.2):
|
||||
""" Creates a efficientnet model. """
|
||||
|
||||
blocks_args = [
|
||||
'r1_k3_s11_e1_i32_o16_se0.25', 'r2_k3_s22_e6_i16_o24_se0.25',
|
||||
'r2_k5_s22_e6_i24_o40_se0.25', 'r3_k3_s22_e6_i40_o80_se0.25',
|
||||
'r3_k5_s11_e6_i80_o112_se0.25', 'r4_k5_s22_e6_i112_o192_se0.25',
|
||||
'r1_k3_s11_e6_i192_o320_se0.25',
|
||||
]
|
||||
blocks_args = BlockDecoder.decode(blocks_args)
|
||||
|
||||
global_params = GlobalParams(
|
||||
batch_norm_momentum=0.99,
|
||||
batch_norm_epsilon=1e-3,
|
||||
dropout_rate=dropout_rate,
|
||||
drop_connect_rate=drop_connect_rate,
|
||||
# data_format='channels_last', # removed, this is always true in PyTorch
|
||||
num_classes=1000,
|
||||
width_coefficient=width_coefficient,
|
||||
depth_coefficient=depth_coefficient,
|
||||
depth_divisor=8,
|
||||
min_depth=None
|
||||
)
|
||||
|
||||
return blocks_args, global_params
|
||||
|
||||
|
||||
def get_model_params(model_name, override_params):
|
||||
""" Get the block args and global params for a given model """
|
||||
if model_name.startswith('efficientnet'):
|
||||
w, d, _, p = efficientnet_params(model_name)
|
||||
# note: all models have drop connect rate = 0.2
|
||||
blocks_args, global_params = efficientnet(width_coefficient=w, depth_coefficient=d, dropout_rate=p)
|
||||
else:
|
||||
raise NotImplementedError('model name is not pre-defined: %s' % model_name)
|
||||
if override_params:
|
||||
# ValueError will be raised here if override_params has fields not included in global_params.
|
||||
global_params = global_params._replace(**override_params)
|
||||
return blocks_args, global_params
|
||||
|
||||
|
||||
url_map = {
|
||||
'efficientnet-b0': 'http://storage.googleapis.com/public-models/efficientnet-b0-08094119.pth',
|
||||
'efficientnet-b1': 'http://storage.googleapis.com/public-models/efficientnet-b1-dbc7070a.pth',
|
||||
'efficientnet-b2': 'http://storage.googleapis.com/public-models/efficientnet-b2-27687264.pth',
|
||||
'efficientnet-b3': 'http://storage.googleapis.com/public-models/efficientnet-b3-c8376fa2.pth',
|
||||
'efficientnet-b4': 'http://storage.googleapis.com/public-models/efficientnet-b4-e116e8b3.pth',
|
||||
'efficientnet-b5': 'http://storage.googleapis.com/public-models/efficientnet-b5-586e6cc6.pth',
|
||||
}
|
||||
|
||||
|
||||
def load_pretrained_weights(model, model_name):
|
||||
""" Loads pretrained weights, and downloads if loading for the first time. """
|
||||
state_dict = load_state_dict_from_url(url_map[model_name])
|
||||
model.load_state_dict(state_dict, strict=False)
|
||||
print('Loaded pretrained weights for {}'.format(model_name))
|
||||
Executable
+134
@@ -0,0 +1,134 @@
|
||||
from torch import nn
|
||||
|
||||
from ssd.modeling import registry
|
||||
from ssd.utils.model_zoo import load_state_dict_from_url
|
||||
|
||||
model_urls = {
|
||||
'mobilenet_v2': 'https://download.pytorch.org/models/mobilenet_v2-b0353104.pth',
|
||||
}
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Sequential):
|
||||
def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
|
||||
padding = (kernel_size - 1) // 2
|
||||
super(ConvBNReLU, self).__init__(
|
||||
nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
|
||||
nn.BatchNorm2d(out_planes),
|
||||
nn.ReLU6(inplace=True)
|
||||
)
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
def __init__(self, inp, oup, stride, expand_ratio):
|
||||
super(InvertedResidual, self).__init__()
|
||||
self.stride = stride
|
||||
assert stride in [1, 2]
|
||||
|
||||
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))
|
||||
layers.extend([
|
||||
# dw
|
||||
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
|
||||
# pw-linear
|
||||
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(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, width_mult=1.0, inverted_residual_setting=None):
|
||||
super(MobileNetV2, self).__init__()
|
||||
block = InvertedResidual
|
||||
input_channel = 32
|
||||
last_channel = 1280
|
||||
|
||||
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 = int(input_channel * width_mult)
|
||||
self.last_channel = int(last_channel * max(1.0, width_mult))
|
||||
features = [ConvBNReLU(3, input_channel, stride=2)]
|
||||
# building inverted residual blocks
|
||||
for t, c, n, s in inverted_residual_setting:
|
||||
output_channel = int(c * width_mult)
|
||||
for i in range(n):
|
||||
stride = s if i == 0 else 1
|
||||
features.append(block(input_channel, output_channel, stride, expand_ratio=t))
|
||||
input_channel = output_channel
|
||||
# building last several layers
|
||||
features.append(ConvBNReLU(input_channel, self.last_channel, kernel_size=1))
|
||||
# make it nn.Sequential
|
||||
self.features = nn.Sequential(*features)
|
||||
self.extras = nn.ModuleList([
|
||||
InvertedResidual(1280, 512, 2, 0.2),
|
||||
InvertedResidual(512, 256, 2, 0.25),
|
||||
InvertedResidual(256, 256, 2, 0.5),
|
||||
InvertedResidual(256, 64, 2, 0.25)
|
||||
])
|
||||
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
# 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.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(self, x):
|
||||
features = []
|
||||
for i in range(14):
|
||||
x = self.features[i](x)
|
||||
features.append(x)
|
||||
|
||||
for i in range(14, len(self.features)):
|
||||
x = self.features[i](x)
|
||||
features.append(x)
|
||||
|
||||
for i in range(len(self.extras)):
|
||||
x = self.extras[i](x)
|
||||
features.append(x)
|
||||
|
||||
return tuple(features)
|
||||
|
||||
|
||||
@registry.BACKBONES.register('mobilenet_v2')
|
||||
def mobilenet_v2(cfg, pretrained=True):
|
||||
model = MobileNetV2()
|
||||
if pretrained:
|
||||
model.load_state_dict(load_state_dict_from_url(model_urls['mobilenet_v2']), strict=False)
|
||||
return model
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ssd.layers import L2Norm
|
||||
from ssd.modeling import registry
|
||||
from ssd.utils.model_zoo import load_state_dict_from_url
|
||||
|
||||
model_urls = {
|
||||
'vgg': 'https://s3.amazonaws.com/amdegroot-models/vgg16_reducedfc.pth',
|
||||
}
|
||||
|
||||
|
||||
# borrowed from https://github.com/amdegroot/ssd.pytorch/blob/master/ssd.py
|
||||
def add_vgg(cfg, batch_norm=False):
|
||||
layers = []
|
||||
in_channels = 3
|
||||
for v in cfg:
|
||||
if v == 'M':
|
||||
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
|
||||
elif v == 'C':
|
||||
layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]
|
||||
else:
|
||||
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
|
||||
if batch_norm:
|
||||
layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
|
||||
else:
|
||||
layers += [conv2d, nn.ReLU(inplace=True)]
|
||||
in_channels = v
|
||||
pool5 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
|
||||
conv6 = nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)
|
||||
conv7 = nn.Conv2d(1024, 1024, kernel_size=1)
|
||||
layers += [pool5, conv6,
|
||||
nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=True)]
|
||||
return layers
|
||||
|
||||
|
||||
def add_extras(cfg, i, size=300):
|
||||
# Extra layers added to VGG for feature scaling
|
||||
layers = []
|
||||
in_channels = i
|
||||
flag = False
|
||||
for k, v in enumerate(cfg):
|
||||
if in_channels != 'S':
|
||||
if v == 'S':
|
||||
layers += [nn.Conv2d(in_channels, cfg[k + 1], kernel_size=(1, 3)[flag], stride=2, padding=1)]
|
||||
else:
|
||||
layers += [nn.Conv2d(in_channels, v, kernel_size=(1, 3)[flag])]
|
||||
flag = not flag
|
||||
in_channels = v
|
||||
if size == 512:
|
||||
layers.append(nn.Conv2d(in_channels, 128, kernel_size=1, stride=1))
|
||||
layers.append(nn.Conv2d(128, 256, kernel_size=4, stride=1, padding=1))
|
||||
return layers
|
||||
|
||||
|
||||
vgg_base = {
|
||||
'300': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',
|
||||
512, 512, 512],
|
||||
'512': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',
|
||||
512, 512, 512],
|
||||
}
|
||||
extras_base = {
|
||||
'300': [256, 'S', 512, 128, 'S', 256, 128, 256, 128, 256],
|
||||
'512': [256, 'S', 512, 128, 'S', 256, 128, 'S', 256, 128, 'S', 256],
|
||||
}
|
||||
|
||||
|
||||
class VGG(nn.Module):
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
size = cfg.INPUT.IMAGE_SIZE
|
||||
vgg_config = vgg_base[str(size)]
|
||||
extras_config = extras_base[str(size)]
|
||||
|
||||
self.vgg = nn.ModuleList(add_vgg(vgg_config))
|
||||
self.extras = nn.ModuleList(add_extras(extras_config, i=1024, size=size))
|
||||
self.l2_norm = L2Norm(512, scale=20)
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
for m in self.extras.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
nn.init.xavier_uniform_(m.weight)
|
||||
nn.init.zeros_(m.bias)
|
||||
|
||||
def init_from_pretrain(self, state_dict):
|
||||
self.vgg.load_state_dict(state_dict)
|
||||
|
||||
def forward(self, x):
|
||||
features = []
|
||||
for i in range(23):
|
||||
x = self.vgg[i](x)
|
||||
s = self.l2_norm(x) # Conv4_3 L2 normalization
|
||||
features.append(s)
|
||||
|
||||
# apply vgg up to fc7
|
||||
for i in range(23, len(self.vgg)):
|
||||
x = self.vgg[i](x)
|
||||
features.append(x)
|
||||
|
||||
for k, v in enumerate(self.extras):
|
||||
x = F.relu(v(x), inplace=True)
|
||||
if k % 2 == 1:
|
||||
features.append(x)
|
||||
|
||||
return tuple(features)
|
||||
|
||||
|
||||
@registry.BACKBONES.register('vgg')
|
||||
def vgg(cfg, pretrained=True):
|
||||
model = VGG(cfg)
|
||||
if pretrained:
|
||||
model.init_from_pretrain(load_state_dict_from_url(model_urls['vgg']))
|
||||
return model
|
||||
Reference in new issue
Block a user