지난시간까지는 weight 초기화하는 방법에 대해 배웠다. activation func에 따라 다른 weight초기화 방법을 썼었다.
그렇게 하면 Layer를 더 쌓더라도 activation value(output)의 평균과 표준편차가 일정하게 유지되어 성능이 유지되었다.
예를 들어,
sigmoid, tanh에서는 Xavior initialization(Normalized initialization)  - keras에서는 glorot_intialization
relu에서는 He initialization

오늘은 3가지 딥러닝 튜닝의 마지막 시간이다.
activation func -> weight initialization -> optimization


Optimization


코드상 Multi-Layer Neutral net / feed-forward Neural net은 아래와 같았다.


image

Optimizer는 실제로 Gradient descent로 w를 업데이트 하는 알고리즘이라고 보면 된다. 그리고 이 optimizer는 튜닝할 여지가 많이 남아있다.

최근에 Optimizer가 논란거리가 있다고 한다. 이론적으로 검증이 안되었음에도 벤치마킹에 의해 퉁치고 넘어가는 경우가 많았다.
벤치마킹 결과가 잘나오면 그게 대세가 되는 것이다. 그런데 알고봤더니 그게 그렇지 않은 경우가 있다.
2017년 후반과 2018년에 Optimizer를 이해 및 쓰는 알고리즘 트렌트가 바뀌었다고 볼 수 있다.


간단한 Overview를 해보자. 앞에서 우리는 Gradient Descent 알고리즘를 배웠다.
Cost(Loss)의 w편미분을 통해 w의 변화량을 구해서 가져와, Gradient Descent를 태우면  랜덤으로 초기화된 w라 하더라도 어느순간 좋은w를 찾아가는 train이 이루어진다.


그라디언트 디센트를 이해하기 위해 2차원(x축 w는 1개 뿐 - y축 cost(loss) func)의 그래프를 학습했었다.
초기화된 랜덤한 w점에서 Graident Descent가 업데이트 시키기를 점점 접선기울기가 0인 지점(cost or loss 최소값)을 찾아갔었다.
그 이동하는 간격이 동일하게 톡톡~ 이동할 것 같지만, 그렇지 않고,
w를 업데이트를 하는 순간, 기울기가 가파른 순간에는 크게 내려간 다음 ,기울기가 작아지면 조금씩 업데이트 된다.
image



하지만 Gradient를 태워 w를 업데이트 시킬 때 중요한 문제 중 하나가 아래와 같은 Cost(loss)를 가질 때다.
평평하게, 뭉뚝하게 생긴 부분에서 업데이트 속도가 매우 느려지는 문제이다.

ReLU를 activation function으로 쓰면, cost 함수가 아래와 같이 뭉뚝한 부분이 없어져서 업데이트 속도가 빨라진다.
그러나 ReLU사용이 불가능한 경우도 있고, 미분가능함수를 사용하다보면  뭉뚝한 부분 많은 경우도 많기 때문이다.

실제로 train 시키다 보면, 100번 train시킬 때,
한 3번 정도는 loss가 처음에는 툭! 떨어지다가, 97번은 계속 제자리에 멤도는 수준으로 느리게 w가 업데이트 된다.

이러한 문제를 해결하기 위해, Optimization(그라디언트 태우는 알고리즘)을 무엇으로 선택할지가 중요한 문제이다.

image


Local Optima(minimum)과 Global Optima(minimum)

어떤 Loss function이 있을 때, 그것을 시각화해서 이해했다.
맨 처음 이해를 위해, Regression문제를 해결하는데 있어서, loss function을 MeanSquareError(2차 함수)를 학습했다.

하지만, 실제 우리가 loss function을 사용할 때는, 단순한 2차 함수만 나오는 것이 아니다.
아래와 같은 울퉁불퉁한 n차 함수가 나올 수 있다.

image


아래와 같이 울퉁불퉁한 loss function이 있다고 가정하자. x축은 w, y축은 loss function이다

.초기화w에서 시작하여, graident descent를 타고 내려갔는데,,
아직 최종목표(loss 최소값 = Global Optima)에 도달하지 않았음에도 불구하고.
어느 지점에 들어가니까 평평하고, 더 진행하려다 보니까 loss가 늘어나므로, w를 멈추게 된다.
이러한 지점을 Local Optima(minimum)이라 하고, Optimization을 잘못한 경우 로컬옵티마에 빠지기 쉽다고 얘기한다.
image

