ResNet 시작하기

Pytorch 를 본격적으로 사용하기에 앞서, 기본적인 네트워크 구조를 살펴보고자 합니다. 다양한 머신러닝 분야 중 Computer Vision 의 대표적인 모델 ResNet 을 다루고, 이론적인 내용을 어떻게 Pytorch 로 구현하고 활용할 수 있는지 설명하겠습니다.

  • 담당자: 유승민 님

  • 최종수정일: 21-09-29

  • 본 자료는 가짜연구소 3기 Pytorch guide 크루 활동으로 작성됨

Background of Deep Residual Network

Problem Statement

When deeper networks start to converge, a degradation problem has been exposed

How do we get deeper network without degradation of accuracy?

VGGNet에서는 네트워크의 깊이를 깊게 만드는 데 집중하였습니다.

네트워크의 깊이가 깊어짐에 따라 앞선 AlexNet보다 모델의 성능이 좋아졌지만 문제들이 생기게 됩니다.

고질적인 문제인 vanishing/exploding gradient problem은 normalized initialization과 BatchNormalization에 의해 조금 완화되었지만

그 밖에도 네트워크가 깊어질 수록 오히려 모델의 accuracy가 향상되지 않는 문제가 생깁니다.

ResNet 논문의 저자는 이를 degradation problem이라고 부릅니다.

그렇다면 네트워크의 깊이를 깊게 만들면서 accuracy를 향상시키는 방법은 없을까 생각해보려 합니다.

Degradation problem

위 그림은 layer의 갯수만 다르게 학습 시킨 네트워크를 비교하고 있습니다.

위 그림에서 나타나듯이 56-layer 네트워크가 20-layer 네트워크 보다 좋지 못한 성능을 보여주고 있습니다.

이는 overfitting에 의한 문제가 아닌 네트워크의 깊이가 깊어짐에 있어서 성능 저하가 생김을 보여줍니다.

직관적인 이해를 돕기 위한 설명으로, 만약 네트워크의 깊이가 깊어지게 된다면

shallow layer에서 deep layer까지 정보를 전달하는 과정에서 정보 손실이 많이 일어납니다.

따라서, front propagation 과정에서 학습이 잘 되지 않는 문제가 생깁니다.

Front propagation 과정에서의 문제점을 해결하려면 단순히 shallow layer와 deep layer를 잇는 shortcut을 만들면 됩니다.

Shortcut을 통하여, 정보를 직접 전달하면 정보 손실이 일어나지 않기에 front propagation 과정이 효율적으로 이루어집니다.

하지만 지금 제시한 해결책은 앞으로 서술할 Residual Block의 개념의 직관적인 이해를 돕기 위한 예시일 뿐임을 명심해야합니다.

물론, 위의 방법과 유사한 개념으로 Residual Block이 만들어진 것은 맞지만,

실제 우리가 Deep Learning을 통해 풀고자하는 문제는 이처럼 간단하지 않습니다.

Residual Block이란?

Residual Network은 input value \(x\)가 output value \(F(x) +x\) 로 나타납니다.

이때, \(F(x)\) 는 기존 plain block의 형태가 동일하고 \(x\) 는 identity mapping입니다.

\(H(x) = F(x) + x\) 이라 한다면, \(F(x) = H(x) - x\) 로 변형할 수 있는데, 이는 output value에서 input value를 뺀 값으므로

\(F(x)\) 는 residue(잔차)라고 볼 수 있습니다.

우리의 목표는 \(H(x)\), 즉 residual block에서의 output value가 ground truth와 동일해집니다.

사실 \(x\) 는 input value와 동일하므로 \(H(x)\) 는 원하는 \(F(x)\) 을 도출할 수 있다면, 우리가 원하는 목표에 도달할 수 있습니다.

\(H(x)\) 를 구하는 것과 \(F(x)\) 를 구하는 것, 무슨 큰 차이가 있을까?

이해를 돕기 위해 한 가지 예시를 들어보겠습니다.

우리의 목표를 \(H(x)\)\(x\) 와 동일해지길 원한다고 가정합니다. (즉, \(H(x) = x\)이 되도록 원합니다.)

이 경우, \(F(x)\)\(0\) 와 동일해지면 이 문제는 해결됩니다.

원래 문제 \(H(x)\)\(x\) \(\rightarrow weight\) \(...\) \(\rightarrow weight\) \(\rightarrow ReLu\) \(\rightarrow x\) 이지만,

