본문 바로가기

Deep Learning for Computer Vision

EECS 498-007 / 598-005 Lecture 10 : Training Neural Networks (Part 1)

강의 링크

https://www.youtube.com/watch?v=lGbQlr1Ts7w&list=PL5-TkQAfAZFbzxjBHtzdVCWE0Zbhomg7r&index=10

 

강의 슬라이드

https://web.eecs.umich.edu/~justincj/slides/eecs498/498_FA2019_lecture10.pdf

 

이번 시간부터는 신경망을 제대로 학습시키기 위해 해야 할 것들에 대해 알아볼 것인데,

이번 시간에는 위 3 가지 사항 중, 첫 번째 것만 강의하고 나머지 2개는 다음 강의에서 강의할 것이다.

 

Activation Function은 신경망에 Non-Linearity를 추가해주는 매우 중요한 역할을 수행하는데

Activation Function으로는 여러 가지를 사용할 수 있다.

 

먼저 Sigmoid는 과거 수십년동안 사용했던 activation으로 sigmoid가 그리는 그래프가 S자 커브를 그린다고 해서 sigmoid이다. sigmoid는 뉴런의 'firing rate'를 간단하게 표현할 수 있지만 그만큼 좋지 않은 성능을 보이는데, 

왜냐하면 sigmoid의 경우 양 끝단으로 다가갈수록 gradient는 0에 더 가까워지고, computational graph의 노드에서는

upstream gradient가 0에 근접한 수이면 downstream gradient도 0에 근접하게 되는데, 이 때문에 모델이

'deep' 해질수록 레이어의 gradient가 0이 되어 학습이 진행되지 않는 '죽은' 레이어가 생기게 된다.

즉, sigmoid를 사용하면 Sigmoid의 입력이 0 근처의 값이 들어와야만 한다는 강력한 제약조건이 붙는다.

이 때문에 sigmoid는 현재 거의 쓰이지 않고 있다.

Sigmoid는 다른 문제점도 가지고 있는데, Sigmoid의 출력은 'zero-centered'가 아니고 항상 양수이다.

이럴 경우, 신경망의 각 레이어의 입력은 양수만 존재한다는 것이고, 이러면 각 wieght의 모든 gradient의 부호는

모두 같다. 그러면 gradient descent의 모든 step은 지그재그를 그리면서 갈 수밖에 없는데, 이러한 움직임으로 인한

노이즈는 데이터의 차원이 크면 클수록 많아진다. 특히 step의 크기가 크면, learning rate가 크면 이러한 노이즈는 더욱 증가한다. 다행스럽게도 이 문제점은 데이터 1개에 대해서 학습을 진행하기보다 mini batch를 사용한다면 어느 정도 방지할 수 있다. mini batch를 사용하면 weight의 step은 각 batch의 step의 합으로 이루어져 있기에

weight의 모든 원소에 대한 step의 부호가 같은 일은 일어나기 힘들다.

Sigmoid의 마지막 문제점은 exponential 연산이 1회 연산을 수행할 때, 여러 클럭 사이클이 걸쳐 수행된다는 점이

있는데, 한 사이클만에 연산이 수행되는 'ReLU'와 같은 것들에 비하면 매우 값비싼 연산이라고 할 수 있다. 그나마 

GPU를 사용할 때는 메인 메모리와 GPU 메모리 간의 데이터 이동이 대부분의 시간을 잡아먹기에 상관이 없지만,

모바일에서 구동할 때나, GPU를 사용할 수 없는 환경에서는 매우 치명적인 문제가 된다.

Sigmoid를 rescale한 Tanh 란 것도 있는데, 이 함수에서는 Sigmoid가 'zero-centered'하지 않다는 문제를 해결했다.

하지만 여전히 gradient가 0이 돼버리는 문제와 값비싼 연산이라는 문제를 해결하지는 못했다.

ReLU는 sign bit만 체크하면 되는 매우 값싼 activation으로 sigmoid와 비교하면 매우 빠르게 수행된다.

AlexNet에서만 하더라도 sigmoid 보다 6배 빠르게 학습이 완료된다.

