본문 바로가기
ML & DL/밑바닥부터 시작하는 딥러닝

[파이썬 딥러닝] 수치 미분

by Glory_Choi 2023. 3. 22.
반응형

미분

미분이란?

한순간의 변화량을 표시한 것

 

수식

파이썬으로 구현한 미분

def numerical_diff(f, x):
    h = 1e-50
    return (f(x + h) - f(x)) / h

그러나 이 방식은 반올림 오차 문제를 일으킵니다. 반올림 오차는 작은 값이 생략되어 최종 계산 결과에 오차를 생기게 합니다.

import numpy as np
np.float32(1e-50)

[출력]
0.0

위와 같이 작은 값을 넣어 0으로되어 버리는 오차를 말합니다.

이를 개선하기 위해서 두가지 방법이 있습니다. 첫째로는 h를 10 ^ -4를 사용하여 컴퓨터로 계산하는데 문제가 되지 않도록 하는 방법입니다. 둘째로는 차분을 이용하는 방법입니다.

 

차분

'진정한 미분'은 x 위치의 함수의 기울기에 해당하지만, 이번 구현에서는 미분은 (x + h)와 x 사이의 기울기에 해당합니다. 그래서 진정한 미분과 이번 구현의 값은 엄밀히 일치하지 않습니다. 이 차이는 h를 무한히 0으로 좁히는 것이 불가능해 생기는 한계입니다.

이 오차를 줄이기 위해 (x + h)와 (x - h)일 때의 함수 f의 차분을 계산하는 방법을 쓰기도 합니다. 이 차분은 x를 중심으로 그 전후의 차분을 계산한다는 의미에서 중심 차분 혹은 중앙 차분이라 합니다.

 

개선된 파이썬으로 구현한 미분

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

 

 

수치 미분의 예

수치 미분을 사용하여 간단한 함수를 미분해봅시다. 

def function_1(x):
    return 0.01 * x**2 + 0.1 * x
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x, y)
plt.show()

function_1 함수를 그래프로 그려보면

다음과 같이 2차 함수 그래프가 나옵니다. 이 함수를 특정값 x = 5일 때와 x = 10일 때 미분을 계산해 봅시다.

numerical_diff(function_1, 5)

[출력]
0.1999999999990898
numerical_diff(function_1, 10)

[출력]
0.2999999999986347

 

편미분

편미분이란?

변수가 여럿인 함수에 대한 미분

 

편미분을 파이썬으로 구현

 

def function_2(x):
    return x[0] ** 2 + x[1] ** 2

위 함수를 편미분 해보겠습니다. 편미분은 x0에 대하여 미분을 하거나 x1에 대하여 미분을 합니다.

 

문제1 : x0 = 3, x1 = 4 일 때, x0에 대한 편미분을 구하라.

def function_tmp1(x0):
    return x0 ** 2 + 4.0 ** 2.0

numerical_diff(function_tmp1, 3.0)

[출력]
6.00000000000378

문제2 : x0 = 3, x1 = 4 일 때, x1에 대한 편미분을 구하라.

def function_tmp2(x1):
    return x1 ** 2 + 3.0 ** 2.0

numerical_diff(function_tmp2, 4.0)

[출력]
7.999999999999119

 

기울기

def numerical_gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)

    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = tmp_val + h
        fxh1 = f(x)

        x[idx] = tmp_val - h
        fxh2 = f(x)
        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val
    return grad

위 함수를 이해하는게 어려우신 분들을 위해 위 함수를 간단히 분석해보겠습니다. x는 넘파이 배열입니다. 예를 들면 2.0, 3.0, 4.0등이 쭉 나열 되어있다고 생각하면 x[인덱스]마다 값이 다르고 그 값 마다의 수치 미분 값을 구하는거라고 생각하시면 됩니다. 위에서 grad를 np.zeros_like 함수를 사용해준 이유는 x의 크기가 10이라면 10개의 기울기가 존재하기 때문에 같은 크기의 배열을 선언해준 것입니다.

조금더 예를 들면 세 점 (3, 4), (0, 2), (3.0)에서의 기울기를 구해보겠습니다.

numerical_gradient(function_2, np.array([3.0, 4.0]))

[출력]
array([6., 8.])
numerical_gradient(function_2, np.array([0.0, 2.0]))

[출력]
array([0., 4.])
numerical_gradient(function_2, np.array([3.0, 0.0]))

[출력]
array([6., 0.])

 

경사법(경사 하강법)

기계학습 문제 대부분은 학습 단계에서 최적의 매개변수를 찾아냅니다. 신경망 역시 최적의 매개변수(가중치와 편향)를 학습 시에 찾아야 합니다. 여기에서 최적이란 손실 함수가 최솟값이 될 때의 매개변수 값입니다. 그러나 일반적인 문제의 손실 합수는 매우 복잡합니다. 매개변수 공간이 광대하여 어디가 최솟값이 되는 곳인지를 짐작할 수 없습니다. 이런 상황에서 기울기를 잘 이용해 함수의 최솟값( 또는 가능한 한 작은 값)을 찾으려는 것이 경사법입니다.

 

경사법은 현 위치에서 기울어진 방향으로 일정 거리 만큼 이동합니다. 그런 다음 이동한 곳에서도 마찬가지로 기울기를 구하고, 또 그 기울어진 방향으로 나아가기를 반복합니다. 이렇게 해서 함수의 값을 점차 줄이는 것이 경사법입니다. 경사법은 기계학습을 최적화하는 데 흔히 쓰는 방법입니다. 특히 신경망 학습에서는 경사법을 많이 사용합니다.

 

경사 하강법 파이썬으로 구현하기

def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x

    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad
    return x

 

신경망에서의 기울기

신경망 학습에서도 기울기를 구해야 하는데 여기서 말하는 기울기는 가중치 매개변수에 대한 손실 함수의 기울기입니다.

간단한 신경망을 예로 들어 실제로 기울기를 구하는 코드를 구현해보겠습니다.

import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient

class simpleNet:
    def __init__(self):
        self.W = np.random.randn(2, 3)
    
    def predict(self, x):
        return np.dot(x, self.W)
    
    def loss(self, x, t):
        z = self.predict(x)
        y = softmax(z)
        loss = cross_entropy_error(y, t)

        return loss
net = simpleNet()
print(net.W)

[출력]
[[-0.09421298 -0.48304419 -0.64576591]
 [-0.1799829   0.21240914  0.52464518]]
x = np.array([0.6, 0.9])
p = net.predict(x)
print(p)

[출력]
[-0.2185124  -0.09865829  0.08472112]
np.argmax(p)

[출력]
2
t = np.array([0, 0, 1])
net.loss(x, t)

[출력]
0.9442475695572569
def f(W):
    return net.loss(x, t)

dW = numerical_gradient(f, net.W)
print(dW)

[출력]
[[ 0.17233634  0.19428034 -0.36661668]
 [ 0.25850452  0.29142051 -0.54992503]]

신경망의 기울기를 구한 다음에는 경사법에 따라 가중치 매개변수를 갱신해주면 됩니다.

반응형