변형된 문제 \(F(x)\)\(x\) \(\rightarrow weight\) \(...\) \(\rightarrow weight\) \(\rightarrow ReLu\) \(\rightarrow 0\) 으로

간단히 하나의 weight layer에 input value가 0으로 수렴하도록 만들어주면 쉽게 해결됩니다.

실제 우리가 풀려는 문제는 항상 \(H(x) = x\) 가 optimum은 아닙니다.

하지만, \(x\) 라는 충분한 정보를 가지고 optimize 시킨다면 정보를 가지지 않고 optimize 시키는 것보다 훨씬 수월할 것입니다.

Identity mapping by Shortcut (Skip Connection)

Shortcut은 추가적인 parameter가 요구되지 않고 계산 복잡성이 높아지지 않는다.

    $f$ : activation function , $ℱ$ : residual function , $ℎ$ : skip connection function (identity mapping)

    $𝒚_l = 𝒉(x_l) + 𝓕(x_l, 𝑾_l)$  ,  $x_{l+1} = f(y_l)$

    $f$ 와 $ℎ$ 는 identity mapping 이라 가정하자

    $𝒉(x_l) = x$  ,  $y_l = x_{l+1}$

    $x_{l+1} = x_l + ℱ(x_l, 𝑾_l)$

    $x_L = x_l + \sum_{i=l}^{L-1}𝓕(x_i, 𝑾_i)$

    $\epsilon$ 은 loss function이라 하면 Chain Rule에 의해   $\frac{\partial \epsilon}{\partial x_l} = \frac{\partial \epsilon}{\partial x_L} \frac{\partial x_L}{\partial x_l} = \frac{\partial \epsilon}{\partial x_L} ( 1 + \frac{\partial}{\partial x_l} \sum_{i=l}^{L-1}𝓕(x_i, 𝑾_i))$

Residual block에서 shortcut을 identity mapping으로 하였을 때, 주는 이점을 설명하려 합니다.

먼저 Residual block에서의 activation function은 \(ReLu\) 이지만 지금은 identity mapping으로 가정을 하였습니다.

즉, identity mapping과 residual function을 더하면 output이 됩니다. ( \(x_{l+1} = x_l + 𝓕(x_l, 𝑾_l)\) )

총 layer의 갯수가 \(L\) 개 있다고 할 때, \(i\)번째 layer부터 \(L\)번째 layer까지 학습을 시키면 \(x_L = x_l + \sum_{i=l}^{L-1}𝓕(x_l, 𝑾_i)\) 가 됩니다.

원래 기존 plain network에서는 matrix-vector product를 통해 학습이 진행되었다면,

residual network에서는 앞선 output들의 덧셈으로 학습이 된다는 의미입니다.

물론, 실제 activation function을 \(ReLu\) 로 사용하게 된다면 output들의 덧셈으로는 표현되지 못하지만

계산 복잡성이 늘어나지는 않는다는 것을 보여주고 있고 input인 \(x\) 를 그대로 정보로 가져오기에 추가적인 parameter를 사용하지 않습니다.

또한, backward propagation 과정에서도 이점이 생깁니다.

\(\frac{\partial \epsilon}{\partial x_l}\)\(\frac{\partial \epsilon}{\partial x_L}\)\(\frac{\partial \epsilon}{\partial x_L} \frac{\partial}{\partial x_l} \displaystyle\sum_{i=l}^{L-1}𝓕(x_l, 𝑾_i))\) 로 분해됩니다.

따라서 \(\frac{\partial \epsilon}{\partial x_L}\) 의 정보가 weight layer와 관계없이 직접 전달됩니다. (즉, \(L\)번째 layer의 정보가 \(l\)번째 layer에 직접 propagate 됩니다.)

