티스토리 뷰

목차



     

    핵심아이디어는 SENet에서 나왔던 channel-wise response를 계산하여 이를 적용하는 것과 ReLU6에 H-Swish(HardSwish) 이다.

     

    1. H-Swish

     

    $$ Swish(x) = x \sigma (x) $$

    로 표현되며 기존 ReLU에서 음수값에 대한 정보손실 문제를 해결하기 위한 함수이다.

     

    하지만 Sigmoid 자체가 연산량이 크기 때문에 V3에서는 이를 줄이기 위해 Approximation을 한 함수를 제안한다.

     

    $$ H-Swish (x) = x \frac{ReLU6(x+3)}{6} $$

     

    이는 Swish를 잘 approximation 하고, 연산량도 줄어들어 mobile network에 필요한 연산량을 줄이는 방향과 부합한 방법이다.

     

    그림에서 나온 것처럼 거의 같다.

     

     

     

    2. V3 Unit

     

     

    위의 그림처럼 ReLU6 대신 H-Swish로 대체하는데 전부다 하지는 않고 어느정도의 depth 이후에 적용하는 식으로 설계했다.

     

    feature 추출 시 바로 1x1 projection 통과 전 Squeeze-extraction(SE)을 적용하는 방법으로 구성되었다.

     

     

     

    구현 코드는 다음과 같다.

     

    class SqueezeExcitation(nn.Module):
        def __init__(self, in_channels, out_channels):
            super(SqueezeExcitation, self).__init__()
            self.avgpool = nn.AdaptiveAvgPool2d(1)
            self.fc1 = nn.Conv2d(
                in_channels=in_channels,
                out_channels=out_channels // 4,
                kernel_size=1,
                stride=1
            )
            self.batch1 = nn.BatchNorm2d(out_channels // 4)
            self.relu = nn.ReLU()
            self.fc2 = nn.Conv2d(
                in_channels=out_channels // 4,
                out_channels=out_channels,
                kernel_size=1,
                stride=1
            )
            self.batch2 = nn.BatchNorm2d(out_channels)
            self.sigmoid = nn.Sigmoid()
    
        def forward(self, x):
            x = self.avgpool(x)
            x = self.fc1(x)
            x = self.batch1(x)
            x = self.relu(x)
            x = self.fc2(x)
            x = self.batch2(x)
            x = self.sigmoid(x)
            return x

    SENet에 있던 squeeze - extraction expansion layer를 out channel의 1/4로 수정했다.

     

    class bottleneck(nn.Module):
        def __init__(self, in_channels, out_channels, mid_ch, stride = 2, se=False, size=3, mode = 'RE'):
            super(bottleneck, self).__init__()
            padding = 1
            if size == 3:
                padding = 1
            elif size == 5:
                padding = 2
            self.se = se
            self.stride = stride
            self.conv1 = nn.Conv2d(
                in_channels = in_channels,
                out_channels = mid_ch,
                kernel_size=1,
                stride=1
            )
            self.batch1 = nn.BatchNorm2d(mid_ch)
            if mode == 'RE':
                self.act1, self.act2, self.act3 = nn.ReLU6(), nn.ReLU6(), nn.ReLU6()
            elif mode == 'HS':
                self.act1, self.act2, self.act3 = nn.Hardswish(), nn.Hardswish(), nn.Hardswish()
    
            self.DWConv = nn.Conv2d(
                in_channels=mid_ch,
                out_channels=mid_ch,
                groups=mid_ch,
                kernel_size=size,
                stride=stride,
                padding=padding
            )
            self.batch2 = nn.BatchNorm2d(mid_ch)
    
            self.seblock = SqueezeExcitation(
                in_channels=mid_ch,
                out_channels=mid_ch
            )
    
            self.conv2 = nn.Conv2d(
                in_channels=mid_ch,
                out_channels=out_channels,
                kernel_size=1,
                stride=1
            )
            self.batch3 = nn.BatchNorm2d(out_channels)
            if self.stride == 1:
                self.identity = nn.Conv2d(
                    in_channels=in_channels,
                    out_channels=out_channels,
                    kernel_size=1,
                    stride=self.stride
                )
    
        def forward(self, x):
            x2 = self.conv1(x)
            x2 = self.batch1(x2)
            x2 = self.act1(x2)
            x2 = self.DWConv(x2)
            x2 = self.batch2(x2)
            x2 = self.act2(x2)
            if self.se:
                x3 = self.seblock(x2)
                x2 = torch.mul(x2, x3)
            x2 = self.conv2(x2)
            x2 = self.batch3(x2)
            if self.stride == 1:
                x = self.identity(x)
                x2 = torch.add(x, x2)
    
            x2 = self.act3(x2)
            return x2

     

    RE, HS 모드에 따라 activation이 다르게 적용되도록 구성했고, SE layer 적용 여부를 parameter로 추가해둬 전체 네트워크 구성시 편하게 코딩할 수 있게했다. size가 3x3과 5x5가 섞여서 적용되기에 size parameter도 추가해뒀다.

     

     

     

    3. Architecture

     

    Large모델에서 마지막 conv2d 1x1 은 BatchNorm을 적용하지 않는 것이 특이한 점이며, 중간부터 H-Swish를 적용한 것도 특징이라 할 수 있다. 나머지 Layer에서는 Batch Norm을 적용했다.

     

    small모델은 depth와 width를 줄인 정도이며 비슷한 구조를 갖는다.

     

    구현은 Large 모델로 진행했다.

     

    class MobileNetV3_Large(nn.Module):
        def __init__(self, num_classes, in_channels):
            super(MobileNetV3_Large, self).__init__()
            self.conv1 = nn.Sequential(
                nn.Conv2d(
                    in_channels=in_channels,
                    out_channels=16,
                    kernel_size=3,
                    stride=2,
                    padding=1
                ),
                nn.BatchNorm2d(16),
                nn.Hardswish()
            )
            self.bneck1 = bottleneck(in_channels=16, out_channels=16, mid_ch=16, stride=1, mode='RE')
            self.bneck2 = nn.Sequential(
                bottleneck(in_channels=16, out_channels=24, mid_ch=64, stride=2, mode='RE'),
                bottleneck(in_channels=24, out_channels=24, mid_ch=72, stride=1, mode='RE')
            )
            self.bneck3 = nn.Sequential(
                bottleneck(in_channels=24, out_channels=40, mid_ch=72, stride=2, mode='RE', size=5, se=True),
                bottleneck(in_channels=40, out_channels=40, mid_ch=120, stride=1, mode='RE', size=5, se=True),
                bottleneck(in_channels=40, out_channels=40, mid_ch=120, stride=1, mode='RE', size=5, se=True)
            )
            self.bneck4 = nn.Sequential(
                bottleneck(in_channels=40, out_channels=80, mid_ch=240, stride=2, mode='HS'),
                bottleneck(in_channels=80, out_channels=80, mid_ch=200, stride=1, mode='HS'),
                bottleneck(in_channels=80, out_channels=80, mid_ch=184, stride=1, mode='HS'),
                bottleneck(in_channels=80, out_channels=80, mid_ch=184, stride=1, mode='HS')
            )
            self.bneck5 = nn.Sequential(
                bottleneck(in_channels=80, out_channels=112, mid_ch=480, stride=1, mode='HS', se=True),
                bottleneck(in_channels=112, out_channels=112, mid_ch=672, stride=1, mode='HS', se=True)
            )
            self.bneck6 = nn.Sequential(
                bottleneck(in_channels=112, out_channels=160, mid_ch=672, stride=2, mode='HS', se=True, size=5),
                bottleneck(in_channels=160, out_channels=160, mid_ch=960, stride=1, mode='HS', se=True, size=5),
                bottleneck(in_channels=160, out_channels=160, mid_ch=960, stride=1, mode='HS', se=True, size=5)
            )
            self.conv2 = nn.Sequential(
                nn.Conv2d(
                    in_channels=160,
                    out_channels=960,
                    kernel_size=1,
                    stride=1
                ),
                nn.BatchNorm2d(960),
                nn.Hardswish()
            )
            self.avgpool = nn.AdaptiveAvgPool2d(1)
            self.conv3 = nn.Sequential(
                nn.Conv2d(in_channels=960, out_channels=1280, kernel_size=1, stride=1),
                nn.Hardswish()
            )
            self.fc = nn.Conv2d(in_channels=1280, out_channels=num_classes, kernel_size=1, stride=1)
            self.soft = nn.Softmax(dim=1)
    
    
        def forward(self, x):
            x = self.conv1(x)
            x = self.bneck1(x)
            x = self.bneck2(x)
            x = self.bneck3(x)
            x = self.bneck4(x)
            x = self.bneck5(x)
            x = self.bneck6(x)
            x = self.conv2(x)
            x = self.avgpool(x)
            x = self.conv3(x)
            x = self.fc(x)
            x = x.squeeze()
            x = self.soft(x)
    
            return x

     

    마지막 fc layer와 conv2에 Batch Norm을 적용하지 않았다.

     

    참고논문 - https://arxiv.org/abs/1905.02244