Coding, Filming, and Nothing
article thumbnail

info

**DenseNet: Densely Connected Convolutional Networks**  
*Gao Huang, Zhuang Liu, Laurens van der Maaten, Kilian Q. Weinberger*  
CVPR 2017

구현 깃허브: https://github.com/Ahn-Ssu/model-implementation 

-

 

 

DenseNet 입니다. 특별한 내용은 없고, ResNet의 Residual connection( sum skip connection)을 concatenation으로 변경을 한 skip connection을 제안하였습니다. (dense connectivity pattern을 가진 skip connection은 U-Net 등 encoder-decoder 아키텍쳐에서 사용됩니다) 

 

 

Dense connectivity pattern을 통해서 얻는 이점은 다음과 같습니다.

  1. Vanishing-gradient problem 완화 - backprop을 통해 gradient가 직접 흐르게 됩니다. 이는 deep supervision을 암시적으로 활용하는 효과를 가집니다. 
  2. strengthen feature propagation - summation을 통해서 '희석되는' feature 없이 강한 신호를 가진 feature로 모두 전달됩니다.
  3. encourage feature reuse - 네트워크 중간에서 출력된 feature를 재 사용함으로써, 모델의 효율 증대 (중복되는 입력에 재학습이 필요하지 않습니다)
  4. substantially reduce the number of parameters - 네트워크 파라미터 감소로 더 적은 크기의 모델로 운용 가능; ResNet은 레이어마다 각자 weight가 존재합니다. DenseNet은 파라미터 수를 줄일 수 있기 때문에 feature dim을 작게 가져갈 수 있습니다.

 

위의 장점들을 통해 DenseNet은 비교적 더 간단하고, 효과적인 모델 구조라는 점을 이야기하며 DenseNet도 ResNet처럼 충분히 깊은 네트워크를 구조를 형성을 저자는 이야기 합니다. 

 

재밌던 내용은 모든 레이어가 학습에 필요하지 않다는 점이 선행 연구 1202-layer ResNet을 통해서 밝혀진 점을 추가하며 깊은 네트워크에서는 중복되는 내용이 매우 크며, 그렇기 때문에 feature reuse가 가지는 이점이 좋다는 것이었습니다. 

 

 


구현은 DenseNet121로 구현을 하였으나, Dense Block에 전달되는 파라미터 (num_conv)를 수정하면 다른 레이어 갯수를 가지는 모델도 생성할 수 있습니다.

 

 

Basic operation modules

Hyperparameter configuration

import torch
import torch.nn as nn
import torch.nn.functional as F


from easydict import EasyDict as edict

args = edict() 

# net dim 
args.in_dim     = 1 
args.init_dim   = 32 # the growth rate
args.enc_depth  = 5
args.net_dim    = [args.init_dim*2**x for x in range(args.enc_depth)] # [64, 128, 256, 512, 1024]
args.out_dim    = 2

# net operator
conv_kwargs = edict()
conv_kwargs.kernel_size = 3
conv_kwargs.stride      = 1
conv_kwargs.padding     = 1

down_kwargs = edict()
down_kwargs.kernel_size = (2,2,2)
down_kwargs.stride      = (2,2,2)
down_kwargs.padding     = 0


args.conv_kwargs = conv_kwargs
args.down_kwargs = down_kwargs

 

Convolution layer

  • 본문에서는 Composite function $H_l(\cdot)$dmf BN-ReLU-Conv의 순서로 구성을 했기 때문에, convolution layer도 그에 맞추어 구성이 되어 있습니다.
class ConvLayer(nn.Module):
    def __init__(self, in_dim, out_dim,
                    conv_kwargs=None) -> None:
        super(ConvLayer, self).__init__()

        if conv_kwargs is None:
            conv_kwargs = edict()
            conv_kwargs.kernel_size = 3
            conv_kwargs.stride      = 1
            conv_kwargs.padding     = conv_kwargs.kernel_size // 2 # padding=convKenrel_size//2 --> keep size of the input image


        self.norm = nn.BatchNorm2d(in_dim)
        self.act  = nn.ReLU()
        self.conv = nn.Conv2d(in_dim, out_dim, **conv_kwargs) 

    def forward(self, inputs):
        # H() = BN - ReLU - Conv
        h = self.norm(inputs)
        h = self.act(h)
        h = self.conv(h)

        return h

 

 

The first Convolution module: initial convolution

  • 이 즈음 나온 CNN 모델들이 가지는 공통점 같은데, 224x224 사이즈의 이미지를 우선 크기부터 줄이고자 하였는지 큰 커널사이즈의 Conv 연산 후에 pooling을 수행합니다. 
  • 덕분에 224x224 크기의 이미지는 112x112 사이즈가 됩니다.
