본문 바로가기
Tensorflow

[ANN] 08. GDA(Gradient Descent) of Neural Network

by 청양호박이 2020. 5. 15.

지난 시간에 이어서 신경망 학습의 최우선 과제인 학습에 대해서 마저 알아보겠습니다. 학습의 목적은 결국 ANN(인공신경망)모델 내 구성된 변수인 weight, bias에 대해서 최적의 값을 찾아내는 것이고, 이는 Loss Function의 최소값을 찾는 것이라고도 했습니다.

 

그리고 이를 위해서 미분을 알아보았고, 그 연장선 상으로 GDA(Gradient Descent Algorithm, 경사하강법)에 대해서 운을 살짝 뗐습니다. 그럼 이제는 본격적으로 GDA를 통해서 Global Min 과 Local Min을 찾는 여정을 떠나보겠습니다.

 

  • 인공신경망(ANN)의 개요 및 퍼셉트론과의 차이점
  • 인공신경망(ANN)의 활성화함수(AF, Activation Function)
  • 인공신경망(ANN)의 출력층 설계 및 순전파(Forward Propagation)
  • 인공신경망(ANN)의 손실함수(Loss) 구현
  • 수치미분 및 편미분, 그리고 GDA
  • 인공신경망(ANN)의 역전파(Back Propagation) 개념
  • 인공신경망(ANN)의 역전파(Back Propagation) 구현

 

 

1. GDA(Gradient Descent Algorithm) 개요


아무래도 가장 이상치는 Global Min을 찾는 것이지만, Local MIn도 의미가 당연히 있습니다. Global Min의 경우는 해당 함수에서 전체적으로 가장 작은 부분이고,  Local Min의 경우는 해당 함수의 전체중에 가장 작은 부분은 아니지만 작은축에 속하는 부분입니다. 

 

이번에는 2차 함수를 예로들어 GDA를 찾아보겠습니다. 해당 함수는 아래 그림처럼 밑으로 볼록한 함수입니다. 이 함수가 Loss Function이라고 가정할 때, 가장 작은 값은 기울기가 0이 되는 위치입니다.

 

현재 주어진 위치의 기울기를 알아내면 어느 방향으로 가야 최소의 Loss를 구할 수 있는지 알 수가 있는데... 

 

(1) 현재 위치의 기울기가 (+)일 경우, (-) 방향으로 이동하면 최소 위치로 이동

(2) 현재 위치의 기울기가 (-)일 경우, (+) 방향으로 이동하면 최소 위치로 이동

 

이 됩니다. 그래서 나온 GDA의 기본적인 공식은 

 

x0 = x0 - learing_rate * df / dx 

 

입니다. 현재 구해진 기울기의 방향의 반대로 이동해야 하기 때문에 (-)를 곱해주고 learning_rate를 기준으로 일정배수로 이동하면 점점 최소위치로 도달하게 될 것 입니다.

 

여기서 learning_rate는 설계자가 직접 정해야하는 상수로 잘 지정해야 합니다. 왜냐하면... 

 

(1) 너무 작은경우, 학습이 잘 이루어 지지 않을테고 (너무 조금씩 움직여서)

(2) 너무 클 경우, 학습이 너무 크게 일어나서 아래의 그림과 같이 overshooting이 발생할 수 있음

 

다음과 같이 overshooting이 발생하면 기울기0으로 수렴하기 않고 계속해서 발산을 하게 됩니다. 결국 원래 결과보다 더 않좋아지는 결과를 볼 수 있습니다.

 

 

2. GDA(Gradient Descent Algorithm) 구현


그렇다면 python코드를 통해서 GDA(Gradient Descent Algorithm)를 구현해 보겠습니다. 

 

학습을 적용하고자 한다면, 2가지 상수를 정해주어야 합니다. 상수는 유식한 말로 하이퍼파라미터(Hyper Parameter)라고 하고, 그 대상은 바로 학습율과 학습량 입니다. 

 

- 학습율(learning_rate)은 미분 결과에 대해서 입력값에 얼마나 반영을 할지에 대한 결정

- 학습량(epochs)은 이런 학습을 얼마나 수행할지에 대한 결정

 

위에 이야기 했던것과 같이 이 두가지 하이퍼파라미터를 잘 지정해서 좋은 결과를 도출할 수 있습니다. 학습율을 높게 세팅하면 학습은 빨리진행되겠지만 overshooting이 발생할 확률도 높아지고, 학습율을 낮게 세팅하면 학습이 늦어 엄청난 학습량을 감당해야 할 것입니다. 

 

이 값에 대해서는 적정치가 없고, 내가 사용할 입력 데이터의 유형에 대해서 많은 차이가 있어 정답은 없습니다. 

 

[GDA 예시코드]

def GDA(f, x, lr=0.001, epochs=100):
    for i in range(epochs):
        diff_res = diff(f, x)
        x -= lr * diff_res
    return x

사실 기존에 다 구현이 되어있어서 GDA에는 크게 추가할 것이 없습니다. 미분에 대한 메서드도, f(x)에 대한 메서드도 이미 다 존재합니다.  

 

결국, 미분의 결과에 learning_rate를 곱해서 입력값에 반영하고, 그것을 epochs만큼 학습하는 것이기 때문입니다. 

 

 

3. GDA(Gradient Descent Algorithm) Test


그렇다면 이제 새로운 함수를 선택해서 테스트를 진행해 보겠습니다. GDA가 적용되기 까지 Full Stack으로 구현합니다.

 

대상 함수 : f(x) = x1^2 + x2^2

