[밑바닥부터 시작하는 딥러닝] 6. 학습 관련 기술들 part1 - 매개변수 갱신

Yejin Kim·2022년 3월 19일
0

🌿 매개변수 갱신

신경망 학습의 목적은 손실 함수의 값을 가능한 한 낮추는 매개변수를 찾는 것
→ 매개변수의 최적값을 찾는 것

이러한 문제를 푸는 것을 최적화(optimization) 이라고 한다

📍 확률적 경사 하강법(SGD)

Stochastic Gradient Descent

  • 최적의 매개변수 값을 찾는 단서로 매개변수의 기울기(미분)을 이용
  • 매개변수의 기울기를 통해 매개변수 값을 갱신하면서 최적의 값에 다가감

SGD의 수식에서 알 수 있다시피 SGD는 기울어진 방향으로 일정 거리만 가겠다는 단순한 방법임

구현은 다음과 같다.

class SGD:

    def __init__(self, lr=0.01):
        self.lr = lr
        
    def update(self, params, grads):
        for key in params.keys():
            params[key] -= self.lr * grads[key] 

lr는 learning rate(학습률)을 뜻하고, update(params, grads) 메서드는 SGD 과정에서 반복해서 불린다.

반복해서 최적화시켜주면서 매개변수를 갱신 !

SGD의 단점

SGD는 단순하고 구현도 쉽지만, 문제에 따라서 비효율적일 때가 있다

  • 등방성 함수

    등방성 : 어느 방향에서 봐도 똑같은 성질을 가지고 있음

함수에서는 정의에서의 '성질'을 '기울기' 라고 해석할 수 있다.
등방성 함수의 예시는 다음과 같다.

ex) f(x, y) = x^2 + y^2

이 함수의 기울기는 다음과 같다.

등방성 함수는 각 위치에서 기울어진 방향의 본래의 최솟값을 가리킴을 알 수 있다. 따라서 등방성 함수의 경우 SGD를 이용해도 무방 !

  • 비등방성 함수

    비등방성 : 방향에 따라서 물리적 성질이 바뀌는 것

비등방성 함수는 이름에서도 알 수 있다시피 각 위치에서의 기울기가 가리키는 지점이 하나가 아니라 여러개이다.

비등방성 함수의 예시는 다음과 같다.

이 함수는 '밥그릇'을 x축 방향으로 늘인 듯한 모습을 하고 있고, 등고선은 x축 방향으로 늘인 타원의 형태이다.

함수의 기울기는 다음과 같다.

기울기가 y축 방향은 크고 x축 방향은 작다는 것이 특징이다.
위의 그림에서 알 수 있듯이 기울기의 대부분은 최소값의 위치인 (0,0)을 가리키지 않는다.

이 상태에서 SGD를 적용하면 결과가 다음과 같다.

상당히 비효율적인 움직음으로 매개변수가 갱신되고 있음을 알 수 있다.

이런 결과가 나오는 이유는 기울어진 방향이 본래의 최솟값과 다른 방향을 가리켜서라는 점을 고려하여 무작정 기울어진 방향으로 진행하는 방식보다 더 영리한 묘안을 생각해내야 한다.


📍 모멘텀 (Momentum)

모멘텀은 '운동량' 을 뜻하는 단어
물리에서 p(운동량) = m(질량) * v(속도) 인데, 신경망에서는 질량을 1로 두고 운동량을 속도로만 나타낸다.

모멘텀 기법은 수식으로 다음과 같이 나타낸다.

여기서 v 변수는 속도에 해당하고, 기울기 방향으로 힘을 받아 물체가 가속된다는 물리법칙을 수식으로 나타낸 것이다.

αv 항물체가 아무런 힘을 받지 않을 때 서서히 하강시키는 역할을 한다.
즉, 물체에 가해지는 기울기(가속도)가 없다면 물체의 속도는 저항(ex. 지면의 마찰, 공기의 저항) 등에 의해 감소하는데 이를 위해 α를 0.9 등의 값으로 설정한다.

모멘텀의 구현은 다음과 같다.

class Momentum:

    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = None
        
    def update(self, params, grads):
        if self.v is None:
            self.v = {}
            for key, val in params.items():                                
                self.v[key] = np.zeros_like(val)
                
        for key in params.keys():
            self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] 
            params[key] += self.v[key]

아까의 비등방성 함수를 모멘텀을 활용하여 최적화 갱신하면 경로는 다음과 같다.

SGD에 비하면 '지그재그 정도'가 덜하다.
이는 x축의 힘이 아주 작기는 하지만 계속해서 같은 방향으로 진행되어 일정하게 가속하는 형태로 반영되기 때문이다.


