티스토리 뷰

목차



     

    SENet의 기본 아이디어를 간단하게 리뷰하고 ResNet101에 붙여보는 식으로 구현하려고 한다.

     

    기본 ResNet101 구현은 다음 글을 참고하면 된다.

     

     

     

    1. Idea

     

     

    SENet의 구조는 다음 그림으로 모든게 설명이된다.

     

    각 채널별로 중요도(논문에서는 response, attention 등을 언급한다.)가 있어서 이를 계산하는 구조로 설계했다.

     

    논문에서는 channel간 relationship에 focus를 두어 representation quality 향상을 목적으로 설계했다고 한다.

     

    feature map의 채널간 interdependency를 모델링하여 attention 처럼 계산하여 채널간 가중치를 부여하여 계산하는 방식을 택한다.

     

    위의 그림을 설명하면 다음과 같다.

     

    input $X$ ($C`xW`xH`$)를 feature map을 만드는 $F_{tr}$에 통과시킨 후 나오는 feature $U$를 생성한다.

     

    이후 squeeze operation을 통과시켜($F_{sq}$) $1x1xC$의 channel descriptor (가중치 벡터라 생각하면 편하다.) 생성

     

    이때 global receptive field의 정보를 모두 취합할 수 있도록 Global pooling 등의 operation을 한다. 이를 통해

     

    channel-wise feature response를 계산한다.($F_{ex}$) 

     

    이 response embedding(descriptor)와 feature를 계산하여 최종 output을 만들어낸다($F_{scale}$)

     

    2. SE Block

     

    논문에서는 Inception 추가버전과 ResNet 추가버전을 제시하고 있다. 구현은 ResNet101을 기준으로 작성할 것이다.

     

    Block Unit은 다음과 같이 구성된다.

     

     

    이때 r은 reduction factor이다.

     

     

    class로 구현하면 다음과 같다.

     

    class SqueezeExcitation(nn.Module):
        def __init__(self, in_channels, c_list):
            super(SqueezeExcitation, self).__init__()
            self.avgpool = nn.AdaptiveAvgPool2d(1)
            self.fc1 = nn.Conv2d(
                in_channels=in_channels,
                out_channels=c_list[0],
                kernel_size=1,
                stride=1
            )
            self.batch1 = nn.BatchNorm2d(c_list[0])
            self.relu = nn.ReLU()
            self.fc2 = nn.Conv2d(
                in_channels=c_list[0],
                out_channels=c_list[1],
                kernel_size=1,
                stride=1
            )
            self.batch2 = nn.BatchNorm2d(c_list[1])
            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

     

    편의를 위해 reduction factor가 아닌 c_list로 값을 받도록 했다.

     

     

    3. Architecture

     

    전체 Network는 다음과 같다.

     

    가운데가 SE-ResNet-50이다. fc [a, b] 의 a는 첫 번째 fc layer의 output channel, b는 두 번째 fc layer의 output channel 값을 뜻한다.

     

    구현은 ResNet101기준으로 작성했다.

     

    Bottleneck에 다음과 같이 2~3줄만 추가해주면 된다.

     

    class bottleneck(nn.Module):
        def __init__(self, in_channels, mid_channels, out_channels, first = False, stride = 1):
            super(bottleneck, self).__init__()
            self.in_channels = in_channels
            self.mid_channels = mid_channels
            self.out_channels = out_channels
            self.first = first
            self.stride = stride
    
            self.conv1 = nn.Conv2d(
                in_channels = self.in_channels,
                out_channels = self.mid_channels,
                kernel_size=1,
            )
            self.batch1 = nn.BatchNorm2d(num_features=mid_channels)
            self.act1 = nn.ReLU()
            self.conv2 = nn.Conv2d(
                in_channels = self.mid_channels,
                out_channels = self.mid_channels,
                kernel_size = 3,
                stride = self.stride,
                padding = 1
            )
            self.batch2 = nn.BatchNorm2d(num_features=mid_channels)
            self.act2 = nn.ReLU()
            self.conv3 = nn.Conv2d(
                in_channels=self.mid_channels,
                out_channels=self.out_channels,
                kernel_size=1
            )
            self.act3 = nn.ReLU()
            self.batch3 = nn.BatchNorm2d(num_features=out_channels)
            self.proj = nn.Sequential(nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride = self.stride), nn.BatchNorm2d(num_features=out_channels))
            self.net = nn.Sequential(self.conv1, self.batch1, self.act1, self.conv2, self.batch2, self.act2, self.conv3)
            self.seblock = SqueezeExcitation(
                in_channels=self.out_channels,
                c_list = [self.out_channels // 16, self.out_channels]
            ) # SE block 추가
    
        def forward(self, x):
            x2 = self.net(x)
            x2 = self.batch3(x2)
            x3 = self.seblock(x2) # seblock 추가 부분
            x2 = torch.mul(x2, x3) # scale operation
            if self.first:
                x = self.proj(x)
            x = torch.add(x, x2)
            x = self.act3(x)
            return x

     

     

    이후 코드는 아래 링크를 참고하면 된다.

     

    https://ai.dreamkkt.com/21

     

    [논문 구현] ResNet 직접 구현해보기!

    ResNet101을 기준으로 구현했다. bottleneck 을 class로 따로 떼었으며, first 변수로 사이즈를 줄이는 layer 여부를 구분했다. bottleneck은 conv 1x1 - batchnorm - relu - conv 3x3 - batchnorm - relu - conv..

    ai.dreamkkt.com

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

     

    Squeeze-and-Excitation Networks

    The central building block of convolutional neural networks (CNNs) is the convolution operator, which enables networks to construct informative features by fusing both spatial and channel-wise information within local receptive fields at each layer. A broa

    arxiv.org