목적 : 대상 함수의 최소값 구하기

 

구현로직은 간단합니다. 우선 해당함수자체를 구현하고, 이에 대한 수치미분을 적용하고 구현하고, 마지막으로 GDA를 적용하여 학습을 반복하여 수행 후 그 결과를 도출하여 확인합니다.

 

예상 결과 : [x1, x2] = [0, 0]

 

그럼 본격적으로 구현을 해보겠습니다.

 

[대상함수 구현 x1^2 + x2^2]

def func(x):
    return np.sum(x**2)
    
x = np.array([1,2])
print(func(x))
=========================

5

테스트로 입력값을 넣어보면 결과가 올바르게 나옵니다.

 

[대상 함수 모양]

from mpl_toolkits.mplot3d import Axes3D

x1 = np.arange(-5.0, 5.0, 0.1)
x2 = np.arange(-5.0, 5.0, 0.1)
x1, x2 = np.meshgrid(x1, x2)
y = x1**2 + x2**2

plt.figure().gca(projection='3d').plot_wireframe(x1, x2, y, color='black')
plt.show()

2개 변수의 함수로 3차원의 그래프에서 그 모양을 확인 할 수 있습니다. 3차원을 그리기 위해서는 추가 패키지를 import해야하고 간단하게 몇줄만 추가하면 가능합니다. 그림에서 확인이 가능한데... 기울기가 0이 되는 부분은 x1 = 0, x2 = 0이 되는 지점이 됩니다.

 

[수치미분 구현]

def diff(f, x):
    x_hat = 1e-4
    res = np.zeros_like(x)
    for i in range(x.size):
        temp = x[i]
        x[i] = temp + x_hat
        f_res1 = f(x)
        x[i] = temp - x_hat
        f_res2 = f(x)
        res[i] = (f_res1 - f_res2) / (2*x_hat)
        x[i] = temp
    return res

이건 바로 전 글에서 확인한 내용입니다. 그럼 테스트겸 입력값을 넣어보겠습니다.

x = np.array([4.0, -4.0])
y = func(x)
print(y)

diff_x = diff(func, x)
print(diff_x)
===========================

32.0
[ 8. -8.]

함수에 대한 결과와 수치미분에 대한 결과 모두 정확하게 도출되었습니다.

 

[GDA 구현]

def GDA(f, x, lr=0.02, epochs=500):
    for i in range(epochs):
        diff_res = diff(f, x)
        x -= lr * diff_res
        if i % 50 == 0:
            print(x)
    return x

하이퍼파라미터인 learning rate와 epochs는 몇번 테스트 해 보고 넣었습니다. 이 부분은 모델은 만드는 사람이 직접 넣어야 하는 값이기 때문이죠. 

 

[결과 확인]

x = np.array([4.0, -4.0])
res = GDA(func, x)
print('결과: ',res)
==========================

[ 3.84 -3.84]
[ 0.49876145 -0.49876145]
[ 0.06478203 -0.06478203]
[ 0.00841426 -0.00841426]
[ 0.00109289 -0.00109289]
[ 0.00014195 -0.00014195]
[ 1.84374619e-05 -1.84374619e-05]
[ 2.39476437e-06 -2.39476437e-06]
[ 3.11045871e-07 -3.11045871e-07]
[ 4.04004397e-08 -4.04004397e-08]
결과:  [ 5.46608664e-09 -5.46608664e-09]

학습을 반복할 수록 GDA를 통해 보정되는 결과는 점점 x1 = 0, x2 = 0에 근접해 집니다. 모델에 대한 학습이 잘 됬다고 보여집니다. 그러하면 overshooting이 발생하는 장면을 확인해 볼까요??

 

[번외변 - overshooting 확인하기]

def GDA(f, x, lr=2, epochs=500):
    for i in range(epochs):
        diff_res = diff(f, x)
        x -= lr * diff_res
        if i % 50 == 0:
            print(x)
    return x
    
x = np.array([4.0, -4.0])
res = GDA(func, x)
print('결과: ',res)
====================================

[-12.  12.]
[ 1.63597748e+12 -1.63597748e+12]
[ 1.63597748e+12 -1.63597748e+12]
[ 1.63597748e+12 -1.63597748e+12]
[ 1.63597748e+12 -1.63597748e+12]
[ 1.63597748e+12 -1.63597748e+12]
[ 1.63597748e+12 -1.63597748e+12]
[ 1.63597748e+12 -1.63597748e+12]
[ 1.63597748e+12 -1.63597748e+12]
[ 1.63597748e+12 -1.63597748e+12]
결과:  [ 1.63597748e+12 -1.63597748e+12]

간단하게 learning rate를 완전 높여주고, 그냥 돌리면 됩니다. 역시나 저 먼곳으로 날아가게 되네요.... 안녕~~~

 

 

0. 마치며


지금까지 알아본 내용은 ANN(인공신경망)에 바로 적용이 가능합니다. 왜냐하면 결국 Loss Function도 함수이고 이에대한 수치미분도 쉽게 구할 수 있으며, 하이퍼파라미터를 설정하여 학습을 진행하면 되기 때문입니다. 하지만 실제로 각 변수에까지 학습을 진행하기 위해서는 몇가지가 더 필요합니다 .

 

바로  역전파(Back Propagation)을 알아야 합니다. 복잡한 ANN에서 학습은 역전파를 통해서 각 변수인 weight와 bias까지 모두 진행하게 됩니다. 그럼 다음부터는 이 역전파에 대해서 자세히 알아보도록 하겠습니다. 

 

- Ayotera Lab -

댓글