또한 입력으로 음수만 아니라면 gradient는 0이 되는 문제는 없으며 sigmoid와 동일하게 'zero-centered'를 보이지는

않지만 근본적으로 속도가 빠른 activation이기에 모델 학습 시에 문제가 되지 않는다.

ReLU에서는 sigmoid에 비할바는 아니지만 그래도 입력이 음수가 들어오면  gradient가 0이 되어버린다는 문제를 가지고 있다.

gradient가 0이 되어버린 ReLU를 'dead ReLU'라고 부르며 dead ReLU를 만드는 데이터를 만나면 gradient는 이후로도 계속 0이 되어버린다. 그래도 ReLU에 매우 작은 bias를 주거나 모든 데이터가 음수로 주어지는 것이 아니라면 

dead ReLU가 되는 확률을 줄일 수 있다.

Leaky ReLU는 ReLU의 zero-centered 하지 않고, zero gradient를 가지는 문제를 해결한 것인데, ReLU에서 입력이 

음수면 0을 출력하는 것과 달리, Leaky ReLU에서는 입력이 음수일 때, hyperparameter인 임의의 작은 양수 alpha를

곱해주어 gradient가 0이 아니게 만든다. 이를 통해 ReLU에서 발생하는 gradient가 0이 되는 상황을 피할 수 있다.

또한 Leaky ReLU의 hyperparameter alpha를 parameter로 만들어 이 자체도 학습하는 PReLU란 Activation도 있다고

한다.  그리고 모든 ReLU 계열의 함수들은 0에서 미분이 불가능하여 임의로 0+나 0-에서의 gradient 값으로 선택하는데

입력이 0으로 주어지는 경우는 매우 드물기에 어떤 것을 선택하던지 상관없다.

또한 ReLU보다 더 smooth 하고 zero-centered 되어 있는 ELU도 있는데, ELU에서는 입력이 음수일 때, exp연산을 

이용한다. 그리고 exp 연산의 결괏값에 1을 빼주는데, 이는 gradient가 0이 나오는 것을 막기 위해 사용한다고 하며,

거기다 임의의 hyperparameter alpha를 곱해서 스케일 해준다고 한다. 이 activation을 사용하면 기존 ReLU의 문제를 

해결할 수 있지만 exp 연산의 비용이 ReLU와 비교하면 매우 크다는 한계를 가진다.

ELU를 스케일 하여 사용하는 SELU란 것도 있는데, 여기서 alpha와 lambda는 매우 긴 상수 값으로 정해진다. 이 방식을 사용하면 레이어가 매우 많은 'deep'한 모델에서 normalization 효과를 주어 batch norm 같은 기법들을 사용하지

않아도 학습이 진행된다.

이번 강의에서 여러 가지 Activation Function들에 대해서 알아봤는데, Sigmoid나 Tanh 같이 0 gradient에 취약한 것만 아니면 실성 능은 다들 별 차이가 없어서 어떤 걸 선택하든지 성능상으론 별 문제가 안 된다.

지금까지 배운 Activation Function들에 대해서 정리하자면, 

  1. 특별한 이유 없으면 ReLU를 쓰면 무난하다.
  2. 특별한 이유가 있거나 0.1%의 정확도라도 올리고 싶으면 Leaky ReLU나 ELU, SELU, GELU 등을 써봐라
  3. sigmoid나 tanh는 쓰지 마라

로 정리할 수 있다.

 

그리고 지금까지 다룬 Activation Function 모두 단조함수인데, 단조함수가 아니라 Cosine 함수 같은 그래프를 그리는 함수를 사용할 경우, 모델의 학습에 문제가 생기기 쉽다.

그래도 불가능한 것은 아니라서 드물게 사용되기는 하지만, GELU와 같이 단조함수가 아닌 activation도 있다.

지금부터는 데이터를 전처리하는 과정인 Data Preprocessing에 대해서 다룰 것인데

머신러닝에서는 일반적으로 모델을 학습시키기 전에 데이터를 전처리를 함으로써 데이터의 분포를 개선시키는데