하지만 요즘 trend에 의하면, 로컬옵티마는 중요한 문제가 아니라고 한다.
실제 딥러닝에서는 로컬옵티마에 빠질 확률이 거의 없기 때문이다.
위의 그래프는 w가 1개인 모델이지만 실제 딥러닝 모델에서는 w가 수도없이 많으며, 그 수많은 w가 모두 로컬옵티마에 빠져야 w업데이트가 정지되기 때문이다. 이론적으로는 그것이 불가능하기 때문에

로컬옵티마를 해결하기 위해서 Optimization을 할 이유는 없다고 보면 된다. 


기존에는 로컬옵티마를 해결하기 위해 Optimization을 한다고 했었지만... 로컬옵티마는 고려할 필요는 없다고 하는 것이 추세다.
그렇다면 요즘에 Optimization을 쓰는 이유는 무엇일까?


Plateau

아래와 같이 w가 저 위치에 초기화 되었다고 가정해보자.
Gradient Descent를 타고 Global Optima를 향해서 나아가는데, 평지(Plateau)가 생겨 통통통 튀다가 더이상 loss가 업데이트 되지 않는 현상이 발생한다. 이러한 것을 Plateau현상이라 한다. 그리고 이 플래튜현상이 로컬옵티마에 비해 발생확률이 무지하게 높다.

image

예를들어, w가 2개(x축, y축)이고, loss를 점으로 찍는 scatter(3차원)를 그린다고 가정해보자.
이러한 평면에서, x축과 y축이 동시에 평평해질 확률은 꽤 높다고 한다.  실제 우리가 살고 있는 지구를 봐도 그렇다.
x축과 y축이 있다고 가정하면 평평한 땅이 얼마나 많은 가?
아무리 w가 늘어나더라도 Plateau는 항상 발생한다는 것을 알 수 있다.


우리는 이 Plateau현상을 극복하기 위해서 Gradient를 Optimization할 것이다.



Zigzag현상


이전에 지그재그 현상에 대해서 배웠다. dL/dw2 를 체인룰로 풀 때, 3개의 곱 중에 2개가 양수로 부호가 같이 나오는 sigmoid or ReLU를 사용하는 경우에는, w업데이트 행렬의 부호가 모두 동일하여, 원하는 방향으로(+ 과 - 조합)으로 못가서, 지그재그로 w목표점을 찾아가는 현상이었다.
 

Gradient Descent를 시각화하는 방법 중 하나로, w가 2개인 loss(cost)function의 3차원 그래프를
등고선을 활용하여 scatter형식으로 2차원으로 그려보자.

image


이번에는 조금 찌그러진 gradient( Skewed gradient)을 시각화해보자.
w1방향(x축)으로는 길고, w2방향(y축)으로는 짧은 구조이다.
- w1 폭 넓다 -> 업데이트는 많이 해줘야하지만, 3차원상으로 완만하여 조금밖에 못간다
- w2 폭 좁다 -> 업데이트를 조금만 해줘야하지만, 3차원상으로 가팔라서 많이씩 간다.
이러한 Skewed gradient에서는 ZigZag현상이 일어날 수 밖에 없다.
image

결과적으로 좋은 Optimization 알고리즘( gradient태울 때, w변화량 구하는 알고리즘)은

1) Plateau 현상을 해결해서 w를 업데이트 해준다.
2) weight space가 Skewed된 Gradient에서 이러하는 ZigZag현상을 해결해서 업데이트 해준다.


지금까지 쓴 Gradient Descent 알고리즘(SGD)은 Local Optima, Plateau, ZigZag현상을 해결하진 못한다.
그럼 어떤 Otimization 알고리즘을 사용해서 Gradient를 태워야할까?


Optimizer의 종류


Optimization 알고리즘(gradient 태우는 방식)의 개선된 방식은 크게 모멘텀 방식어댑티브 방식 2가지로 나뉜다.


optimization
우리가 여태 썼던 optimizer는 sgd(keras기준, stochastic gradient descent)이다.
- sgd : 언제나 느리다.

1. 모멘텀 방식의 optimizer : 속도를 최대한 빠르게!
- 초록색(momentum). 보라색(nag, Nesterov Accelerated Gradient = Nesterov Momentum) : 스타트부터 엄청빠르게 global optima에 들어간다. 휘기는 하나 엄청 빠르다.
- 단점 : 속도는 빠르지만, skewed된 weight space에서는 휜다.

