ResNet 논문 리뷰

김태훈·2023년 6월 23일
0

본 페이지에서는 ResNet의 등장배경과 특징에 대해서 말하고자 합니다.


1. ResNet 특징

네트워크의 깊이가 성능에서 중요한 역할을 하는 것은 이미 이전의 논문들에서 많이 나왔다.

"레이어를 더 많이 쌓을 수록 네트워크의 학습이 더 잘 될 것인가?" 라는 질문을 시작으로 모델을 구현한다.

실제로 대조군(얕은 Network)을 두어 비교를 하면 성능이 향상되는 것을 어느정도 확인 하였다.

위의 사진을 보면 분명 네트워크가 깊어졌음에도 불구하고 Error율은 높다.

네트워크가 깊어질 수록 Gradients Vanishing/Exploding 문제가 발생할 가능성이 높다.

이러한 문제점을 해결하기 위해 ResNet은 Skip connection을 통해 Residual learning이 가능하도록 하였다.

2. Residual Block

기존의 블럭이라면 입력에 대해서 두 weight layer 를 F(x)라고 할 때 identity mapping을 추가하고 이를 F(x)와 더해주어 F(x)+x가 되고 기존의 H(x) = F(x) 였지만 H(x)= F(x)+x라고 재정의 한다. 즉 F(x)=H(x)-x가 되는 것이다.

이때 만약 x가 최적값을 찾은 상태라면. 즉, 목표 결과값 H(x)와 x 가 같은 상황이라면 H(x)= F(x)+x 식에서 F(x)를 0으로 향하도록 학습 하면 되는 것이다.

실험적으로 F(x)를 학습 하는 것보다 F(x)= H(x)-x을 0으로 향하도록 학습 하는 것이 더 쉽다는 것을 알 수 있다.

이러한 구조를 통해 Gradients Vanishing/Exploding 문제를 해결할 수 있었고 이에 따라 더 깊게 쌓으면서 지속적인 성능 향상을 확인할 수 있었다.

3. 구조



34 layer residual 구조에서 실선은 identity skip connection을 의미하고 인풋이 그대로 넘어감을 의미한다.

점선은 identity skip connection을 의미하지만 그대로 넘어가는 것이 아닌 input과 output의 채널을 맞춰주기 위해 zero-padding을 사용하여 1x1 kernel size로 stride는 2와 함께 conv 연산을 진행한다.

3.1 Plain Network

구조는 주로 VGGNet의 영향을 받았다.

Conv 연산의 Kernel size는 주로 3x3을 사용하였고

Down sampling 연산은 stride를 2로 지정한 Conv 연산을 통해 구현한다.

3.2 Residual Network

Plain Network를 기반으로 하면서 Shortcut connection을 삽입했다.

Shortcut connection의 결과와 기존 layer의 결과를 pixel별로 더하는 연산을 진행한다.

3.3 성능비교 (PlainNet vs ResNet)


위의 이미지는 PlainNet과 ResNet 각각의 깊이별 training error를 그래프화 한 것이다.

PlainNet은 깊이가 깊어지면서 train error가 높아지는 것을 확인할 수 있지만

ResNet은 깊이가 깊어져도 지속적인 성능 향상이 보인다.

4. 상세구조

18,34,50,101,152 개의 레이어에 대한 모델들의 각각의 stage 구조를 나타내는 것이다.

이때 18,34 와 50,101,152의 residual block 구조는 아래의 사진과 같다.

왼쪽은 18,34-layer에 대한 residual block(building block)이고 오른쪽은 50,101,152에 대한 residua block(bottleneck block)이다.

이때 bottleneck block에서 1x1 conv의 역할은 차원을 줄이고 늘리는 역할을 한다.

5. 코드구현

Keras

import keras

def conv(input,chennel,kernel_sizes,strides=2,padding='valid',is_relu=True,is_bn=True):
  x = input
  x = keras.layers.Conv2D(chennel,kernel_sizes,strides,padding=padding)(x)
  if is_bn:
    x = keras.layers.BatchNormalization()(x)
  if is_relu:
    x = keras.activations.relu(x)
  return x

def resnet50():
  inputs = keras.Input(shape=[224,224,3])
  x= inputs
  initial_features=64
  x = conv(x,initial_features,7,2,'same')
  x = keras.layers.MaxPool2D(3,2,'same')(x)

  repeat = [3,4,6,3]
  for i in range(4):
    for j in range(repeat[i]):
      shortcut = x
      strides = 1
      if j==0:  
        if i != 0:
          strides = 2
        shortcut = conv(shortcut,initial_features*2**(i+2),1,strides,'same',False)
        
      x = conv(x,initial_features*2**i,1,strides,'same')
      x = conv(x,initial_features*2**i,3,1,'same')
      x = conv(x,initial_features*2**(i+2),1,1,'same',False)
      x = keras.layers.add([x,shortcut])
      x = keras.activations.relu(x)
  
  x = keras.layers.GlobalAvgPool2D()(x)
  x = keras.layers.Dense(1000)(x)
  x = keras.activations.softmax(x)

  return keras.Model(inputs=[inputs], outputs=[x], name=f'ResNet50')

model = resnet50()
model.summary()

PyTorch

import torch.nn as nn
from torchsummary import summary

class bottleneck_block(nn.Module):
    def __init__(self,i,o,s,e,stage):
        super(bottleneck_block,self).__init__()
        
        self.conv1 = nn.Conv2d(i,o,1,s)
        self.bn1 = nn.BatchNorm2d(o)
        self.relu = nn.ReLU()
        self.conv2 = nn.Conv2d(o,o,3,1,1)
        self.bn2 = nn.BatchNorm2d(o)
        self.conv3 = nn.Conv2d(o,o*e,1,1)
        self.bn3 = nn.BatchNorm2d(o*e)
        if s == 2 or i==o:
          self.identity = nn.Sequential(
              nn.Conv2d(i,o*e,1,s),
              nn.BatchNorm2d(o*e)
          )
        else :
          self.identity = nn.Sequential()
        

    def forward(self,x):
        identity = self.identity(x)

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)
        out = self.conv3(out)
        out = self.bn3(out)

        out += identity
        out = self.relu(out)

        return out


class ResNet50(nn.Module):
    def __init__(self,e=4,num_layers=[3,4,6,3]):
        super(ResNet50,self).__init__()
        def n_blocks(i,o,s,stage):
            layers = []
            layers.append(bottleneck_block(i,o,s,e,stage))

            for _ in range(1,num_layers[stage]):
                layers.append(bottleneck_block(o*e,o,1,e,stage))

            return nn.Sequential(*layers)

        
        self.conv1 = nn.Sequential(
            nn.Conv2d(3,64,7,2,3),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(3,2,1)
        )

        self.stage1 = n_blocks(64,64,1,0)
        self.stage2 = n_blocks(64*e,128,2,1)
        self.stage3 = n_blocks(128*e,256,2,2)
        self.stage4 = n_blocks(256*e,512,2,3)

        self.F = nn.AdaptiveAvgPool2d(1)

        self.FC = nn.Sequential(
            nn.Linear(512*e,1000)
        )

    def forward(self,x):
        out = self.conv1(x)
        out = self.stage1(out)
        out = self.stage2(out)
        out = self.stage3(out)
        out = self.stage4(out)

        out = self.F(out)

        out = out.view(out.size(0),-1)

        out = self.FC(out)
        
        return out

summary(ResNet50(),(3,224,224))

profile
👋 인공지능을 통해 다음 세대가 더 나은 삶을 살도록

0개의 댓글