그리고 일반적으로 \(\frac{\partial}{\partial x_l} \sum_{i=l}^{L-1}𝓕(x_l, 𝑾_i))\) 가 항상 -1 이 되지 않기에, weight가 아무리 작아지더라도 gradient가 소멸하는 문제를 개선할 수 있습니다.

    $f$ : activation function , $ℱ$ : residual function , $ℎ$ : skip connection function (identity mapping)

    $𝒚_l = 𝒉(x_l) + 𝓕(x_l, 𝑾_l)$  ,  $x_{l+1} = f(y_l)$

    $f$ 는 identity mapping $h(x_l) = \lambda_lx_l$ 이라 가정하자

    $𝒉(x_l) = \lambda_lx_l$  ,  $y_l = x_{l+1}$

    $x_{l+1} = \lambda_lx_l + ℱ(x_l, 𝑾_l)$

    $x_L = ( \prod_{i=l}^{L-1} \lambda_i )x_l + \sum_{i=l}^{L-1}\hatℱ(x_i, 𝑾_i)$ , 이 때, $\hat{ℱ}(x_i, 𝑾_i) = ( \prod_{j=i+1}^{L-1} \lambda_j )$ $ℱ(x_i, 𝑾_i)$

    $\epsilon$ 은 loss function이라 할 때, 앞선 과정과 비슷하게   $\frac{\partial \epsilon}{\partial x_l} = \frac{\partial \epsilon}{\partial x_L} \frac{\partial x_L}{\partial x_l} = \frac{\partial \epsilon}{\partial x_L} ( ( \prod_{i=l}^{L-1} \lambda_i ) + \frac{\partial}{\partial x_l} \sum_{i=l}^{L-1}𝓕(x_l, 𝑾_i))$

shortcut을 identity mapping이 아닌 \( h(x) = \lambda_i x_i\)으로 변경하면 어떻게 되는지 설명하려 합니다.

먼저 L 이 충분히 크다고 가정합니다 ( 즉, 굉장히 깊은 네트워크로 가정합니다. )

만약 모든 i에 대하여 \(\lambda_i x_i\) > 1 이면, \(\frac{\partial \epsilon}{\partial x_l}\) 은 기하급수적으로 커질 것입니다.

이는 결국 gradient exploding problem으로 인해 학습에 큰 문제가 됩니다.

그렇다면 만약 모든 i에 대하여 \(\lambda_i x_i\) < 1 이라고 한다면 어떻게 될까요?

이 경우, \(\frac{\partial \epsilon}{\partial x_l}\) 가 너무 작아지거나 소멸하게 되어 결국 gradient vanishing problem이 생깁니다.

따라서, \( h(x) = \lambda_i x_i\) 으로 shortcut을 지정하는 것은 좋은 선택이 아닙니다.

    110-layer ResNet on CIFAR-10

    (a) $h(x) = x$  ,  error : 6.61%

    (b) $h(x) = 0.5x$  ,  error : 12.35%

    (c) $h(x) = (1 - g(x))\cdot x$  ,  error : 8.70%

    (d) $h(x) = (1 - g(x))\cdot x$  ,  error : 6.91%

    (e) $h(x) = 1x1 conv(x)$  ,  error : 12.22%

    (e) $h(x) = dropout(x)$  ,  error > 20%

마찬가지로 shortcut을 다양한 함수로 변형하여 실험을 해본 결과입니다.

(b) ~ (f) 까지의 shortcut은 정보가 직접 전달되지 않다보니 최적화하기 어려워지는 것으로 추정됩니다.

따라서, identity shortcut은 다른 shortcut에 비해 성능과 속도면에서 우위에 있습니다.

Pre-activation

1.4의 설명에서 activation function을 identity mapping으로 설정할 경우, 많은 장점이 있다는 것을 수식으로 알 수 있었습니다.

그렇다면 activation function을 ReLU가 아닌 identity mapping으로 사용할 수 없을까?

기존 Residual Network에서의 activation function은 \(ReLU\) 입니다.

만약 \(l\)번째 layer에서 \(x_{l+1} = f(y_l)\) , \(f = ReLU\) 이라고 한다면, \(f(y_l)\)\(l+1\)번째 layer에서 residual block과 identity mapping으로 나뉘어 집니다.

즉 , \(y_{l+1} = f(y_l) + ℱ(f(y_l), 𝑾_l)\) 으로 나타나게 됩니다.

하지만 \(ReLU\)은 addition에 많은 영향을 주게 되어 최적화하기 어려워지게 됩니다.

    (b)는 (a)에서의 BatchNormalization과 ReLU를 위치만 변경하여.

    shortcut과 Residual block의 addition 이외에는 어떠한 연산도 없도록 만들었습니다.

    이는 (b)의 activation function을 identity mapping으로 변경되도록 합니다.

    $x_{l+1} = f(y_l)$  ,  $f(x) =$ identity mapping  ,  $\hat f = ReLU$ 이라고 하면,  $x_{l+1} = x_l + ℱ(\hat f(y_l), 𝑾_l)$

    Pre-activation 구조는 최적화하기 쉽고 overfitting을 방지할 수 있습니다. (1.4 참조)

Wide Residual Networks

Problem Statement

Each fraction of a percent of improved accuracy costs nearly doubling the number of layers