각 데이터에 평균을 빼서 데이터가 zero-centered 하게 만들고, 표준편차로 나눔으로써 각 feature들의 분산을 동일하게 해 준다.

데이터에 위와 같은 전 치리를 수행해주는 이유는 만약 모든 데이터가 양수, 혹은 음수를 가지는 경우

gradient도 한쪽으로 편향되는데 데이터의 평균을 0으로 만들어 주면 이를 방지할 수 있다.

또한 데이터의 분포가 원점에서 먼 위치에서 존재할 경우 weight의 사소한 변화도 데이터에는 크게 영향을 끼쳐서

학습하기 어려워진다. 그래서 normalization을 통해 데이터를 원점에 모아준다면 weight의 변화가 끼치는 영향을 

감소시켜 원활한 학습이 가능하게 해 준다.

데이터가 이미지인 경우에는 위 2가지 사항으로 충분하지만 이미지가 아닌 경우에는 PCA나 Whitening,

Decorrelation 등을 적용할 수 있다.

 

이미지 데이터의 경우 전처리로 보통 위 슬라이드에 나온 사항들을 적용하며, test 과정에서는 train 과정에서 구한

평균과 표준 편차를 활용한다. 그리고 Batch Normalization을 첫 번째 레이어에 적용시켜도 normalization 효과를

얻을 수는 있지만, 따로 하는 것보다는 효과가 떨어지기에 보통 사람들은 두 가지 모두 적용한다고 한다.

 

지금부터는 Weight Initialization에 대해서 다룰 것인데

만약 모든 wieght과 bais가 0이라면 레이어의 입력과는 별개로 출력은 항상 0이 나오게 되며, gradient도 항상 0이

되게 된다. 모델이 이런 상황에 처하게 되는 것을 'symmetry braeaking'에 실패했다고 하며

그렇기에 weight을 0으로 초기화해서는 안된다.

그래서 지금까지의 과제에서는 모두 가우시안 분포를 따르는 임의의 작은 값으로 초기화하였는데

이 방식은 모델의 레이어가 늘어날수록 학습이 되지 않는다는 문제가 있다.

위 슬라이드에 나와있듯이 가우시안 분포로 작은 값으로 초기화하면,

더 깊은 레이어로 갈수록 activation function의 값들은 0에 가까워져 gradient도 0이 되어버린다.

그렇다고 initial weight을 큰 값으로 주면, activation의 출력 값은 양 끝단으로 몰리게 되고, 이러면 다시 gradient가 0이 되어버리는 문제가 생긴다.

그래서 initial weight의 적정 값을 얻기 위한 방식으로 'Xavier Initialization'을 사용하는데

이를 통해 괜찮은 activation을 레이어의 깊이와 상관없이 얻을 수 있다. 그리고 여기서 Din은 입력 데이터의 크기

Dout 은 출력 데이터의 크기이다.

Xavier Initialization은 Activation Function의 입력과 출력의 분산이 동일하게 하기 위해 고안된 것으로, 

위와 같은 전개과정으로 통해 구한 weight의 분산이 1/Din이면 입력과 출력의 분산이 동일하다는 사실을 이용하여

weight initialization을 수행한다.

하지만 Xavier Initialization은 activation function이 zero-centered 하다고 가정하여 사용하는 것이라, ReLU와 같이

zero-centered하지 않은 activation을 사용할 때에는 여전히 학습이 진행되지 않는다는 문제를 가지고 있다.

그래서 ReLU를 사용하는 경우에는 Var(W) = 2 / Din 이 되도록 weight을 초기화해준다.

강의에서는 구체적인 설명 없이 그냥 ReLU가 입력이 음수인 영역을 0으로 만들어서 입력의 절반을 없애버리는 효과를 주기에 Xavier에서의 분산에 2를 곱한다고만 하는데,

논문에 자세히 설명되어 있으니 궁금한 사람은 논문 한번 읽어보는 것도 괜찮다고 생각한다.

어쨌든 Kaimimg initialization을 사용하면 VGG에서도 특별한 방법을 사용하지 않고도 문제없이 학습이 진행된다.

 

