본문 바로가기
파이썬 프로그래밍/Numpy 딥러닝

20. 경사하강법의 개선 - Momentum, RMSprop

by Majestyblue 2022. 2. 4.

지난 시간에 단순 경사하강법은 수렴 가능성이 생각보다 낮다는 것을 보여주었다. 이번 시간은 경사하강법을 개선하여 빠르게 수렴하면서 지역 최소점에 수렴하지 않도록 할 수 있는지 보여줄 것이다.

 

저번 포스트에서 처럼 아래와 같은 오차 함수에 대해 단순 경사하강법을 진행하였다.

 

 

 

 

단순 경사하강법(GD)로는 시작 위치 -8일 때 움직임은 다음과 같다.

(1) learning rate = 1 이상부터 지역최소점 통과

(2) learning rate = 1.4 이상부터 전체 최소점 또는 지역 최소점 근처에서 진동하고 제대로 수렴하지 않는다.

(3) learning rate = 2.2 이상부터 overflow, 튕겨버린다.

 

 

 

1. Momentum

일반적으로 물리에서 Momentum(모멘텀) 이라 함은 '운동량'을 뜻한다. TMI로, 뉴턴 시절에는 Pimentum 이라고 사용하여 기호로 p를 많이 쓴다. 물체의 운동상태를 나타내는 물리량인데 작명이 적절한 것 같으면서도 그렇지 않은듯한? 느낌을 많이 받았다. 

 

모멘텀이 물체의 운동상태를 나타낸다는 것에 주목하자. Momentum 경사하강법은 기울기상태를 이용하는 방법이다.

물리에서 사용하는 방법과 엄밀히 말하면 '전혀' 다르지만 '아이디어'는 비슷한 느낌이다.

 

Momentum 방법은 이전의 기울기 정보를 지수가중이동평균으로 누적하여 기울기를 갱신하는 방법이다.

(포스트 참고 : 

지수가중이동평균(Exponentially Weighted Moving Average)-1

지수가중이동평균(Exponentially Weighted Moving Average)-2 )

 

 

 

수식을 통하여 보자.

 

 

 

이전 기울기정보를 갖고 있기 때문에 2가지 상황에 대해 다음과 같이 행동한다.

 

1) 이전기울기와 다음 기울기가 같은 방향 일 경우(v와 dLoss/dW가 같은 부호일 경우)

→ 더 하강해야 한다는 뜻이므로 더해지게 되고 가속한다, 즉 빨리 이동하게 된다.

 

2) 이전기울기와 다음 기울기가 반대 방향 일 경우(v와 dLoss/dW가 반대 부호일 경우)

→ 지역 최소점 또는 전체 최소점을 한번 지났다는 뜻이다. 다른 부호이므로 값이 줄어들게 되고 천천히 이동하게 된다.

 

위의 수식을 코드로 나타내면 아래와 같다.

momentum = 0.9
v = 0
for i in range(Epochs):
    dL_dx = loss_gradient(W)
    v = momentum*v + (1-momentum)*dL_dx
    W = W -learning_rate*v

 

Momentrum을 사용할 때 위치 -8에서 어떠할까?

 

 

 

 

3) learning rate = 0.4 이상부터 지역최소점 통과

 

 

 

 

 

4) learning rate = 5.4 이상부터 전체 최소점에서 한번 이상 튕김

 

 

 

5) learning rate = 8.6 이상부터 오버플로우 발생, 튕겨버림

 

 

 

 

확실히 단순 GD보다 안정적이다. Momentum은 기울기정보를 이용함을 기억하자.

아래는 전체 코드이다.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
import matplotlib.animation as animation

fig = plt.figure()
ax = plt.axes(xlim=(-10, 10))
start_x = -8
learning_rate=0.4

def loss_function(x):
    return (1/200)*(x-9)*(x)*(x+1)*(x+6) + 4

def loss_gradient(x):
    return (1/100)*(2*np.power(x, 3)-3*np.power(x, 2)-57*x-27)

xrange = np.arange(-10, 10, 0.1)
loss_array = []

for x in xrange:
    loss = loss_function(x)
    loss_array.append(loss)
    