기존 ResNet에서는 네트워크의 깊이를 깊게 할까에 초점을 맞추었다면, wide resnet에서는 깊이보다는 filter의 수를 늘리는데 관심을 가집니다.

이 이유는 단순히 깊이만 깊게한다면 학습 속도가 느려지고, 그에 상응하는 정확도는 조금 밖에 상승하지않는 문제점이 있습니다.

따라서, filter의 수를 늘리고 네트워크의 깊이는 조금 줄여서 학습 속도를 빠르게 만드는 방법에 대해 고민해보고자 합니다.

이 장에서는 이론적인 이유보다는 실험적으로 성능 향상이 되었다는 내용을 중심으로 다루려 합니다.

Architecture of Wide ResNet

위 그림에서 3x3은 kernel size를 의미하고 filter의 수를 k배 (2배, 4배, 8배…)를 해줍니다.

이 때, 각 residual block의 개수 또한 N배를 해줍니다.

Experiments

위 그림은 깊이와 k(기존 filter의 수 x k배)를 하였을 때의 test error입니다.

깊이가 깊어질수록 성능이 향상은 이루어지지만, 그 차이가 미미한 것을 볼 수 있습니다.

반면, filter의 수를 늘리고 깊이를 얕게 하면, 깊이가 깊은 네트워크와 성능은 비슷하지만 학습 속도면에서 빠릅니다.

이는 filter의 수가 늘어남에 따라, image의 특징을 더욱 잘 파악하게 되어 기존 깊이가 깊은 네트워크와 성능이 비슷합니다.

ResNeXt

Problem Statement

Increasing cardinality is more effective than going deeper or wider when we increase the capacity.


기존 ResNet과 Wide ResNet에서는 네트워크의 depth와 width에 초점을 두었습니다.

그리고 한 없이 네트워크를 깊게 만드는 것 보다는 filter의 수를 늘려 적당한 depth와 적당한 width를 가지도록 네트워크를 만드는 것이 중요하는 것을 알게 되었습니다.

이 장에서는 몇 개의 hyperparameter를 사용하여 동차인 다중 분기 architecture인 cardinality를 소개하려 합니다.

이 개념은 inception model 구조와 비슷합니다.

하지만 기존 inception model 구조는 구현하는데 있어서 복잡한 요소들이 많았습니다.

필터 번호와 크기는 각 개별 변환에 맞게 조정되고 모듈은 단계별로 맞춤화 되어집니다.

이는 새로운 dataset이나 task들에 적용하는데 큰 어려움이 있습니다.

Architecture of ResNeXt

좌측 그림은 기존 ResNet이고 우측 그림은 ResNeXt를 나타낸다.

ResNeXt의 방식은 쉽고 확장 가능하고 분할,변환,병합 전략을 활용하여 VGGnet과 ResNet의 반복 레이어 전략을 채택한 간단한 architecture입니다.

네트워크의 모듈은 각각 저차원 임베딩에서 일련의 변환을 수행하며 덧셈을 통해 output이 출력됩니다.

Aggregated Transformations

Network-in-Network는 네트워크의 깊이를 깊게 하기 위한 방법에 반해, ResNext에서는 Network-in-Neuron을 사용합니다.

\(ℱ(x) = \sum_{i=1}^C T_i(x))\) 여기서 \(T_i\) 는 여러가지 함수가 됩니다.

단순한 뉴런과 유사하게 \(T_i\)\(x\)를 (선택적으로 저차원) 임베딩으로 투영한 다음 이를 변환해야합니다.

따라서 ResNeXt 블록은 \(y = x + \sum_{i=1}^C T_i(x))\) 으로 나타낼 수 있습니다.

위의 그림에서 (a), (b), (c)는 모두 ResNeXt 블록과 동형인 블록입니다.

그림 (b)는 여러 경로를 연결한다는 점에서 Inception과 ResNet 모듈과 유사합니다.

하지만 우리 모듈은 모든 경로가 동일한 topology를 공유하므로 경로 수를 쉽게 분리할 수 있다는 점에서 기존의 모든 Inception 모듈과 다릅니다.

그리고 혼동하지말아야 할 점은 그림 (c)는 ResNet에서의 bottleneck block 형태가 아닌 32개의 cardinality를 가진 네트워크라는 점입니다.

Experiments

같은 복잡도를 가진 ResNet와 ResNeXt를 비교하였을 때, 확실히 ResNeXt가 조금 더 좋은 성능을 가지고 있다고 할 수 있습니다.