본문 바로가기
Tensorflow

[ANN] 07. Differential of Neural Network

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

지난 글에서 드디어 학습을 위한 첫 발을 뗐습니다. Loss Function을 통해서 현재 ANN(인공신경망) 모델의 상태를 알 수 있었습니다. 그 값이 높으면 부정확한 모델이 될테고, 낮을수록 정확한 모델이 됩니다. 물론 무조건 낮다고 좋은 모델이 아니겠죠?? 왜냐하면 해당 모델이 overfitting이 발생했을 수도 있으니까요.

 

이제는 이 상태를 가지고 오차를 최소화하여 모델을 최적화하는 방법을 찾아야 합니다. 결국 Loss Function도 함수이기 때문에 특정모양의 곡선이 그려질 것이고... 이 곡선에서 최소값을 찾는 문제가 될 것입니다. 

 

곡선에서 최소값을 찾는다는 말은 돌려서... 수학적인 접근을 시도하면 기울기가 0인 곳을 찾는 문제가 될 것이고, 기울기를 구한다는 것은 결국 미분을 한다는 말이 됩니다. 

 

제가 하고싶은 말은 그래서... 이렇게 돌려서 말을하려는 이유는 미분을 해야하기 때문입니다. 예전의 기억을 떠올려서 (떠올리려고 하니... 않좋은 기억밖에 없을수도 있지만 ^^) 찬찬히 살펴보도록 하겠습니다.

 

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

 

 

1. 미분 Basic


미분은 어렵게 생각할 것 없습니다. 그냥 단순하게 해당 조건하에 특정 순간의 변화량이라고 생각하면 됩니다. 

 

예를들어, 애기가 10분동안 10m를 기어갔다고 가정하면 평균 속도는 1m/분이 됩니다. 하지만, 중간에 장난감의 유혹에... 우유의 유혹에 빠져서 놀았다가 먹었다가... 결국 어떤 순간에는 0.1m/분이 될 수도 10m/분이 될 수도 있습니다. 

 

따라서 균일하지도 정확하지도 않으니, 내가 원하는 시점에... 이 시점은 0.1초 0.01초 0.001초 ... 아주 0에 근접하게 줄여서 순간의 변화량을 보고 싶다!! 바로 이것이 미분의 시작입니다.

 

df(x)/dx = lim (f(x+x_hat) - f(x)) / x_hat

(단, x_hat 은 0으로 수렴 |  x_hat -> 0 )

 

기울기는 x의 증가분 분의 y의 증가분 입니다. 아주 0에 근접한 x_hat이 x의 증가분이라고 했을때, 함수 f(x)에 x + x_hat의 결과와 x의 입력결과의 차이를 y의 증가분이라고 할 수 있습니다. 그래서 위의 코드가 도출이 되게 됩니다. 그렇다면 한번 코드로 구현을 해보겠습니다. 아참!! 구현하기전에 미분의 2가지 종류에 대해서 알아보겠습니다.

 

(1) 해석미분 : 우리가 보통 수학시간에 배우는 펜과 머리로 계산하는 미분법

(2) 수치미분 : 수치적으로 근사값을 구하는 방법으로 아주작은 차분으로 변화하는 수치를 계산하는 미분법

 

위에서 알아본 식은 수치미분을 적용한 모습입니다. 사실 코드로 구현하기 위해서는 수치미분을 쓸수밖에 없는 것 같습니다. python으로 구현을 위해서는 2가지 주의사항이 있습니다. 그 주의사항을 확인하고 바로 코드로 넘어가 보겠습니다.

 

(1) x_hat을 0에 아주 근접한 수로 정해야 하는데... 이 수가 너무 작을경우 부동소수점에서 이를 0.0으로 인식하게

    됩니다. 따라서 아주작은 수지만 인식의 오류가 없도록 1e-4 정도로 세팅하여 진행합니다.

(2) 중앙차분을 이용 : 차분에는 3가지 방법이 있는데 전향차분, 후향차분, 그리고 중앙차분입니다. 느낌이 오시겠지만

    전향차분과 후향차분은 x_hat을 더하느냐 빼느냐의 차이고... 중앙차분은 x_hat을 더한것과 뺀것을 모두 사용하고

    그의 평균을 구하게 됩니다.

 

결국 그 식은 (f(x+x_hat) - f(x-x_hat)) / (2 * x_hat) 이 됩니다. 이 방법이 가장 정합성이 높다고 알려져 있습니다. 그럼 특정 함수(f(x))를 예로 수치미분을 적용하여 확인을 해보겠습니다.

 

[함수 f(x) = y = 2x^2 + 4x]

import numpy as np
import matplotlib.pyplot as plt

def func(x):
    return 2*x*x + 4*x
    
    
temp_x = np.arange(-5.0, 5.0, 0.1)
temp_y = func(temp_x)

plt.plot(temp_x, temp_y)
plt.show()

해당 코드는 주어진 함수를 그대로 구현하며, -5 ~ 5 사이의 값을 0.1단위로 결과를 찍어 모양을 확인해 보면 아래와 같습니다.

[수치미분 : 중앙차분 적용]

def diff(f, x):
    x_hat = 1e-4
    return (f(x+x_hat) - f(x-x_hat)) / (2 * x_hat)

결국 부동소수점으로 0이 나오지 않을 만큼의 작은수를 정하여, 중앙차분을 적용합니다.

 

[특정 값으로 확인 (단일, mini-batch)]

# x가 단일 값일때
x = np.array([3.0])
print(diff(func, x))