그리고 여담이지만 파이 토치에서와 이 강의에서는 Kaiming initialization이라고 부르지만

텐서 플로우에서는 He Initialization이라고 한다. 

똑같은 initialization에 서로 저자의 성과 이름을 붙였는데 Kaiming He는 이거 보고 무슨 생각할지 궁금하다 ㅋ

어쨌든 요즘 CNN에서의 baseline 아키텍처인 Residual Network에서는 residual block의 분산이 계속 증가하기에 무작정 kaiming intialization을 적용하는 것은 그다지 효과가 없다. 그래서 Residual block에서 첫 번째 Conv 레이어는

kaiming intialization을 사용하고, 두 번째 Conv 레이어에서는 0으로 초기화해주는 식으로 모델을 구현한다.

 

지금부터는 모델이 오버핏 되는 것을 피하게 해주는 Regularization에 대해서 조금 더 배워볼 것이다.

이전까지의 강의에서는 L2와 L1, Elastic net에 대해서 배웠는데

 

그것들 말고도 Dropout이라는 방법도 있다. Dropout은 각 레이어에서 각 유닛의 값을 임의의 확률에 따라 0으로 만들어 버리는 방법을 뜻하며 보통은 50% 확률로 이를 정한다.

 

Dropout을 적용하는 것은 레이어에서의 중복된 정보를 가지는 것을 혹은 co-adaptation feature를 가지는 것을

막는 것이라고 할 수 있다. 또한 Dropout을 모델에 적용해서 학습하는 것은 parameter를 공유하는

거대한 앙상블 모델을 학습하는 것이라고 볼 수 있다.

 

Dopout을 적용하면 output이 랜덤하게 정해지는데, test time에서는 값이 랜덤하게 나오면 안 된다.

따라서 test time에서는 dropout이 만들어내는 값의 평균을 출력한다.

Dropout을 구현하면 좌측 슬라이드와 같이 나타낼 수 있는데, 실제로 사용할 때는 우측 슬라이드에서의

test time의 오버헤드를 덜어낸 'Inverted dropout'의 형태로 사용한다.

 

Dropout은 AlexNet이나 VGG에서 대부분의 parameter가 사용되는 fully-connected layer에서 효과적으로 작동하는데,

그래서 global average pooling을 통하여 parameter 수를 대폭 감소시키는 GoogLeNet이나 ResNet에서는

사용되지 않는다.

Dropout은 이전 시간에 배운 Batch Normalization에서와 같이 train time에 사용한 요소들의 평균을 test time에서

사용하는 공통된 패턴을 보이는데, 위에서 언급했다시피 요즘 사용되는 아키텍처에서는 그다지 사용되지 않는다. 

 

마지막으로 이미지를 처리하는 모델의 경우, 더 나은 정확도를 위해 이미지를 변형하는 Data Augumentation은

일반적인 방식으로 이미지를 무작위로 수정하거나 일부분을 잘라내는 식으로 이미지를 변형시켜 학습시키면

조금 더 robust 한 모델을 얻을 수 있다. 그리고 Data Augumentation은 단순히 이미지를 변형시키는 작업이 아니라

어떠한 이미지의 변화가 이미지의 레이블에 영향을 끼치는지 알 수 있기에 사람의 지식을 모델에 집어넣는 것으로도

볼 수 있다.

이어서 지금까지 강의에서 다룬 Regularization 말고도 여러 방식들을 알려줬는데, 시간 관계상 이런 게 있다 정도만 하고 넘어갔다. 그냥 뭐가 뭔지만 알아두면 될 거 같다.

요약

 

 

=====================================================================

어쩌다가 방학이 한 달도 안 남은 걸 생각하니 만사가 겁나 귀찮아서 할 게 좀 많이 밀렸다 ㅎ

Assignment # 3만 해도 한지 일주일은 넘은 거 같은데 포스팅은 이제 시작해야 한다 ㅋㅋㅋㅋ

일단 Assignment # 3-1부터 모레까지 끝내는 걸 목표로 열심히 해봐야겠다.

728x90