2. 어댑티브 방식의 optimizer : 방향을 최대한 일직선으로!
- 파란색(adagrad) ,노란색(adadelta), 검은색(rmsprop) : 속도는 빠르지 않지만, skewed한 상황에서도 지그재그없이 일직선으로 들어가려 한다.
- 단점 : 속도는 느리다.


이 Optimization 의 2가지 방식에서는 작년(2017)과 올해(2018)과 트렌드가 달라졌다.
작년까지는 모멘텀 + 어댑티브 방식을 합친 Adam 알고리즘을 선호했는데
요즘엔, 어댑티브 방식이 크게 쓸모없다는 여론이 커졌다. 그래서 모멘텀 방식 위주로 가고 있다고 한다.

결과적으로, 우리는 Optimizer를 고를 때,
Momentum 방식인 adagrad or rmsprop을  고르면 된다.



Optimizer을( Gradient 태우는 알고리즘) 하나씩 살펴보자.


1. 가장 기본적인 SGD(Stochastic Gradient Descent)

cost function 편미분 -> 접선의 기울기를 가지고 w업데이트하는 방식의  gradinet
-> 단점 4가지(뭉뚝한부분에서 느림, 로컬 옵티마, 플래튜, 지그재그현상) 를 모두 가져서 w도달까지 오래걸린다.


2. Momentum -> 단점 4가지 중 3가지를 해결한다.

sgd가 cost func을 편미분(접선의 기울기)를 가지고 w를 업데이트 했다면,
momentum현재의 기울기 + 이전의 기울기를 포함하여 누적된 가속도로 w를 업데이트를 함. (관성의 개념)

image

간단히 말해, 현재 w를 업데이트할 때, 이전 w업데이트량만큼 계속 가속도를 붙혀, 업데이트 속도가 빨라진다.
그 결과,
- 뭉뚝한 부분에서, 현재의 속도가 느려서 업데이트 못했었는데, 이전 업데이트량이 포함되어있으니 그 구간에서도 스피드가 빨라짐
- 로컬 옵티마에서, 타고내려가다가 현재가 평평해서 업데이트 안해줄 상황에서도, 이전 업데이트량에 의해 통과해버린다.
  글로벌 옵티마에서도 뛰어넘는 현상이 발생하여(위의 그림) loss와 accuracy를 보면 튀는 현상이 있는데, 결국엔 돌다가 들어온다.
- 플래튜에서, 현재 그라디언트(접선의 기울기)는 0이지만, 이전의 업데이트량이 포함되어, 계속 진행된다.
(지그재그현상은 해결안된다.)
image


코드상에서 원리를 살펴보자.
1) dw1( 이전 w업데이트량 )을 캐싱하여 가지고 있는 상태에서,
2) mu(뮤)라는 dw1 반영 비율(보통 0.9)을 생성하여 반영시켜주면 된다.
3) 현재 업데이트량에 더해서 업데이트 시켜준다.

그러면 w업데이트속도가 빨라져서 3가지 단점을 해결할 수 있다.

image


Momentum의 단점Global Optima에서도 이전 가속도가 더해지기 때문에, 추월하는 현상이 발생하는 것이다.

그래서 이전 가속도를 조금 더 줄여서 반영하고 싶은데, 그러한 Optimizer가 바로 Nesterov Momentum이다.



3. Nesterov Momentum

Momentum현재 그라디언트(접선의기울기, w업데이트량)과 이전 누적된 그라디언트를 각자 구해서 더한다고 했다.
Nesterov Momentum은
1) 먼저 Momentum을 먼저 진행한 상태에서,
2) 그 상태에서의 그라디언트를 계산하여 더하면,
추월하는 현상이 줄어든다.

image


코드상으로는 너무 복잡하므로 변형공식을 이용해서 코드를 작성한다.
모멘텀처럼, 현재 그라디언트 +이전 그라디언트를 모두 구한다음에,  이전 그라디언트의 누적치를 빼주는 방식으로 쓴다.
결과적으로 momentum보다 추월을 좀 더 하는 느낌이다.


하지만, 성능이 너무 크게 향상되지 않기 때문에

우리는 default Optimizer로 Momentum을 쓰면 된다.




