ResNet 이해하기



ResNet이란?

Residual Network를 명하는 단어로써, CNN모델 중 하나입니다. 앞으로 다양한 CNN 모델을 접할 예정인데, 그에 앞서 가장 기본이 되는 ResNet의 원리를 이해해보겠습니다.

img

일반 CNN모델은 층이 깊어질수록 ERROR% 가 더 큼을 알 수 있음

우리가 이해하는 CNN모델은 Convolution layer와 Pooling layer의 층을 깊게 쌓아 학습시킴으로서, 컴퓨터가 특징을 알아서 학습하는 모델입니다.

CNN모델은 층이 깊어질수록 더 섬세한 특징을 학습할 수 있단 장점을 갖지만, 오히려 너무 섬세하게 학습을 해 과적합(Overfitting)될 수 있다는 단점을 가지고있습니다.

즉, 층이 너무 깊어질수록 모델에 부작용이 미친다는 것입니다. Resnet은 이런 문제를 해결하기 위해 Residual Block라는 아이디어를 제안합니다.

v2-4fb840788867722ba48fb6398041c678_1440w

위 사진 속엔 3가지 모델이 있습니다. 밑의 2, 3번 모델은 일반적을 사용되는 CNN모델이며 Convolution layer와 Pooling layer이 적접 사용되는것을 볼 수 있습니다.
반면 1번 모델은 화살표 모양의 특이한 구조가 있습니다. 이것이 Residual Block의 핵심 기법입니다.


Residual Block

위에서 말한것 처럼 Residual Block은 기본 CNN모델과는 달리 화살표 모양의 특이한 구조를 갖고있습니다.

images

원래 기존 CNN모델의 경우에 일반적으로 Weight layer를 2개 거친 후 F(x)를 구하는 것을 반복합니다.

하지만 Residual Block에선 “Weight layer를 2개 거친 후의 결과 : F(x)” + “Weight layer를 2개 거치기 전의 입력 : x(그림상에선 화살표를 의미)” 를 통해 H(x) = F(x) + x 로 결과를 구합니다.

이는 입력에서 뽑은 특성과 출력에서 뽑은 더 복잡한 특성을 모두 사용한다는 장점이 있습니다. 또한 더하기 연산한 역전파 계산시 기울기가 1이기에, 기울기 소실이 발생하지 않습니다.

def layer_function(x):
  nn.Conv(32,64,3)(x)
  x = nn.ReLU()(x)
  x = nn.Conv(64,64,3)(x)
  return x
  
x = np.random.randint(10, size=(10,10,32))
F = layer_function(x)
H = F + x


Bottleneck Block

Residual Block과 원리는 같지만 조금 다른 Bottleneck현상을 해결해주는 Bottleneck Block입니다.

IMG_0480

Bottleneck Block은 Weight layer를 2개 거친 후 결과를 구하는 것이 아닌 Weight layer전에 1x1 Convolution을 진행해 연산수(파라미터)를 줄여 주는 블록입니다.

ResNet에선 50 layer이상의 모델에선 Bottleneck현상 해결을 위해 Bottleneck Block을 이용합니다.

def layer_function(x):
  nn.Conv(32,64,1)(x)
  x = nn.ReLU()(x)
  x = nn.Conv(64,64,1)(x)
  x = nn.ReLU()(x)
  x = nn.Conv(64,128,1)(x)
  return x
  
x = np.random.randint(10, size=(10,10,32))
F = layer_function(x)
H = F + x


ResNet Architecture

resnet-architectures-34-101

위는 ResNet의 기본적인 구조입니다. 18 / 34 / 50 / …. 등 깊이에 따라 Residual Block을 사용하는지 Bottleneck Block을 사용하는지 다르며, 필터의 크기 층의 개수등 모두 다르기 떄문에, 자신의 입력 데이터에 적합한 모델을 이용하시길 바랍니다.

Pytorch Code

import pytorch
import pytorch.nn as nn
def Resnet_conv_block_1(ind_dim, out_dim, act_fn, stride=1):
  model = nn.Sequential(
      nn.Conv2d(in_dim, out_dim, kernel_size=1, stride=stride),
      act_fn
  )
  return model
def Resnet_conv_block_3(ind_dim, out_dim, act_fn):
  model = nn.Sequential(
      nn.Conv2d(in_dim, out_dim, kernel_size=3, stride=stride, padding=1),
      act_fn
  )
  return model
class BottleNeck(nn.Module):
  def __init__(self, in_dim, mid_dim, out_dim, act_fn, down=False):#down = 블록을 통과했을 때 특성 지도의 크기가 줄어드는 지 여부를 닮은 불리언 변수 / 줄어들 경우 스트라이드가 2가 되어 반으로 줄어듬
    super(BottleNeck, self).__init__()
    self.act_fn = act_fn
    self.down = down

    if self.down:
      self.layer = nn.Sequential(
          Resnet_conv_block_1(in_dim, mid_dim, act_fn, 2),
          Resnet_conv_block_3(mid_dim, mid_dim, act_fn),
          Resnet_conv_block_1(mid_dim, out_dim, act_fn)
      )
      self.downsample = nn.Conv2d(in_dim, out_dim,1,2)
    else:
      self.layer = nn.Sequential(
          Resnet_conv_block_1(in_dim, mid_dim, act_fn, 2),
          Resnet_conv_block_3(mid_dim, mid_dim, act_fn),
          Resnet_conv_block_1(mid_dim, out_dim, act_fn)
      )
      self.dim_equalizer = nn.Conv2d(in_dim, out_dim, kernel_size=1)

    def forward(self, x):
      if self.down:
        downsample = self.downsample(x)
        out = self.layer(x)
        out = out + downsample
      else:
        out = self.layer(x)
        if x.size() is not out.size():
          x = self.dim_equalizer(x)
      out = out + x
    return out


Reference
  • https://arxiv.org/pdf/1512.03385.pdf
  • 파이토치 첫걸음 / 최건호 / 한빛 미디어