Loading files
b150d1354This file is pinned to the selected release. Download the original to open it in your design tool.
import math
from collections import OrderedDict
from copy import deepcopy
from typing import List
import torch
from pytorch_toolbelt.modules import ABN, SpatialGate2d
from pytorch_toolbelt.modules.activations import ACT_HARD_SWISH
from pytorch_toolbelt.modules.agn import AGN
from torch import nn
from torch.nn import functional as F
from torch.nn.init import kaiming_normal_
def round_filters(filters, width_coefficient, depth_divisor, min_depth):
"""
Calculate and round number of filters based on depth multiplier.
"""
filters *= width_coefficient
min_depth = min_depth or depth_divisor
new_filters = max(
min_depth, int(filters + depth_divisor / 2) // depth_divisor * depth_divisor
)
if new_filters < 0.9 * filters: # prevent rounding by more than 10%
new_filters += depth_divisor
return int(new_filters)
def round_repeats(repeats: int, depth_multiplier):
"""
Round number of filters based on depth multiplier.
"""
if not depth_multiplier:
return repeats
return int(math.ceil(depth_multiplier * repeats))
def drop_connect(inputs, p, training):
"""
Drop connect implementation.
"""
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
) # uniform [0,1)
binary_tensor = torch.floor(random_tensor)
output = inputs / keep_prob * binary_tensor
return output
class EfficientNetBlockArgs:
def __init__(
self,
input_filters,
output_filters,
expand_ratio,
r=1,
k=3,
s=1,
se_reduction=4,
dropout=0.0,
id_skip=True,
**kwargs,
):
self.input_filters = input_filters
self.output_filters = output_filters
self.expand_ratio = expand_ratio
self.num_repeat = r
self.se_reduction = se_reduction
self.dropout = dropout
self.kernel_size = k
self.stride = s
self.width_coefficient = 1.0
self.depth_coefficient = 1.0
self.depth_divisor = 8
self.min_filters = None
self.id_skip = id_skip
def scale(
self,
width_coefficient: float,
depth_coefficient: float,
depth_divisor: float = 8.0,
min_filters: int = None,
):
copy = deepcopy(self)
copy.input_filters = round_filters(
self.input_filters, width_coefficient, depth_divisor, min_filters
)
copy.output_filters = round_filters(
self.output_filters, width_coefficient, depth_divisor, min_filters
)
copy.num_repeat = round_repeats(self.num_repeat, depth_coefficient)
copy.width_coefficient = width_coefficient
copy.depth_coefficient = depth_coefficient
copy.depth_divisor = depth_divisor
copy.min_filters = min_filters
return copy
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: EfficientNetBlockArgs, abn_block: ABN, abn_params):
super().__init__()
self._block_args = deepcopy(block_args)
self.has_se = block_args.se_reduction is not None
self.id_skip = block_args.id_skip # skip connection and drop connect
self.expand_ratio = block_args.expand_ratio
# Expansion phase
inp = block_args.input_filters # number of input channels
oup = (
block_args.input_filters * block_args.expand_ratio
) # number of output channels
if block_args.expand_ratio != 1:
self.expand_conv = nn.Conv2d(
in_channels=inp, out_channels=oup, kernel_size=1, bias=False
)
self.abn0 = abn_block(oup, **abn_params)
# Depthwise convolution phase
self.depthwise_conv = nn.Conv2d(
in_channels=oup,
out_channels=oup,
groups=oup, # groups makes it depthwise
kernel_size=block_args.kernel_size,
padding=block_args.kernel_size // 2,
stride=block_args.stride,
bias=False,
)
self.abn1 = abn_block(oup, **abn_params)
# Squeeze and Excitation layer, if desired
if self.has_se:
se_channels = max(1, inp // block_args.se_reduction)
self.se_block = SpatialGate2d(oup, squeeze_channels=se_channels)
# Output phase
final_oup = self._block_args.output_filters
self.project_conv = nn.Conv2d(
in_channels=oup, out_channels=final_oup, kernel_size=1, bias=False
)
self.abn2 = abn_block(final_oup, **abn_params)
self.input_filters = block_args.input_filters
self.output_filters = block_args.output_filters
self.reset_parameters()
def reset_parameters(self):
if hasattr(self, "expand_conv"):
kaiming_normal_(self.expand_conv.weight,
a=self.abn0.slope,
nonlinearity=self.abn0.activation)
kaiming_normal_(self.depthwise_conv.weight,
a=self.abn1.slope,
nonlinearity=self.abn1.activation)
kaiming_normal_(self.project_conv.weight,
a=self.abn1.slope,
nonlinearity=self.abn2.activation)
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
"""
x = inputs
if self.expand_ratio != 1:
# Expansion and Depthwise Convolution
x = self.abn0(self.expand_conv(inputs))
x = self.abn1(self.depthwise_conv(x))
# Squeeze and Excitation
if self.has_se:
x = self.se_block(x)
x = self.abn2(self.project_conv(x))
# Skip connection and drop connect
if (
self.id_skip
and self._block_args.stride == 1
and self.input_filters == self.output_filters
):
if drop_connect_rate:
x = drop_connect(x, p=drop_connect_rate, training=self.training)
x = x + inputs # skip connection
return x
def get_default_efficientnet_params(dropout=0.2, **kwargs):
return [
EfficientNetBlockArgs(
r=1,
k=3,
s=1,
expand_ratio=1,
input_filters=32,
output_filters=16,
se_reduction=4,
dropout=dropout,
**kwargs,
),
EfficientNetBlockArgs(
r=2,
k=3,
s=2,
expand_ratio=6,
input_filters=16,
output_filters=24,
se_reduction=4,
dropout=dropout,
**kwargs,
),
EfficientNetBlockArgs(
r=2,
k=5,
s=2,
expand_ratio=6,
input_filters=24,
output_filters=40,
se_reduction=4,
dropout=dropout,
**kwargs,
),
EfficientNetBlockArgs(
r=3,
k=3,
s=2,
expand_ratio=6,
input_filters=40,
output_filters=80,
se_reduction=4,
dropout=dropout,
**kwargs,
),
EfficientNetBlockArgs(
r=3,
k=5,
s=1,
expand_ratio=6,
input_filters=80,
output_filters=112,
se_reduction=4,
dropout=dropout,
**kwargs,
),
EfficientNetBlockArgs(
r=4,
k=5,
s=2,
expand_ratio=6,
input_filters=112,
output_filters=192,
se_reduction=4,
dropout=dropout,
**kwargs,
),
EfficientNetBlockArgs(
r=1,
k=3,
s=1,
expand_ratio=6,
input_filters=192,
output_filters=320,
se_reduction=4,
dropout=dropout,
**kwargs,
),
]
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,
blocks_args: List[EfficientNetBlockArgs],
num_classes: int,
abn_block=ABN,
abn_params=None,
in_channels=3,
dropout_rate=0.0,
**kwargs,
):
super().__init__()
assert isinstance(blocks_args, list), "blocks_args should be a list"
assert len(blocks_args) > 0, "block args must be greater than 0"
if abn_params is None:
assert issubclass(abn_block, ABN)
abn_params = {"activation": "swish", "momentum": 0.01, "eps": 1e-3}
first_block_args = blocks_args[0]
last_block_args = blocks_args[-1]
# Stem
self.stem = nn.Sequential(
OrderedDict(
[
(
"conv",
nn.Conv2d(
in_channels,
first_block_args.input_filters,
kernel_size=3,
padding=1,
stride=2,
bias=False,
),
),
("abn", abn_block(first_block_args.input_filters, **abn_params)),
]
)
)
# Build blocks
for i, block_args in enumerate(blocks_args):
module = []
# The first block needs to take care of stride and filter size increase.
module.append(MBConvBlock(block_args, abn_block, abn_params))
if block_args.num_repeat > 1:
block_args = deepcopy(block_args)
block_args.stride = 1
block_args.input_filters = block_args.output_filters
for _ in range(block_args.num_repeat - 1):
module.append(MBConvBlock(block_args, abn_block, abn_params))
self.add_module(f"block{i}", nn.Sequential(*module))
# Head
out_channels = round_filters(
1280,
last_block_args.width_coefficient,
last_block_args.depth_divisor,
last_block_args.min_filters,
)
self.conv_head = nn.Conv2d(
last_block_args.output_filters, out_channels, kernel_size=1, bias=False
)
self.abn_head = abn_block(out_channels, **abn_params)
# Final linear layer
self.dropout = nn.Dropout(dropout_rate, inplace=True)
self.fc = nn.Linear(out_channels, num_classes)
def forward(self, inputs):
# Stem
x = self.stem(inputs)
# Blocks
x = self.block0(x)
# print(x.size())
x = self.block1(x)
# print(x.size())
x = self.block2(x)
# print(x.size())
x = self.block3(x)
# print(x.size())
x = self.block4(x)
# print(x.size())
x = self.block5(x)
# print(x.size())
x = self.block6(x)
# print(x.size())
# Head
x = self.abn_head(self.conv_head(x))
x = F.adaptive_avg_pool2d(x, 1).squeeze(-1).squeeze(-1)
x = self.dropout(x)
x = self.fc(x)
return x
def efficient_net_b0(num_classes: int, **kwargs):
params = get_default_efficientnet_params(dropout=0.2, **kwargs)
params = [p.scale(width_coefficient=1.0, depth_coefficient=1.0) for p in params]
return EfficientNet(params, num_classes=num_classes, **kwargs)
def efficient_net_b1(num_classes: int, **kwargs):
params = get_default_efficientnet_params(dropout=0.2)
params = [p.scale(width_coefficient=1.0, depth_coefficient=1.1) for p in params]
return EfficientNet(params, num_classes=num_classes, **kwargs)
def efficient_net_b2(num_classes: int, **kwargs):
params = get_default_efficientnet_params(dropout=0.3)
params = [p.scale(width_coefficient=1.1, depth_coefficient=1.2) for p in params]
return EfficientNet(params, num_classes=num_classes, **kwargs)
def efficient_net_b3(num_classes: int, **kwargs):
params = get_default_efficientnet_params(dropout=0.3)
params = [p.scale(width_coefficient=1.2, depth_coefficient=1.4) for p in params]
return EfficientNet(params, num_classes=num_classes, **kwargs)
def efficient_net_b4(num_classes: int, **kwargs):
params = get_default_efficientnet_params(dropout=0.3)
params = [p.scale(width_coefficient=1.4, depth_coefficient=1.8) for p in params]
return EfficientNet(params, num_classes=num_classes, **kwargs)
def efficient_net_b5(num_classes: int, **kwargs):
params = get_default_efficientnet_params(dropout=0.5)
params = [p.scale(width_coefficient=1.6, depth_coefficient=2.2) for p in params]
return EfficientNet(params, num_classes=num_classes, **kwargs)
def efficient_net_b6(num_classes: int, **kwargs):
params = get_default_efficientnet_params(dropout=0.5)
params = [p.scale(width_coefficient=1.8, depth_coefficient=2.6) for p in params]
return EfficientNet(params, num_classes=num_classes, **kwargs)
def efficient_net_b7(num_classes: int, **kwargs):
params = get_default_efficientnet_params(dropout=0.5)
params = [p.scale(width_coefficient=2.0, depth_coefficient=3.1) for p in params]
return EfficientNet(params, num_classes=num_classes, **kwargs)
def test_efficient_net():
from pytorch_toolbelt.utils.torch_utils import count_parameters
num_classes = 1001
x = torch.randn((1, 3, 600, 600)).cuda()
for model_fn in [
efficient_net_b0,
# efficient_net_b1,
# efficient_net_b2,
# efficient_net_b3,
# efficient_net_b4,
# efficient_net_b5,
# efficient_net_b6,
# efficient_net_b7
]:
print("=======", model_fn.__name__, "=======")
model = model_fn(num_classes).eval().cuda()
print(count_parameters(model))
print(model)
print()
print()
output = model(x)
print(output.size())
def test_efficient_net_group_norm():
from pytorch_toolbelt.utils.torch_utils import count_parameters
num_classes = 1001
x = torch.randn((1, 3, 600, 600)).cuda()
for model_fn in [
efficient_net_b0,
efficient_net_b1,
efficient_net_b2,
efficient_net_b3,
efficient_net_b4,
efficient_net_b5,
efficient_net_b6,
efficient_net_b7,
]:
print("=======", model_fn.__name__, "=======")
agn_params = {"num_groups": 8, "activation": ACT_HARD_SWISH}
model = (
model_fn(num_classes, abn_block=AGN, abn_params=agn_params).eval().cuda()
)
print(count_parameters(model))
# print(model)
print()
print()
output = model(x)
print(output.size())
torch.cuda.empty_cache()