4. AdaGrad ( 어댑티브 방식 ) : 속도를 높이는 것보다, 일직선으로 향하게 하는 방식 첫번째


요즘은 쓰지 않는 어댑티브 방식이다. 하지만 내부적으로 모멘트 방식과 섞으려는 연구가 진행중이다.
작년까지는 Adam이라는 알고리즘이 대세였지만, 어댑티브 방식이 안좋다는 증명때문에, 모멘트 방식을 주로 사용한다.
하지만 경험적으로 모멘텀 방식( 모멘텀, 네스테로브 모멘텀)보다 가끔, 어댑티브 방식인 RMSProp이 성능이 더 좋을 때도 있다. 하지만 이유가 증명되지 않았기 때문에, 아직까지는 모멘텀만 사용하기를 권하고 있다.


어댑티브 방식은 Skewed 상황에서 w1/w2의 필요한 업데이트량과 정반대로 업데이트되어 느린 zigzag 현상
각 w의 업데이트량을 보정해서 해결하는 방식이 AdaGrad이다.

아래 그림처럼, SGD나 모멘텀방식은 속도만 빠를뿐, Skewed한 weight space에서 zigzag현상을 해결하진 않는다.
어댑티브 방식인 AdaGrad를 사용할 경우, w1, w2의 업데이트량을 보정하여 휘는 정도가 줄면서 일직선에 가깝게 w를 업데이트한다.

image

w1을 그냥 대입하는 것이 아니라, 제곱한 것을 캐슁해놓고 루트씌워서 w1업데이트량에 나눠서 보정하는 방식이다.
1)  dw1의 현재 그라디언트(접선의기울기, w1 업데이트량)를 제곱하여, (-)음수도 양수가 되게 한다. 그 부분을 캐슁해놓는다.
2) 거기다 루트씌워서 크기만 원상복귀하여 마이너스만 제거한다.
3) 이제 w1업데이트부분에서 dw1이 아닌 dw1 / 루트( 캐싱한 부분 )을 나누어준다.
업데이트량이 크다면 크게 나누어지고, 작으면 작은값을 나누어주어서, Normalization효과가 일어나게 된다.


첫번째 업데이트시에는, 제곱한걸 더해서 캐슁해놓는 부분이 없기 때문에, 업데이트량 dw 부분은 1이 될 것이다.
하지만 그 다음부터는 이적 누적치가 반영되기 때문에,, 이전에 뛴만큼 누적치로 나누어줘서 보정된다.

결과적으로는 w1과 w2의 업데이트량이 비슷해지는 효과가 난다. -> 가능한 일직선으로 업데이트 된다.

cf) 0.000001 을 넣어주는 이유는, 나눗셈시 혹시나 0이 나올 때를 에러방지하기 위해, 아주작은 양수를 넣어두는 것


요약 ) 제곱하고 루트씌워서, 양수의 자기자신을 만든 다음에, 나누어주는 것!  + 제곱부분해준 부분만 누적되어 캐싱


AdaGrad의 단점은?
실행하면 실행할수록 느려진다. 그 이유는 제곱부분(루트씌우기전) 캐싱부분에서는 끊임없이 누적되기 때문에..
나누어주는 수가 커지니까 w업데이트가 너무 느려진다. 결국엔 0이 될 것임.



5. RMS Prop(어댑티브 2번째 방식) : AdaGrad를 개선한 어댑티브 방식


AdaGrad가 제곱부분을 계속 캐슁할 때, 캐쉬값이 너무 커져서, 업데이트량에 나누어주는 값이 너무 커져 업데이트가 안되는 현상이 발생했다.

이 때, 이전에 제곱되어 누적된 캐슁되어있는 값새롭게 제곱되는 부분의 반영비율decay라는 상수반영비율을 조절한다.
마치, Momentum의 mu로 이전 업데이트량이 더해지는 부분의 반영비율을 조절하는 것과 마찬가지다.

image

만약, decay가 0.9라면, 이전의 누적된 그라디언트의 제곱은 90%반영해서 누적,  현재 그라디언트의 제곱10%반영 되는 것이다.
만약 속도가 느려졌다면, decay를 줄여 캐쉬값의 반영을 줄인다면, 나누는 부분이 작아져서, 업데이트 속도가 올라갈 것이다.


