지난 시간에는 다양한 Activation Function과 각각의 특성을 배웠다. sigmoid나 tanh은 Vanishing gradients 문제가 있다. 요즘은 일반적인 네트워크에서 가장 잘 동작하기 때문에 주로 ReLU를 쓴다.
다음은 가중치 초기화이다. 가중치가 지나치게 작으면 activation이 사라진다. 작은 값이 여러 번 곱해지기 때문에 점점 그래디언트가 0이 되기 때문이다. 결국 모든 activation이 0이 되고 학습은 일어나지 않는다. 반대로 가중치가 너무 큰 값으로 초기화되면 그 값이 계속 곱해지기 때문에 결국은 터져버릴 것이다(explode). 따라서 Xavier/MSRA(HE) Initialzation 같은 방법으로 초기화를 잘 시켜주면 Activation의 분포를 좋게 유지할 수 있다.
가중치 초기화는 Network가 깊어지면 깊어질수록 더 중요해진다. Network가 깊어지면 깊어질수록 가중치를 더 많이 곱하게 되기 때문이다.
데이터 전처리에서 CNN은 zero-mean을 주로 사용하며, zero-mean, unit variance에 대해서도 배웠다. 정규화의 중요성 에 대해 예를 들어 설명할 것이다.
빨간점과 파란 점을 나누는 Binary classification 문제를 푼다고 가정하자. 왼쪽의 그림은 정규화되지 않은 데이터이다. 이 경우에 분류할 수 있지만 선이 조금만 움직여도 분류가 잘 되지 않는다. 왼쪽의 예시가 의미하는 것은 손실 함수가 아주 약간의 가중치 변화에도 엄청 예민하다는 것이다. 따라서 왼쪽의 경우 동일한 함수를 쓰더라도 학습시키기 아주 어렵다. Loss가 파라미터에 너무 민감하기 때문이다.
반면 오른쪽은 데이터의 중심을 원점에 맞추고(zero-center), Unit variance로 만들어 준 경우이다. 오른쪽은 선이 조금씩 흔들리는 경우에도 분류가 잘 된다. 따라서 오른쪽의 손실 함수는 가중치의 변동에 덜 민감하다. 이 경우에 최적화가 더 쉽고 학습이 더 잘된다.
Neural network 내부에도 다수의(interleavings) linear classifer가 있다고 생각할 수 있다. 이 경우에도 Neural network의 입력이 zero-centered과 Unit variance가 아니라면 레이어의 Weight matrix가 조금만 변해도 출력은 크게 변하여 학습이 어렵게 된다.
Normalization이 중요하므로 batch normalization도 배웠다. BN은 activations이 unit gaussian가 될 수 있도록(0과 1사이에 있도록) 레이어를 하나 추가하는 방법이다. BN에서는 forward pass시 미니 배치에서의 평균과 표준편차를 계산해서 Normalization을 수행했다. 그리고 레이어의 유연한 표현성을 위해서 scale, shift 파라미터를 추가했다.
다음은 학습 과정을 다루는 방법이다. 학습 도중 Loss curve가 어떻게 보여야 하는지도 배웠다. 가운데 그래프는 시간에 따른 Loss 값이다. 네트워크가 Loss를 줄이면 학습을 잘 하고 있는 것이다.
맨 오른쪽 그래프의 X는 시간축이고 Y는 성능 지표이다. 즉 오른쪽 그래프는 Training/Validation set의 성능지표를 나타낸다. Training set의 성능은 계속 올라가고(오른쪽 그래프) Loss도 계속 내려간다(가운데 그래프). 하지만 validation accuracy는 별 차이가 없다. 이 경우는 학습이 overfititing된 것이다. 따라서 추가적인 regularization이 필요하다.
마지막으로 hyperparameter search이다. 네트워크에는 많은 하이퍼파라미터가 존재하고, 이를 잘 선택하는 것은 중요하다. hyperparameter search에는 grid search와 random search가 있다. 성능이 특정 하이퍼파라미터에 의해 크게 좌우될 때는 더 넓은 범위로 그 파라미터를 탐색할 수 있기 때문에 random search가 더 좋다.
하이퍼파라미터 최적화 시에는 coarse search 이후 fine search를 한다. 처음에는 하이퍼파라미터를 조금 더 넒은 범위에서 찾고, Interation도 작게 주어 학습시킨다.(coarse) 그리고 결과가 좋은 범위로 좁힌다. iterations을 조금 더 돌리면서 더 작은 범위를 다시 탐색한다. (fine search) 적절한 하이퍼파라미터를 찾을 때까지 이 과정을 반복한다.
중요한 것은 coarse range를 설정할 때 가능한 최대한 넓은 범위를 설정해 줘야 한다는 것이다. 그 범위가 하이퍼파라미터 범위의 끝에서 끝까지 다 살펴볼 수 있도록 충분히 넓은 범위를 사용해야 한다.
Q. 보통 하이퍼파라미터를 몇 개씩 선택하는가?
그 선택은 모델에 따라 다르다. 선택한 하이퍼파라미터의 수가 많을수록 기하급수적으로 경우의 수가 늘어나기 때문에 한번에 너무 많이 선택할 수 없다. 얼마나 많은 자원을 학습에 사용할 수 있는지도 선택에 영향을 미친다. 2-3가지나 최대 4 가지 정도만 선택하는 것이 좋다. Learning rate가 제일 중요하므로 Learning rate를 가장 먼저 선택해야 한다.
Block Coordinate Descent(BCD) 같은 방법을 쓸 수도 있다. 우선 Learning rate를 정해놓고 다양한 모델 사이즈를 시도해 보는 것이다. 이 방법을 쓰면 기하급수적으로 늘어나는 Search space를 조금 줄일 수 있다. 하지만 이 방법은 어떤 순서로 어떻게 찾아야 할지 정하는 것이 가장 큰 문제이다.
Q. 어떤 하이퍼파리미터 값을 변경했을 때, 다른 하이퍼파라미터의 최적값이 변하는 경우가 있는가?
Learning rates는 이런 문제에 덜 민감하지만 실제로 이런 일이 가끔 발생한다. 오늘 배우게 될 fancier 최적화 방법을 사용하면 모델이 learning rate에 덜 민감하도록 할 수 있다.
Q. Low learning rate를 설정하면 local optima에 빠질 수 있지 않는가?
실제로 그런 일은 많이 발생하지 않는다.
Neural network에서 가장 중요한 것은 최적화 문제이다. Nerwork의 가중치에 대해서 손실 함수를 정의해 놓으면 이 손실 함수는 그 가중치가 얼마나 좋은지 나쁜지를 알려준다. 손실 함수는 가중치에 대한 "산(landscape)"이라고 할 수 있다. 오른쪽 사진에서 X/Y축은 두 개의 가중치를 의미한다. 각 색은 Loss의 값이다. 이 2차원의 문제를 두 개의 가중치 과 를 최적화시키는 문제라고 생각하자. 가장 붉은색인 지점, 즉 가장 낮은 loss를 가진 가중치를 찾는 것이 최적화의 목적이다.
가장 간단한 최적화 알고리즘은 Stochastic Gradient Descent(SGD)이다. 미니 배치 안의 데이터에서 Loss를 계산하고 'Gradient의 반대 방향"을 이용해서 파라미터 벡터를 업데이트한다. Gradient의 반대 방향인 이유는 손실 함수를 내려가는 방향이어야 하기 때문이다. 이 단계를 반복하면 결국 붉은색 지역으로 수렴할 것이고 Loss가 낮게 될 것이다.
하지만 이 SGD에는 몇 가지 문제가 있다. 손실함수의 가중치 과 가 있다고 가정하자. 는 수평축으로 가중치가 변하고 는 수직축으로 가중치가 변한다고 하면, 둘 중 하나가 업데이트되어도 손실함수는 매우 느리게 변한다. 수평 축의 가중치가 변하면 Loss가 아주 천천히 줄어든다. 수직 방향의 가중치 변화에 Loss가 더 민감하게 반응한다. 현재 지점에서 Loss는 bad condition number를 지니고 있다. 이 지점의 Hessian maxrix의 최대/최소 singular values 값의 비율이 안좋다.
그림처럼 Loss는 아래로 움푹파인 모양(taco shell)이다. Loss에 영향을 덜 주는 수평방향 차원의 가중치는 업데이트가 아주 느리게 진행되고, 수직 방향은 빠르게 변하므로 지그지그로 움직인다. 이렇게 움직이는 학습은 속도가 느리므로 바람직하지 않다.
이 문제는 고차원 공간에서 훨씬 더 빈번하게 발생한다. 가중치가 수억개라면 수억개의 방향으로 움직일 수 있다. 이런 수억개의 방향중에 불균형한 방향이 존재한다면 SGD는 잘 동작하지 않을 것이다. 수억개의 파라미터가 있다고 했을때 이런 불균형의 발생 비율은 상당히 높다. 고차원 공간에서 발생할 수 있는 이런 문제는 큰 문제이다.
SGD에서 발생하는 다른 문제는 local minima 와 saddle points이다. 오른쪽 그림에서 X축은 어떤 하나의 가중치이고 Y축은 Loss이다.
위쪽의 그래프에서 휘어진 손실함수는 중간에 "valley"(local minima)가 하나 있다. 이런 상황에서 SGD는 멈춘다(locally falt). 왜냐하면 gradient가 0이고, "opposite gradient"도 0이기 때문이다.
또 다른 문제는 saddle points이다. saddle points는 한쪽 방향으로는 증가하고 다른 한쪽 방향으로는 감소하는 지역이다. 여기서도 gradient는 0이므로 SGD는 멈춘다.
1차원의 예제에서는 local minima가 심각하고 saddle point는 덜 심각해 보이지만, 고차원 공간에서는 그 반대이다. 1억개의 차원에서 생각하면 saddle point는 빈번하게 발생한다. Local minima의 경우 1억 개의 방향을 계산했을 때 방향이 전부 Loss가 상승하는 방향이다. 고차원 공간에서는 그런 일은 매우 드물다.
매우 큰 neural network가 local minima보다 saddle point에 취약하다. 또한 saddle point의 근처에서도 문제가 발생한다. saddle point 근처에서 gradient는 매우 작다. gradient를 계산해서 업데이트를 해도 기울기가 아주 작기 때문에 현재 가중치의 위치가 saddle point 근처라면 업데이트는 아주 느리게 진행된다.
SGD의 또 다른 문제는 gradient의 추정값(noisy estimate)만을 구할 수 있다는 것이다. 손실함수를 계산할 때 많은 Traing set 각각의 loss를 전부 계산해야 한다. Loss를 계산할 때마다 전부를 계산하는 것은 어렵다. 그래서 실제로는 미니배치의 데이터들의 Loss를 계산하고 실제 Loss를 추정한다. 즉 gradient의 추정값(noisy estimate)만 구할 수 있다.
오른쪽 그림은 각 지점의 gradient에 random uniform noise를 추가하고 SGD를 수행하게 만든 것이다. 이 그림은 과장된 것이라 실제로 SGD가 이런식으로 동작하진 않지만 이 예제를 통해 gradient에 noise가 들어가면 어떻게 되는지 알 수 있다. 손실함수 공간을 비틀거리며 돌아다니면 minima까지 도달하는데 시간이 오래 걸릴 것이다.
Q. SGD를 쓰지 않고 GD를 쓰면 이런 문제가 전부 해결되는가?
full batch gradient descent에서도 taco shell에서의 문제가 발생한다. Noise는 네트워크의 explicit stochasticity로 발생한다. Saddle points 또한 full batch GD에서 문제가 된다. 따라서 full batch gradient descent를 사용해도 이런 문제들이 해결되지는 않는다.
이런 문제를 해결하기 위해 더 좋은 최적화 알고리즘이 필요하다. 대부분의 문제를 해결할 수 있는 간단한 방법은 SGD에 momentum term을 추가하는 것이다.
왼쪽은 gradient 방향으로만 움직이는 SGD이다. 오른쪽은 SGD + momentum이다. SGD + momentum은 gradient를 계산할 때 velocity를 이용한다. 즉 미니배치의 gradient 방향과 velocity를 함께 고려한다. momemtum의 비율인 하이퍼파라미터 rho를 추가한다.
velocity에 일정 비율 rho(보통 0.9, 0.99)를 곱해주고 현재 gradient를 더한다. 그 결과 velocity vector의 방향으로 나아간다.
SGD + momentum이 어떻게 SGD의 문제를 해결하는지 살펴보자.
local minima와 saddle points 문제는 공이 굴러내려오는 것과 같다. 공은 떨어지면 가속도가 붙어 속도가 점점 빨라진다. 공은 local minima에 도달해도 여전히 velocity를 가지고 있기 때문에 gradient = 0 이라도 움직일 수 있다. 따라서 local minima를 극복하고 계속해서 내려갈 수 있다.
Saddle Point에서도 비슷한 이유로 잘 내려간다. 또한 saddle point 주변의 gradient가 작더라도, 굴러내려오는 속도가 있기 때문에 velocity를 가지므로 계속 밑으로 내려올 수 있다.
업데이트가 잘 안되는 경우(poor conditioning)
지그재그로 움직이는 상황이라면 momentum이 이 변동을 서로 상쇄시킨다. 이를 통해서 loss에 만감한 수직 방향의 변동은 줄여주고 수평방향의 움직임은 점차 가속화된다. 따라서 momentum을 추가하게 되면 high condition number problem을 해결하는 데 도움이 된다.
Noise 문제
오른쪽 그림에서 검은색이 일반 SGD이고 파란색이 Momentum SGD이다. Momentum을 추가해서 velocity가 생기면 noise는 평균화된다. 보통의 SGD가 구불구불 움직이는 것에 비해서 momemum은 minima를 향해서 더 부드럽게 움직인다.
Q. 어떻게 SGD Momemtum이 poorly conditioned coordinate 문제를 해결할 수 있는가?
velocity estimation term에서 velocity는 gradient를 계속해서 더해간다. velocity는 하이퍼파라미터인 rho에 영향을 받고 현재 gradient는 상대적으로 작은 값이다. rho가 적절한 값으로 잘 동작한다면 velocity가 실제 gradient보다 더 커지는 지점까지 조금씩 증가할 것이다. 이는 poorly conditioned dimension에서 더 빨리 학습될 수 있도록 도와준다.
왼쪽 그림은 SGD momentum이다. 빨간 점이 현재 지점이고, Red Vector는 현재 지점에서의 gradient의 방향이며 Green vector는 Velocity vector이다. 실제 업데이트는(autual step) 이 둘의 가중평균이고, 이는 gradient의 noise를 극복하게 한다.
Nesterov momentum(Nesterov accelerated gradient)은 Momentum의 변형이다. Nesterov momentum은 빨간 점에서 시작해서 Velocity방향을 예측하여 그 지점에서의 gradient를 계산하고, 원점으로 돌아가 둘을 합친다. velocity의 방향이 잘못되었을 경우에 현재 gradient의 방향을 더 활용할 수 있도록 해준다.
Nesterov는 Convex optimization 문제(볼록한 경우)에서는 뛰어난 성능을 보이지만 Neural network와 같은 non-convex problem에서는 성능이 보장되지는 않는다.
Nesterov의 수식은 다음과 같다. velocity를 업데이트하기 위해 이전의 velocity()와 ()에서의 gradient를 계산한다. step update()는 앞서 계산한 velocity를 이용해서 구해준다. 여기서 velocity의 초기값은 항상 0이다. (움직이지 않은 고정된 점이기 때문)
velocity은 이전 gradients의 weighted sum이고, 더 최근의 gradients에 가중치가 더 크게 부여된다. 매 스텝마다 이전 velocity에 rho(0.9 or 0.99)를 곱하고 현재 gradient를 더한다. 이것이 moving average이다. 시간이 지날수록 이전의 gradient들은 exponentially하게 감소한다.
Nesterov의 공식은 까다롭게 생겼다. Momentum에서는 Loss와 Gradient를 같은 점()에서 구했지만 Nesterov는 이 규칙을 조금 바꾸었다. 그러나 변수들을 적절히 잘 바꿔주면 Loss와 Gradient를 같은 점에서 계산할 수 있다.
수정된 수식을 통해서 Nesterov를 다시 이해할 수 있다. 첫 번째 수식은 기존의 momentum과 같다. velocity와 계산한 gradient를 일정 비율로 섞어준다(). 두 번째 사각형의 맨 밑의 수식을 보면, 현재 점()과 velocity()를 더해준다. 거기에 "현재 velocity - 이전 velocity"에 일정 비율(rho)을 곱한 것을 더해준다. Nesterov momentum는 현재/이전의 velocity간의 에러보정(error-correcting term, )이 추가되었다.
기본 SGD은 검정색인데 천천히 내려간다. 파란색이 momentum, 초록색이 Nesterov이다. momentum 방법은 이전의 velocity의 영향을 받기 때문에 처음에는 minima를 그냥 지나친다. 하지만 스스로 경로를 수정하고 결국 minima에 수렴한다.
Nesterov은 추가된 수식 때문에 momentum과 조금 다르게 움직인다. 일반 momentum에 비해서 overshooting이 덜 한 것을 알 수 있다. 그러나 NN에서는 사용하지 않는다.
Q. 이 예시만 보면 momentum이 좋아 보이는데 만일 minima가 엄청 좁고 깊은 곳이라면 어떻게 되는가? momentum의 velocity가 오히려 minima를 건너 뛰는 현상이 발생할 수 있지 않을까?
좁고 깊은(sharp) minima는 좋은 minima가 아니다. 그런 곳에 도달하는 것도 좋지 않다. 좁고 깊은 minima는 더 심한 overfit을 불러온다. 예를 들어 Training set이 2배 늘어나면, 최적화 시키는 산의 지형(landscape) 자체가 바뀐다. Training data이 더 많이 모이면 좁은 minima는 점점 사라진다.
우리가 원하는 minima는 아주 평평한 minima이다. "아주 평평한 minima"는 Training data의 변화에 좀 더 강인할 수도 있다. 평평한 minima가 더 일반화를 잘 할 수도 있으며, Testing data에도 더 좋은 결과를 얻을 수 있다. momentum이 좁고 깊은 minima를 지나치는 것은 버그가 아니라 momentum의 특징이다. (momentum의 장점)
최적화 방법 중에 AdaGrad도 있다. 현재 Stanford에 계시는 John Duchi 교수님께서 Ph.D 시절에 제안하신 방법이다. AdaGrad는 훈련 도중 계산되는 gradients를 활용하는 방법이다. Adagrad는 학습 도중에 계산되는 gradient를 제곱해서 더해준다. Update할 때 Update term을 앞서 계산한 gradient 제곱 항으로 나눈다. (스텝 사이즈를 줄여준다.)
Condition number인 경우 빨간색 박스 안의 값은 어떻게 될까? 2차원 좌표가 있다고 하자. 그 중 한 차원은 항상 gradient가 높고 다른 하나는 항상 작다. Small dimension에서는 gradient의 제곱 값의 합이 작다. 이 작은 값이 나눠지므로 속도가 빨라진다. Large dimension에서는 gradient가 큰 값이다. 따라서 큰 값이 나눠지므로 속도가 줄어든다.
AdaGrad에는 문제가 하나 있다. 학습이 계속 진행되면 학습 횟수 t가 계속 늘어나기 때문에 AdaGrad는 step을 진행할수록 값이 점점 작아진다. update 동안 gradient의 제곱이 계속해서 더해지므로 이 값(estimate)은 서서히 증가한다. 이는 Step size를 더 작은 값이 되게 한다. (처음에는 오버슈팅이 일어남)
손실함수가 convex한 경우(볼록한 경우)에 Step size가 점점 작아지는 것은 좋은 특징이다. convex case에서는 minimum에 근접할수록 서서히 속도를 줄여서 minimum에 수렴할 수 있게 하면 좋다. 하지만 non-convex case에서는 문제가 될 수 있다. 예를 들어 saddle point에 걸리면 AdaGrad는 멈출 수 있다.
RMSProp은 AdaGrad의 변형이다. RMSProp는 앞서 언급한 문제를 개선시켰다. RMSProp에서는 AdaGrad의 gradient 제곱 항을 그대로 사용하고 기존의 누적 값에 decay_rate를 곱해준다. 이 값은 기존의 momentum 수식과 유사하게 생겼으나 gradients의 제곱을 계속해서 누적한다. RMSProp에서 gradient 제곱 항에 쓰는 decay rate는 보통 0.9 또는 0.99정도로 하면 된다. '현재 gradient의 제곱'은 (1 - decay rate)를 곱해줘서 더해준다. RMSProp은 gradient 제곱을 계속 나눠준다는 점에서 AdaGrad와 유사하지만 위와 같은 특징으로 점점 속도가 줄어드는 문제를 해결할 수 있다.
그림에서 SGD는 검정색이고 momentum은 파란색, RMSProp은 빨간색이다. RMSProp이나 momentum은 기본 SGD보다는 훨씬 더 좋다. 그러나 이 둘의 행동양상은 조금 다르다. momentum은 overshoots한 뒤에 minima로 돌아오나 RMSProp은 각 차원마다의 상황에 맞도록 적절하게
궤적(trajectory)을 수정시킨다.
그림에는 동일한 Learning rate로 학습시킨 초록색으로 그린 AdaGrad도 있다. 하지만 Learning rates가 점차 감소하기 때문에 RMSProp에 가려서 보이지 않는다. AdaGrad의 Learning rate를 늘리면 RMSProp과 비슷한 동작을 할 것이다. 하지만 일반적으로 Nerural Network를 학습시킬 때 AdaGrad를 잘 사용하지 않는다.
Q. convex인데 왜 Adagrad에게 불리한가? (convex case)
Learning rates가 서로 다르기 때문이다. 여러 알고리즘 간에 '같은 Learning rates'를 가지고 AdaGrad를 visualization하는 것은 공정하지 못하다. visualization을 하고자 한다면 알고리즘별로 learning rates를 조정하는 것이 좋다.