class InitialConv(nn.Module):
    def __init__(self, in_dim, out_dim,
                    convKernel_size=7, convStride=2, poolingKernel_size=3, poolingStride=2) -> None:
        super(InitialConv, self).__init__()

        self.composite = ConvLayer(in_dim, out_dim, convKernel_size, convStride)
        self.pool = nn.MaxPool2d(poolingKernel_size, stride=poolingStride)

    def forward(self, inputs):

        h = self.composite(inputs)
        h = self.pool

        return h

 

Dense Blocks 

  • DenseNet의 핵심, Dense block. 1x1 convolution을 우선 통과시켜 bottleneck을 형성 model parameter의 수를 조절합니다. 그 후 3x3 convolution에 통과시키며, 1x1-3x3 convolution이 통과할 때마다 output은 input에 다시 concat되어 다음 레이어로 전달이 됩니다. 
class DenseBlock(nn.Module):
    def __init__(self, in_dim, out_dim, num_conv=6,
                    conv_kwargs=None) -> None:
        super(DenseBlock, self).__init__()

        self.num_conv = num_conv
        self.consecutive = nn.ModuleList([])

        for idx in range(self.num_conv):
            # 1x1 convolution
            self.consecutive.append(ConvLayer(in_dim+(out_dim*(idx+1)), out_dim, convKernel_size=1, convStride=1))
            # 3x3 convlotuon
            self.consecutive.append(ConvLayer(out_dim, out_dim, conv_kwargs=conv_kwargs))

    def forward(self, inputs):

        prev = inputs

        for idx in range(self.num_conv):
            inter = self.consecutive[idx*2](prev)      # 1x1 conv
            inter = self.consecutive[idx*2 +1](inter)   # 3x3 conv
            prev  = torch.concat([prev, inter], dim=1)

        return inter #the final layer output

 

Transition Layer

  • pooling layer에 1x1 convolution을 추가하여, channel dimmension scaling을 진행 한 뒤에 pooling을 수행합니다.
class TransitionLayer(nn.Module):
    def __init__(self, in_dim, out_dim, 
                    down_kwargs=None) -> None:
        super(TransitionLayer, self).__init__()

        self.conv = ConvLayer(in_dim, out_dim, convKernel_size=1, convStride=1)
        self.pool = nn.AvgPool2d(**down_kwargs) # used average pool (not global)

    def forward(self, dense_out):

        h = self.conv(dense_out)
        h = self.pool(h)

        return h

 

Model

  • feature encoder와 classifier 밖에 존재하지 않기 때문에 간단한 구조입니다. 아이디어가 새롭고, 구현은 단순하고, 장점은 크다.
class DenseNet(nn.Module):
    def __init__(self, args) -> None:
        super(DenseNet, self).__init__()
        assert not args is None, "invalid args passing"


        self.growthRate = args.init_dim

        self.init_conv = InitialConv(args.in_dim, self.growthRate, convKernel_size=7, convStride=2, poolingKernel_size=3, poolingStride=2)

        self.dense1      = DenseBlock(self.growthRate*2, self.growthRate*2, num_conv=6, convKernel_size=3, convStride=2)
        self.transition1 = TransitionLayer(self.growthRate*2, self.growthRate*4)

        self.dense2      = DenseBlock(self.growthRate*4, self.growthRate*4, num_conv=12, convKernel_size=3, convStride=2)
        self.transition2 = TransitionLayer(self.growthRate*4, self.growthRate*8)

        self.dense3      = DenseBlock(self.growthRate*8, self.growthRate*8, num_conv=24, convKernel_size=3, convStride=2)
        self.transition3 = TransitionLayer(self.growthRate*8, self.growthRate*16)

        self.dense4      = DenseBlock(self.growthRate*16, self.growthRate*16, num_conv=16, convKernel_size=3, convStride=2)
        self.pool        = nn.AdaptiveAvgPool2d(1)
        self.classifier  = nn.Sequential(
                        nn.Linear(self.growthRate*16, self.growthRate*16),
                        nn.Dropout(),
                        nn.ReLU(),
                        nn.Linear(self.growthRate*16, args.out_dim)
                    )

    def forward(self, inputs):

        h0  = self.init_conv(inputs)

        h1  = self.dense1(h0)
        h1  = self.transition1(h1)

        h2  = self.dense2(h1)
        h2  = self.transition2(h2)

        h3  = self.dense3(h2)
        h3  = self.transition3(h3)

        h4  = self.dense4(h3)

        hg  = self.pool(h4)

        out = self.classifier(hg)

        return out

 

profile

Coding, Filming, and Nothing

@_안쑤

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!