MobileNet v3 을 이해하고 Pytorch로 구현할 수 있다.
exp size
: DepSepConv
이전 1x1 Conv
에서 얼마나 expansion 할 것인지#out
: 해당 Inverted Bottleneck layer의 out_channels
SE
: SE-Block
적용 여부 (reduction ratio = 4
) NL
: 어떤 NonLinearity 함수를 사용할 것인지 (HardSwish
or ReLU
)s
: DepSepConv
의 Depthwise
layer에 적용할 stride
bneck
은 MobileNetV2
에서 사용했던 Inverted bottleneck과 유사하므로 이 포스트에서도 동일하게 부르겠다.layer
들의 일정한 패턴이 없는 것을 볼 수 있는데 이는 NAS를 사용했기 때문이다.Conv
의 kernel size가 3
또는 5
이다. Activation function
에 대해 Hard Swish
를 제안했다.SE-Block
을 적용했다. GlobalAveragePooling
layer와 출력 layer 사이에 MLP
사용해당 논문도 이전 포스트에서 다뤘던 MobileNet v1, v2
과 비슷한 맥락으로 파라미터 수가 작은 모델로도 좋은 성능을 뽑아내는 것을 목표로 하였다.
MobileNetV2
의 Inverted bottleneck 구조에서Depthwise
layer와 Pointwise
layer 사이에 SE-block를 추가하였음. 이 SE-Block의 출력 layer의 Activation function
은 Sigmoid
대신 HardSigmoid
를 사용하였고, reduction_ratio
는 4로 고정하였음.ReLU
또는 HardSwish
를 사용함. NAS를 이용해서 만들 모델이기 때문에 정해진 패턴이 있지는 않음.
New Activation function (Hard Sigmoid, Hard Swish)
Sigmoid
는 무리수로 표현되는 수들이 많은데, 무리수를 컴퓨터로 표현하는 것은 유리수보다 더 리소스 부담이 된다고 한다. 이는 특히 mobile device 환경에서 부담스럽기 때문에 HardSigmoid
를 고안했다.
HardSigmoid
마찬가지로 Swish
도 이기 때문에
HardSwish
이다. Figure 6는 Sigmoid
와 HardSigmoid
그리고 Swish
와 HardSwish
를 비교해놓은 그래프이다. 서로 꽤 유사한 것을 볼 수 있다. 실제로도 precision loss가 거의 없었기 때문에 성능손해가 미비했다.
MLP 부활
Original Last Stage
는 GAP
(GlobalAveragePooling) 이후 바로 Output layer인 FC
layer가 등장한다. 하지만 MobileNetV3
에서 채택한 Efficient Last Stage
의 GAP
이후의 layer를 보면 FC
가 두 번 등장한다.
MobileNetV3
가 MobileNetV2
보다 더 우수하다.import torch
from torch import nn
from torchinfo import summary
class SE_Block(nn.Module):
def __init__(self, in_channels, reduction_ratio = 4):
super().__init__()
self.squeeze = nn.AdaptiveAvgPool2d((1, 1))
self.excitation = nn.Sequential(
nn.Linear(in_channels, in_channels // reduction_ratio),
nn.ReLU(inplace = True),
nn.Linear(in_channels // reduction_ratio, in_channels),
nn.Hardsigmoid(inplace = True)
)
def forward(self, x):
se = self.squeeze(x)
se = torch.flatten(se, 1) #se.reshape(x.shape[0], x.shape[1]) # (N, C, 1, 1) -> (N, C)
se = self.excitation(se)
se = se.unsqueeze(dim = 2).unsqueeze(dim = 3) # (N, C) -> (N, C, 1, 1)
out = se * x
return out
class InvertedBottleneck(nn.Module):
def __init__(self, in_channels, inner_channels, out_channels, kernel_size, stride, use_se, use_hswish):
super().__init__()
self.skip_connection = (stride == 1 and in_channels == out_channels)
expand = nn.Sequential(
nn.Conv2d(in_channels, inner_channels, 1, bias = False),
nn.BatchNorm2d(inner_channels, momentum = 0.99),
nn.Hardswish(inplace = True) if use_hswish else nn.ReLU(inplace = True)
)
depthwise = nn.Sequential(
nn.Conv2d(inner_channels, inner_channels, kernel_size, stride, padding = (kernel_size - 1) // 2, groups = inner_channels, bias = False),
nn.BatchNorm2d(inner_channels, momentum = 0.99),
nn.Hardswish(inplace = True) if use_hswish else nn.ReLU(inplace = True)
)
se_block = SE_Block(inner_channels) if use_se else None
pointwise = nn.Sequential(
nn.Conv2d(inner_channels, out_channels, 1, bias = False),
nn.BatchNorm2d(out_channels),, momentum = 0.99
)
layers = []
if in_channels < inner_channels:
layers.append(expand)
layers.append(depthwise)
if se_block is not None:
layers.append(se_block)
layers.append(pointwise)
self.residual = nn.Sequential(*layers)
def forward(self, x):
if self.skip_connection:
out = self.residual(x) + x
else:
out = self.residual(x)
return out
class MobileNetV3(nn.Module):
def __init__(self, bottleneck_cfg, last_channels, n_classes):
super().__init__()
self.first_conv = nn.Sequential(
nn.Conv2d(3, 16, 3, stride = 2, padding = 1, bias = False),
nn.BatchNorm2d(16),
nn.Hardswish(inplace = True)
)
in_channels = 16
bottleneck_layers = []
for kernel_size, inner_channels, out_channels, use_se, use_hswish, stride in bottleneck_cfg:
bottleneck_layers.append(InvertedBottleneck(in_channels, inner_channels, out_channels, kernel_size, stride, use_se, use_hswish))
in_channels = out_channels
self.bottlenecks = nn.Sequential(*bottleneck_layers)
#last_inner_channels = bottleneck_cfg[-1][1]
self.last_conv = nn.Sequential(
nn.Conv2d(in_channels, inner_channels, 1, bias = False),
nn.BatchNorm2d(inner_channels),
nn.Hardswish(inplace = True)
)
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc1 = nn.Sequential(
nn.Linear(inner_channels, last_channels),
nn.Hardswish(inplace = True)
)
self.fc2 = nn.Sequential(
nn.Dropout(0.2),
nn.Linear(last_channels, n_classes)
)
def forward(self, x):
x = self.first_conv(x)
x = self.bottlenecks(x)
x = self.last_conv(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc1(x)
x = self.fc2(x)
return x
def mobilenet_v3_large():
cfgs = [
[3, 16, 16, False, False, 1],
[3, 64, 24, False, False, 2],
[3, 72, 24, False, False, 1],
[5, 72, 40, True, False, 2],
[5, 120, 40, True, False, 1],
[5, 120, 40, True, False, 1],
[3, 240, 80, False, True, 2],
[3, 200, 80, False, True, 1],
[3, 184, 80, False, True, 1],
[3, 184, 80, False, True, 1],
[3, 480, 112, True, True, 1],
[3, 672, 112, True, True, 1],
[5, 672, 160, True, True, 2],
[5, 960, 160, True, True, 1],
[5, 960, 160, True, True, 1],
]
return MobileNetV3(cfgs, last_channels = 1280, n_classes = 1000)
def mobilenet_v3_small():
cfgs = [
[3, 16, 16, True, False, 2],
[3, 72, 24, False, False, 2],
[3, 88, 24, False, False, 1],
[5, 96, 40, True, True, 2],
[5, 240, 40, True, True, 1],
[5, 240, 40, True, True, 1],
[5, 120, 48, True, True, 1],
[5, 144, 48, True, True, 1],
[5, 288, 96, True, True, 2],
[5, 576, 96, True, True, 1],
[5, 576, 96, True, True, 1],
]
return MobileNetV3(cfgs, last_channels = 1024, n_classes = 1000)
model = mobilenet_v3_large()
#model = mobilenet_v3_small()
summary(model, input_size = (2, 3, 224, 224), device = "cpu")
#### OUTPUT ####
====================================================================================================
Layer (type:depth-idx) Output Shape Param #
====================================================================================================
MobileNetV3 [2, 1000] --
├─Sequential: 1-1 [2, 16, 112, 112] --
│ └─Conv2d: 2-1 [2, 16, 112, 112] 432
│ └─BatchNorm2d: 2-2 [2, 16, 112, 112] 32
│ └─Hardswish: 2-3 [2, 16, 112, 112] --
├─Sequential: 1-2 [2, 160, 7, 7] --
│ └─InvertedBottleneck: 2-4 [2, 16, 112, 112] --
│ │ └─Sequential: 3-1 [2, 16, 112, 112] 464
│ └─InvertedBottleneck: 2-5 [2, 24, 56, 56] --
│ │ └─Sequential: 3-2 [2, 24, 56, 56] 3,440
│ └─InvertedBottleneck: 2-6 [2, 24, 56, 56] --
│ │ └─Sequential: 3-3 [2, 24, 56, 56] 4,440
│ └─InvertedBottleneck: 2-7 [2, 40, 28, 28] --
│ │ └─Sequential: 3-4 [2, 40, 28, 28] 9,458
│ └─InvertedBottleneck: 2-8 [2, 40, 28, 28] --
│ │ └─Sequential: 3-5 [2, 40, 28, 28] 20,510
│ └─InvertedBottleneck: 2-9 [2, 40, 28, 28] --
│ │ └─Sequential: 3-6 [2, 40, 28, 28] 20,510
│ └─InvertedBottleneck: 2-10 [2, 80, 14, 14] --
│ │ └─Sequential: 3-7 [2, 80, 14, 14] 32,080
│ └─InvertedBottleneck: 2-11 [2, 80, 14, 14] --
│ │ └─Sequential: 3-8 [2, 80, 14, 14] 34,760
│ └─InvertedBottleneck: 2-12 [2, 80, 14, 14] --
│ │ └─Sequential: 3-9 [2, 80, 14, 14] 31,992
│ └─InvertedBottleneck: 2-13 [2, 80, 14, 14] --
│ │ └─Sequential: 3-10 [2, 80, 14, 14] 31,992
│ └─InvertedBottleneck: 2-14 [2, 112, 14, 14] --
│ │ └─Sequential: 3-11 [2, 112, 14, 14] 214,424
│ └─InvertedBottleneck: 2-15 [2, 112, 14, 14] --
│ │ └─Sequential: 3-12 [2, 112, 14, 14] 386,120
│ └─InvertedBottleneck: 2-16 [2, 160, 7, 7] --
│ │ └─Sequential: 3-13 [2, 160, 7, 7] 429,224
│ └─InvertedBottleneck: 2-17 [2, 160, 7, 7] --
│ │ └─Sequential: 3-14 [2, 160, 7, 7] 797,360
│ └─InvertedBottleneck: 2-18 [2, 160, 7, 7] --
│ │ └─Sequential: 3-15 [2, 160, 7, 7] 797,360
├─Sequential: 1-3 [2, 960, 7, 7] --
│ └─Conv2d: 2-19 [2, 960, 7, 7] 153,600
│ └─BatchNorm2d: 2-20 [2, 960, 7, 7] 1,920
│ └─Hardswish: 2-21 [2, 960, 7, 7] --
├─AdaptiveAvgPool2d: 1-4 [2, 960, 1, 1] --
├─Sequential: 1-5 [2, 1280] --
│ └─Linear: 2-22 [2, 1280] 1,230,080
│ └─Hardswish: 2-23 [2, 1280] --
├─Sequential: 1-6 [2, 1000] --
│ └─Dropout: 2-24 [2, 1280] --
│ └─Linear: 2-25 [2, 1000] 1,281,000
====================================================================================================
Total params: 5,481,198
Trainable params: 5,481,198
Non-trainable params: 0
Total mult-adds (M): 433.24
====================================================================================================
Input size (MB): 1.20
Forward/backward pass size (MB): 140.91
Params size (MB): 21.92
Estimated Total Size (MB): 164.04
====================================================================================================