모멘텀을 활용하여 Local Minimum 문제도 해결할 수 있다.

기존 SGD가 최솟값이 아닌 극솟값을 찾는 case에서도,
모멘텀은 관성을 활용하여 빠져나올 수 있는 경우가 있다.


📍 AdaGrad

Adaptive Gradient

신경망 학습에서는 학습률을 값을 적절히 정하는 것이 중요 !

학습률이 너무 작으면 학습 시간이 길어지고, 반대로 너무 크면 발산하여 학습이 제대로 이루어지지 않을 수 있음.

이 학습률을 정하는 효과적 기술로 학습률 감소(Learning rate decay)가 있음
→ 학습을 진행하면서 학습률을 점차 줄여가는 것

간단히는 학습률을 '전체'에 대해 일괄적으로 낮출 수 있고, 이 방법을 더욱 발전시킨 것이 AdaGrad이다.

AdaGrad에서는 개별 매개변수에 적응적으로(adaptive) 학습률을 조정
수식으로는 다음과 같다.

☉ : 행렬의 원소별 곱셈

h라는 변수가 추가되었는데, 이 값은 기존 기울기 값을 제곱하여 계속해서 더해준다.
그리고 매개변수를 갱신할 때 1/sqrt(h)를 곱해 학습률을 줄인다.

→ 즉, 기울기 값이 커서 크게 갱신된 원소는 학습률을 낮춘다.
→ 학습률 감소는 매개변수의 원소마다 다르게 적용됨

AdaGrad에서 과거의 기울기의 제곱을 계속 더하고 학습을 진행할수록 갱신 강도가 약해진다.
이 때, 무한히 학습을 진행하면 어느 순간 갱신량이 0이 되어 갱신이 더 이상 진행되지 않는 문제 발생 가능 !
이 문제를 개선한 기법이 RMSProp
이 방법은 과거의 모든 기울기를 균일히 더하는 것이 아니라 먼 과거의 기울기는 서서히 잊고 새로운 기울기 정보를 크게 반영 (지수이동평균)
과거의 기울기의 반영 규모를 기하급수적으로 감소시킴

AdaGrad의 구현은 다음과 같다.

class AdaGrad:

    """AdaGrad"""

    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None
        
    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

마지막 줄에 1e-7이라는 작은 값을 더하여 h가 0일 때도 division by zero error를 방지


AdaGrad를 사용하여 위의 비등방성 함수를 최적화하면 갱신 경로는 다음과 같다.

y축 방향은 기울기가 커서 처음에는 크게 움직이지만, 그 큰 움직임이 h를 급격히 크게 만들어 갱신 정도를 큰 폭으로 작아지도록 조정한다.


📍 Adam

Adaptive Moment Estimation

  • Momentum과 AdaGrad를 섞은 기법
  • 모멘텀에서 사용하는 계수와 학습률에 대한 계수가 사용
  • 학습률을 줄여나가고 속도를 계산하여 학습의 갱신강도를 적응적으로 조정
  • 하이퍼파라미터의 '편향 보정'이 진행됨

현재 가장 많이 쓰이고 있는 optimizer이다.

Adam 클래스는 다음과 같이 구현된다.

class Adam:

    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
        
    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)
        
        self.iter += 1
        lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)         
        
        for key in params.keys():
            #self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]
            #self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)
            self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
            
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
            
            #unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias
            #unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias
            #params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)

하이퍼 파라미터는 총 3개
학습률 - α, 일차 모멘텀용 계수 - β1, 이차 모멘텀용 계수 - β2
논문에 따르면 기본 설정값은 β1이 0.9, β2가 0.999이고, 이 값이면 많은 경우에 좋은 결과를 얻을 수 있다고 한다.

Adam에 의한 최적화 갱신 경로는 다음과 같다.


💬 어느 갱신 방법을 이용할 것인가?

모든 문제에서 항상 뛰어난 기법이라는 것은 (아직까지) 존재하지 않는다 😢

또 당연하지만 학습률과 같은 하이퍼파라미터를 어떻게 설정하느냐에 따라서도 결과가 바뀐다 !

지금도 많은 연구에서는 SGD를 사용하고 있고, 모멘텀과 AdaGrad도 시도해볼 법한 가치가 있다.
최근에는 Adam이 많이 쓰이는 추세라고 한다.


참고 블로그

https://beoks.tistory.com/30
https://beoks.tistory.com/29?category=789329
https://wordbe.tistory.com/entry/MLDL-성능-향상을-위한-요령
https://sacko.tistory.com/42

profile
The World Is My Oyster 🌏

0개의 댓글