# x가 mini-batch로 들어올때
x = np.array([[1.0],[2.0],[3.0]])
print(x.shape)
print(diff(func, x))
===================================

[16.]

(3, 1)

[[ 8.]
 [12.]
 [16.]]

해석미분으로 정합성을 판단하더라도, 2x^2 + 4x의 미분함수는 4x + 4 입니다. 따라서 수치미분법에 적용한 입력값으로 확인해보면... 1.0일 경우 8 / 3.0일 경우 16  으로 정확하게 일치합니다.

 

지금까지는 변수가 1개 즉 f(x) 인 함수만 알아보았습니다. 하지만 실제 상황이 되면 이 변수는 엄청 많을 수 있습니다. 이 상황에서 적용하는 미분을 알아보겠습니다.

 

 

2. 편미분


특정 함수의 변수가 2가지 이상이고 이 상황에서 미분을 해야 한다면 편미분을 적용해야 합니다. 편미분이란... 다변수 함수에서 특정 변수를 제외한 나머지 변수를 상수로 생각하고 미분을 적용하는 것을 말합니다.

 

예를들어,

f(x0, x1, x2) = x0^2 + x1^2 + x2^2 로 3가지 변수가 존재할때, x0에 대한 편미분인 df(x) / dx0는 2 * x0 가 됩니다.

나머지도 마찬가지입니다. 그리고 각 변수별로 미분을 구한 후 합쳐서 표현하는것이 바로 편미분의 결과가 됩니다.

 

그럼 편미분 구하는 방법에 대해서 python코드로 확인해 보겠습니다.

 

[함수 f(x0, x1, x2) = x0^2 + x1^2 + x2^2]

def func(x):
    res = 0
    for i in range(x.size):
        res += x[i] ** 2
    return res
    
    
x = np.array([1,2,3])
print(func(x))
===========================

14

사실 위에서 for로 작성되어 있는 식을, res = np.sum(x ** 2)로 한줄로 표현이 가능합니다.

def func(x):
    return np.sum(x ** 2)

 

코드에서 확인한 바와같이, 입력이 변수의 개수와 동일하게 행기준으로 들어오게 됩니다. 1 x 개수 처럼 말이죠. 그리고 각 변수에 대해서 x_hat을 사용해서 미분을 구하고 합쳐주면 됩니다. 실제로 데이터를 넣어서 확인해 보겠습니다. 

 

[수치미분 : 중앙차분 적용]

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

편미분은 각 변수에 대해서 개별적으로 미분을 수행해야 하기때문에, 입력 np.array의 size만큼 반복을 수행합니다. 또한, 결과는 입력 np.array의 크기와 동일하게 출력하게 됩니다. 

 

[특정 값으로 확인 (단일, mini-batch)]

단일과 mini-batch의 구현은 차이가 있습니다. 위의 방식은 단일에 적용되며, mini-batch를 적용할 때는 한번더 함수를 구현하여, 단일일 경우와 mini-batch를 구분하여 로직을 생성합니다.

 

(1) 단일 구현

# x가 단일 값일때
x = np.array([1.0,2.0,3.0])
print(func(x))
print(diff(func, x))
=============================

[2. 4. 6.]

(2) mini-batch 구현

# 추가 분기 함수
def checkDim(f, x):
    if x.ndim == 1:
        return diff(f, x)
    else:
        res = np.zeros_like(x)

        for idx, i in enumerate(x):
            res[idx] = diff(f, i)
            
        return res

1차원의 1xN의 배열일 경우는 바로 수치미분을 적용하고, mini-batch와 같이 2차원일 경우... 2차원의 0으로 된 배열을 생성하고, 하나하나 행에대한 수치미분을 적용 후 결과를 res 변수에 담아서 리턴하게 됩니다.

# x가 mini-batch일때
x = np.array([[3.0, 1.0, 2.0],[1.0, 2.0, 3.0]])
print(checkDim(func, x))

=============================================

[[6. 2. 4.]
 [2. 4. 6.]]

 

위의 결과에 대해서 해석미분으로 구해보면 결과가 거의 동일하다는 것을 확인할 수 있습니다. 당연히 1e-4라는 근사치를 사용하기 때문에 100% 동일하기는 어렵습니다.

 

 

3. 경사하강법 (Gradient Descent Algorithm)


신경망 학습의 최우선 과제는 결국 ANN(인공신경망)모델 내 구성된 변수인 weight, bias에 대해서 최적의 값을 찾아내는 것 입니다. 이는 결국 Loss Function에서 도출되는 값 중 최소의 값을 내는 경우가 될 것입니다.

 

위의 내용대로하면 지금까지 알아봤던 미분을 적용할 수가 있습니다 .왜냐하면 미분이 함수 f(x)에서 최소를 찾기위해 기울기가 0을 찾는 과정이기 때문입니다. 

 

위의 이유로 등장한 것이 바로 GDA(Gradient Descent Algorithm, 경사하강법) 입니다. 손실을 최소화 하는데 있어서... 해당 함수에는 Global Min과 Local Min이 있습니다. Global Min의 경우는 해당 함수에서 전체적으로 가장 작은 부분이고,  Local Min의 경우는 해당 함수의 전체중에 가장 작은 부분은 아니지만 작은축에 속하는 부분입니다. 복잡한 함수일수록 Global Min찾기는 쉽지 않습니다. 하지만 Local Min을 찾는 것도 나름의 의미가 있겠죠??

 

이 부분은 길어질 것 같아서 다음에 이어서 진행해 보도록 하겠습니다. 

 

- Ayotera Lab -

댓글