이렇게 개발된 RMSProp은 토론토 교수가 논문으로 작성하지 않아서, 인용이 안된다는 재밌는 소식이 있다.
작년까지 가장 좋은 Optimization 알고리즘은 Momentum  +  RMSProp을 섞어쓴 Adam이었는데, 지금은 모멘텀이 대세라고 한다.
그런데, 경험적으로, 모델을 train하다보면 RMSProp이 좋을 때도 있다.
결과적으로, Momentum을 쓰고 잘 안된다 싶으면, 한번씩 RMSProp으로 바꾸어 lr,deacy를 잘 조절하면 성능이 좋아진다. (증명되진 않음, 논문도 없음)


cf)
Optimization의 방식은 크게 2가지가 있다.
- Iterator방식(Gradient를 태워 backpropagation을 이용해서 for문을 돌면서 w업데이트) : 응용이 가능하고 범용적인 방법
- 수학적방식 : ...?


6. Adam :  Momentum(모멘텀 방식) + RMSProp(어댑티브 방식)을 섞은 것

작년까지 대세였던 Optimization 알고리즘이 바로 이 Adam이다.
모멘텀방식 ( 아래 파란색 ) + 어댑티브RMSProp 방식( 아래 빨간색 )을 합쳐놓았다.
이 때, mudecay 대신 beta1 과 beat2를 decay방식( decay, 1-decay)으로 사용된다.
보통 beta1은 mu처럼 0.9를 , beta2는 decay는 0.99를 사용한다.
1) 현재의 그라디언트를 구한다(dw1)
2) dw1m에다가 현재의 그라디언트를 더해서 계속 누적시키는데, beta1이라는 상수를 사용해서, 누적량과 현재량의 반영비율을 조절한다.
3) dw1v에다가 현재의 그라디언트틀 제곱해서 계속 누적시키는데, beta2라는 상수를 사용해서 ,누적량과 현재량의 반영비율을 조절한다.
4) 그리고 더해서 누적한것 에다가 루트 (제곱해서 누적)시킨 것을 나누어서 업데이트량으로 삼는다.

image


Adam에서 decay를 beta1을 0.9를 사용한다고 했다.
첫 epoch에서는 캐쉬값이 없기 때문에, 0.9* 0 + 0.1 * 현재 그라디언트 값이 캐슁될 것이다. 그러면 0.1만 반영되므로
초기 w업데이트 속도가 느릴 것이다. 이게 Adam의 단점이다.
결국에는 빨라지긴 하나, 초기에 느려지는 것이 싫을 때 쓰는 방식이 Adam의 Warm start버전이다.


7. Adam w


image

4, 5번째 줄에 dw1mb와 dw1vb가 새로 생겼다. 기존 Adam의 dw1m, dw1v를 epoch을 이용해서 보정해준다는 의미일 것이다.

첫 루트의 dw1mb를 보자. 코드상으로는 epoch은 0일 것이다.
1) 1 + epoch = 1
2) beta1 의 1제곱 -> beta1 그대로 0.9
3) 1 - beta1 = 0.1
4) dw1m에 0.1를 나눔 = 10을 곱한 것 --> 업데이트량도 10배 늘어난다.

5) epoch이 늘어남 -> beta1인 0.9(소수점)의 제곱 승수가 커짐 (전체값 줄어듬) -> 1 에서 더작은량이 빠짐 -> dw1m에 더 큰값이 나눠짐 -> dw1mb 작아짐 -> 업데이트량 작아짐

-> 초반에 느려진 것을 epoch을 이용해서 초반에 빠르게 하고, epoch이 커질 수록 dw1m에서 나누어지는 값이 1에 가까워서 Adam처럼 됨.




등고선이 굉장히 꼬여있는 Skewed된 상황에서
sgd : 안가는 수준
Momentum, Nesterov Momentum (모멘텀) : 가속도로 확치고올라가지만, 많이 휜다.
AdaGrad, RMSPROp (어댑티브) : 빠르진 않지만 zigzag없이 일정한 방향을 유지할려고함




Rmsprop > Adagrad (어댑티브가 먼저 들어감) -> Nesterov M -> Momentum -> SGD는 거의 안감
이것을 보면, 어댑티브가 좋은 것 같지만, 요즘은 성능이 안좋다는 이유로 모멘텀을 쓴다.

결과적으로 우리는 Optimization 알고리즘으로
Momentum을 쓰고, 안되면 RMSProp도 한번씩 써보자.

image



실습

로컬 노트북

+ Recent posts