ax.plot(xrange, loss_array)
ax.set_xlabel('x')
ax.set_ylabel('loss')


train_x = []
momentum = 0.9
v = 0
for i in range(100):
    if i == 0: # 첫 번째 위치를 기록하기 위함이므로 if-else문을 없애도 된다.
        pass
    else:
        dL_dx = loss_gradient(start_x)
        v = momentum*v + (1-momentum)*dL_dx
        start_x = start_x -learning_rate*v
    train_x.append(start_x)

redDot, = ax.plot([], [], 'ro')

def animate(frame):
    loss = loss_function(frame)
    redDot.set_data(frame, loss)
    return redDot

ani = FuncAnimation(fig, animate, frames=train_x)
#FFwriter = animation.FFMpegWriter(fps=5) 
#ani.save('mo_sx-8lr86.mp4', writer = FFwriter)
plt.show()

 

 

 

 

 

 

 

 

 

 

2. RMSprop

RMSprop 방법은 학습률(learning rate)를 변화시키는 방법이다. 

이전 기울기 정보의 제곱을 지수가중이동평균시키고 이 값을 제곱근하여 기울기에 나누고 업데이트하는 방법이다.

말로하니까 답답하다 수식을 보자. 여기서 델타(δ) 값은 나누기값이 0이 되지 않게 더해주는 값이다. 

 

 

 

 

학습률을 조정해주기 때문에 2가지 상황을 생각해 볼 수 있다.

 

1) 기울기 값이 큰 경우 → learning_rate * (1 / (s)^(1/2)) 값이 작아진다. → 천천히 움직이게 해준다.

 

2) 기울기 값이 작은 경우 → learning_rate * (1 / (s)^(1/2)) 값이 커진다. → 빨리 움직이게 해준다.

 

기울기가 작은 곳에서는 계속 앞으로 나아가게 해 주고 기울기가 큰 곳에서는 천천히 움직이게 하여 진동을 막는다.

 

아래는 수식을 코드로 표현한 것이다.

 

beta = 0.999
s = 0
for i in range(100):
    dL_dx = loss_gradient(start_x)
    s = beta*s + (1-beta)*dL_dx**2
    start_x = start_x - learning_rate*(1/(np.sqrt(s)+1e-10))*dL_dx

 

 

위의 오차함수에서 위치가 -8인 곳에서 어떻게 움직일까?

 

1) learning rate = 0.3 부터 전역 최소점에 수렴함.

 

 

 

 

2) learning rate = 0.6 부터 전역 최소점에서 튕기고 지역 최소점에 수렴함.

 

 

RMSprop는 학습률을 직접 조절하기 때문에 진동이 적고 overflow 발생할 일이 적다.

 

아래는 전체 코드이다.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
import matplotlib.animation as animation

fig = plt.figure()
ax = plt.axes(xlim=(-10, 10))
start_x = -8
learning_rate=0.3

def loss_function(x):
    return (1/200)*(x-9)*(x)*(x+1)*(x+6) + 4

def loss_gradient(x):
    return (1/100)*(2*np.power(x, 3)-3*np.power(x, 2)-57*x-27)

xrange = np.arange(-10, 10, 0.1)
loss_array = []

for x in xrange:
    loss = loss_function(x)
    loss_array.append(loss)
    
ax.plot(xrange, loss_array)
ax.set_xlabel('x')
ax.set_ylabel('loss')


train_x = []
beta = 0.999
s = 0
for i in range(100):
    if i == 0: # if-else문 없애도 무방
        pass
    else:
        dL_dx = loss_gradient(start_x)
        s = beta*s + (1-beta)*dL_dx**2
        start_x = start_x - learning_rate*(1/(np.sqrt(s)+1e-10))*dL_dx
    train_x.append(start_x)

redDot, = ax.plot([], [], 'ro')

def animate(frame):
    loss = loss_function(frame)
    redDot.set_data(frame, loss)
    return redDot

ani = FuncAnimation(fig, animate, frames=train_x)
#FFwriter = animation.FFMpegWriter(fps=5) 
#ani.save('rms_sx-8lr06.mp4', writer = FFwriter)
plt.show()