Part 1
Idea of Convolutional Networks
해당 챕터에서는 이미지 데이터에 좋은 성능을 보이는
Convolutional neural networks에 대해 배운다.
그전에, 이전 챕터에서 배운 neural network를 활용하여 이미지 데이터를 처리해보자.
이미지는 기본적으로 픽셀 단위의 가로와 세로, 그리고 rgb를 의미하는 채널로 이루어진다.
즉, 왼쪽의 강아지 이미지가 가로 세로 각각 128 픽셀을 가진다면,
이 이미지는 컬러 이미지이기 때문에 128x128x3의 크기를 가진다.
따라서 만약 x가 linear layer를 거쳤을 때 나오는 출력층 \(z^{(1)}\)의 크기가 64x1이라면
해당 linear layer의 크기는 128 x 128 x 3 x 64 가 되어 약 3백만 개의 파라미터를 가질 것이다.
이미지가 크기가 작고 출력층의 크기가 작음에도 파라미터가 매우 많다는 것을 고려했을 때 매우 비효율적이며,
우리는 이미지 처리에 있어서 더 나은 방법이 필요하다는 것을 알 수 있다.
우리가 이미지를 인식하는 방식을 생각해보자.
우리는 이미지를 픽셀 단위로 쪼개서 인식한 후 전체적인 이미지를 파악하지 않는다.
이미지의 local 정보들을 취합하여 전체 이미지를 인식하는 것이 일반적이다.
즉, 왼쪽 사진의 경우, 강아지를 특징짓는 눈과 코, 입 등을 파악한 후
이 사진이 강아지 사진이라는 것을 인식한다.
이러한 이미지 인식 과정을 neural networks에 적용해보자.
각각의 layer은 이미지의 local features를 추출할 것이며,
Layer를 거칠 수록 추출된 local features는 이전의 local features의 정보를 합친
조금 더 인식 가능한(?) features이 될 것이다.
Neural networks에 local features 추출 과정은 다음과 간다.
Kernal, 혹은 Filter라 불리는 patch가 3x3x3 크기를 갖는다 하자.
(height:3, width: 3, channel: 3)
이 patch는 이미지에 겹쳐가며 훑으며 output을 생성하게 된다.
이렇게 생성된 output은 feature map이라 하며,
Kernal을 활용하여 feature map을 추출하는 neural network를 convolutional neural network라 한다.
하지만 하나의 kernal을 사용할 경우 생성된 feature map의 채널은 1이 된다.
따라서 보통 다수의 kernal을 사용하는데,
해당 강의에서는 4개의 kernal을 사용하여 channel size가 4인 feature map을 생성한다.
여기서 주의할 점은 kernal 통과한 output에 대해서 항상 non-linearity function을 적용해야 한다는 것이다.
앞으로의 그림에서는 표시되지 않지만, convolutional 연산 이후에는
ReLu나 sigmoid 같은 non-linearity activation function이 적용된다는 것을 기억하자.
이미지 데이터 x에 대해 4개의 kernal을 가지는 convolutional 연산이 완료되면
다음과 같은 feature map이 완성된다.
(해당 강의에서는 height와 width의 크기에 변화를 주지 않는 kernal size와 stride를 설정했다.)
그렇다면 feature map은 어떠한 시각적 특성을 가질까?
우측 그림을 보면 feature map은 이미지의 수평적인, 혹은 수직적인 정보들을 가지고 있는 듯하다.
Convolutional 연산이 종료된 이후에는 보통 pooling을 수행한다.
우리는 pooling을 통해 feature map에 down sampling을 적용할 수 있다.
해당 강의에서는 max pooling을 소개하고 있으며, 2x2x4 pooling의 경우,
각 채널에서 2x2에 해당하는 부분의 최댓값을 하나 추출한다.
높이와 너비가 2x2인 max pooling을 224x224x64 데이터에 적용할 경우
오른쪽과 같이 높이와 너비가 반절로 감소한 것을 확인할 수 있으며,
하단의 이미지처럼 downsampling이 완료되었음을 알 수 있다.
위의 과정들을 정리하면 다음과 같다.
Kernal을 활용하여 convolutional 연산을 통해 feature map을 생성한다.
여기서 non-linearity activation function 또한 적용된다.
이후에는 pooling을 활용하여 해당 feature map에 대한 downsampling을 적용한다.
이러한 과정을 반복하며 이미지의 features를 추출하게 된다.
Part 2
Implementing Convolutional Layers
Part1의 내용을 정리하면 다음과 같다.
Convolutional Layer
- 이미지 데이터를 비교적 적은 수의 파라미터를 통해 효율적으로 처리한다.
- 이미지는 지역적으로 인식된다. 즉, 이미지의 지역적 특징을 활용한다.
Pooling
- Convolutional layer을 통해 생성된 feature map의 downsampling을 수행한다.
- Max pooling의 경우, 데이터의 각각의 지역에서 최댓값을 추출한다.
- Convolutional 연산과 다르게, 중첩 연산이 발생하지 않는다.
즉, 이전에 한 번 추출된 지역과 겹쳐서 pooling 연산이 일어나지 않는다.
Finishing
- 가장 마지막에는 보통 Fully connected layer를 추가한다.
- Convolutional과 pooling 연산을 통해 나온 feature map을 flatteng 하여
여러 개의 linear layer를 통과시킴으로써 최종 결과물을 추출한다.
Convolutional neural networks를 이루는 모든 연산들은 tesnsor라고 불리는
N-dimensional arrays에 대해 수행된다.
딥러닝에서는 간단하게 다차원 배열이라고 생각하면 된다고 한다.
Input과 activation, output은 3차원으로 이루어지며, filter는 4차원을 갖는다.
Filter는 이전 파트에서 볼 수 있듯, input 채널과 output 채널을 동시에 가지기 때문인데,
Input의 채널 크기를 output의 채널 크기로 변경해준다.
Convolutions 연산은 각 위치의 곱셈으로써 이루어진다.
즉, filters가 input을 훑으며 filters의 크기에 따라 곱셈이 이루어지고
그 결과로 output(feature map)이 생성되는 것이다.
Convolutional 연산에 의해 생성되는 feature map인 \(z^{(2)}\)의 각 구성 성분은 다음과 같다.
원리는 간단하지만 수식적으로 표시하기엔 다소 복잡한 부분이 있다.
따라서 convolutional 연산 과정을 직관적으로 이해하고자 한다면 아래의 링크를 참고하길 바란다.
(참고: https://wikidocs.net/62306)
하지만 이런 convolutional 연산에는 약간의 문제점이 존재한다.
Input 데이터의 edges의 경우, 생성되는 feautre map에 제대로 반영되지 않는다.
Filter가 input을 훑으며 지나가는데,
그 과정에서 edges는 다른 부분보다 적게 훑어지기(?) 때문이다.
이를 해결하기 위한 방법으로는 가장 간단하게 edges를 제거해버리는 방법이 있다.
다른 지역들에 비해 학습에 제대로 된 반영이 이루어지지 않으므로
각각의 layer마다 filter의 크기에 따라 edges를 제거한다.
하지만 이 경우 각각의 layer를 지나갈 때마다 데이터의 크기가 작아지며,
Edges의 정보들을 모두 무시해버린다는 단점이 있다.
Edges와 관련된 또 다른 해결책으로 padding이 있다.
마치 추운 겨울에 패딩을 입어 몸의 크기가 불어나는 것처럼,
Input에 padding을 더하여 edges를 학습에 적절하게 반영하게 만듦과 동시에
Output의 크기를 기존과 동일하게 유지시켜준다.
보통 가장자리에 0을 추가하는 zero padding이 사용되며,
모든 데이터의 값들의 평균은 0이기 때문에 (기존 값에 평균값을 빼주거나 normalization을 통해)
기존 데이터의 가장자리에 0을 추가하는 것은 기존 데이터 정보에 손실을 주지 않는다.
Convolutional 연산에는 stride가 존재한다.
여태까지 우리는 filter가 input을 훑을 때 한 칸씩 filter를 움직이며 값을 계산했다.
하지만 이러한 방식은 expensive computation을 유발한다.
따라서 일부 지역들은 skip 한 채,
즉, 한 칸씩 filter를 움직이는 것이 아니라 2칸 이상씩 filter를 움직이며 계산한다면
Input을 모두 학습에 반영함과 동시에 계산량을 효과적으로 줄일 수 있을 것이다.
이러한 stride convlutions은 단순히 계산량을 줄여줄 뿐만 아니라, Conv + Pooling만큼 좋다고 한다.
(Padding과 Stride 공부시 참고하면 좋은 블로그: https://egg-money.tistory.com/92)
Part 3
Examples of Convolutional Neural Networks
이제 convolutional neural networks를 활용한 모델들에 대해 알아보자.
AlexNet은 Convolutional neural networks를 활용한 모델들 중 중요한 모델로 꼽힌다.
ILSVRC라는 ImagNet large-scale visual recognition challenge에서
처음으로 neurla network를 사용하여 state-of-the-art results를 선보인 모델이기 때문이다.
AlexNet은 medium depth neural networks이면서,
마지막에 fully connected layer를 활용한 convolutional neural network의 classic model이라 할 수 있다.
AlexNet의 구조는 다음과 같다.
이전 파트에서 언급한 Conv와 Pooling, Fully connected 연산을 확인할 수 있으며,
다음 챕터에서 배울 Normalization 또한 존재한다.
특이한 점은 첫 convolutional 연산에서는 padding을 사용하지 않았지만,
그 이후의 convolutional 연산에서는 zero padding을 사용하고 있다.
이는 맨 처음에 입력되는 이미지의 경우 high resolution을 가지고 있기 때문에
Edges의 정보를 제거하는 것은 정보 손실로 이어질 가능성이 현저히 적으며,
오히려 불필요한 정보를 제거하면서 이미지의 크기를 효과적으로 줄일 수 있기 때문이다.
다만 여러 layers를 거치며 input 데이터의 크기가 작아진 상황에서는 edges에 중요한 정보들이 담겨있을 것이다.
따라서 정보의 손실을 막기 위해 두 번째 convolutional 연산부터는 zero padding을 활용하고 있다.
또 다른 특이한 점은 pooling 단계에서 overlapping이 발생한다는 것이다.
우리는 이전에 pooling 단계에서는 추출되는 지역의 크기와 stride의 크기가 일치하여
추출되는 지역의 중복이 발생하지 않는다고 배웠다.
일반적인 pooling에서는 overlapping이 일어나지 않지만
초기 모델인 AlexNet의 경우에는 overlapping이 발생하는 pooling을 활용했다고 생각하면 될 것 같다.
여기서 주의해야 할 점은 convolutional layer와 fully connected layer에서 모두
ReLu와 같은 non-linearity activation function이 적용되어 있다는 점이다.
그림에는 표시되어 있지 않지만 convolutional, linear 연산에서는 마지막 layer를 제외하고는
항상 activation function이 존재함을 기억하자.
다음으로 살펴볼 모델은 VGG이다.
VGG는 2014년 제안된 모델로, 현재까지도 종종 사용되고 있는 모델이다.
등장 당시에 존재하던 best model에 비해 depth, 즉 channel의 크기를 증가시켰다.
VGG의 구조는 다음과 같다.
Convolutional layers는 2~3개가 연속되어 존재하며, 이전에 입력된 input의 width와 height를 유지한 채
하나의 convolutional layers 그룹을 지날 때마다 depth의 크기가 2배씩 증가하고 있다.
이미지의 width와 height는 오직 pooling을 통해서만 감소시키고 있으며, pooling은 non-overlapping 연산이다.
AlexNet과 마찬가지로 각 convolution과 fully connected layer 이후에는
Non-linearity activation function인 ReLu가 적용되어 있으며
가장 마지막 layers에 fully connected layers가 존재한다.
마지막으로 다룰 모델은 ResNet이다.
152개의 layers를 가지고 있으며, AlexNet과 VGG와 비교했을 때 상당히 많은 layers라는 것을 알 수 있다.
또한 이전 모델들과 비교했을 때 다른 점은 가장 마지막 layer에
fully connected layer가 여러 개가 존재하는 형태가 아닌,
단 하나의 fully connected layer와 그 이전 layer에 average pooling이 존재한다는 것이다.
Fully connected layer 이전에 average pooling을 통해 각각의 depth(channel)에 존재하는
Matrix의 값을 하나의 값으로 만들고, 여러 개의 depth를 종합하여
최종적으로 depth 만큼의 크기를 가지는 하나의 vector를 생성한다.
이는 기존에 fully connected layer로 인해 발생한 문제점들
(파라미터 개수 증가, 이미지 크기의 고정, 위치 정보의 손실 등)을 해결할 수 있게 해 준다.
그렇다면 ResNet이 이전 모델에 비해 좋은 성능을 내는 것이
단순히 fully connected layer의 개수를 줄이고 convolutional layer의 갯수를 늘렸기 때문일까?
좌측 그래프는 기존 모델들의 형태를 유지한 채 layers의 개수만 늘린 모델에 대한 정확도를,
우측 그래프는 ResNet 모델의 형태를 갖춘 채 layers의 개수를 늘렸을 때의 정확도를 나타낸다.
좌측 그래프에서 확인할 수 있듯이 layers의 갯수를 단순히 증가시키는 것은
오히려 error를 증가시키는 역효과를 발생시킨다.
반면 ResNet에서 layers의 개수가 증가함에 따라 error가 감소할 수 있었던 이유는
ResNet이 가지는 Residual block이라는 구조 덕분이다.
기존 방식은 단순히 입력값 x에 대해 target 값 y를 매핑하는 함수인 \(H(x)\)를 얻는 것이 목표였다.
반면 Residual block에서는 \(F(x) + x\)를 최소화하는 것을 목표로 한다.
이때 \(x\)는 현시점에서 바꿀 수 없는 값이기 때문에 결국 \(F(x)\)를 최소화하는 방향으로 학습하게 되며,
\(F(x) = H(x) - x\)이기 때문에 입력과 출력이 같아지는 방향으로 학습한다는 것을 의미한다.
이때 \(H(x) - x\)는 잔차(residual)라 불리며, 여기서 ResNet이라는 이름이 탄생하였다.
직관적으로 생각해보자면, \(H(x)\)는 기존에 학습한 값인 \(x\)에 새로 학습한 값인 \(F(x)\)를 더한 값이다.
즉, 기존에 학습한 정보를 보존한 채 추가적인 학습을 한다는 것을 의미한다.
기존에 학습한 정보를 유지하고 있기 때문에 모델이 학습해야 할 양은 적어지게 되며
많은 layers로 인해 나타나는 문제인 gradient vanishing/exploding 문제를 해결해준다.
그렇다면 위와 같은 residual block 구조는 왜 효과적인 성능을 보일까?
이를 알기 위해서는 먼저 deep networks의 학습이 어려운 이유에 대해 알아야 한다.
Layers가 많은 모델의 경우, 첫 번째 layer의 loss에 대한 기울기는
Chain rule에 의해 굉장히 많은 곱셈이 이루어져야 한다.
이때 굉장히 많은 양의 곱셈으로 인해 얻게 되는 결과는 대부분의 경우 다음과 같다.
1. 만약 곱셈을 이루는 대다수의 숫자들이 1보다 작다면, 그 결과는 0(혹은 매우 가까운 값)가 된다.
2. 만약 곱셈을 이루는 대다수의 숫자들이 1보다 크다면, 그 결과는 무한대(혹은 매우 큰 값)이 된다.
우리가 원하는 이상적인 결과는 각 숫자들이 1과 가까운 값을 가지는 것이다.
곱셈을 이루는 숫자들이 1과 가까운 숫자를 가진다면, 많은 숫자들이 곱해진다 할 지라도
해당 결과가 0이나 무한대에 매우 가까워지지 않기 때문이다.
즉, 학습이 잘 이루어질 수 있는 gradient를 생성하기 때문이다.
Residual 구조는 각 미분 값들이 1과 가까운 값을 가지도록 유도해준다.
왼쪽의 Plaint net의 경우 매우 작거나 큰 값을 가질 수 있다.
반면 Residual net의 경우에는 weights(parameters)가 너무 크지 않은 이상
\(dF/dx\)는 작은 값을 유지하기 때문에 (0에 가까운 양수 혹은 음수)
\(dH/dx\)는 1에 가까운 값을 가질 수 있다.
References
- https://www.youtube.com/watch?v=jNW1Hi7Yi4c&list=PL_iWQOsE6TfVmKkQHucjPAoRtIJYt8a5A&index=17
- https://cs182sp21.github.io/
'딥러닝 > CS182' 카테고리의 다른 글
CS182 - [Lecture 8] Computer Vision (0) | 2022.07.24 |
---|---|
CS182 - [Lecture 7] Getting Neural Nets to Train (0) | 2022.07.23 |
CS182 - [Lecture 5] Backpropagation (0) | 2022.07.02 |
CS182 - [Lecture 4] Optimization (0) | 2022.06.29 |
CS182 - [Lecture 3] Error Analysis (0) | 2022.06.27 |
댓글