본문 바로가기
딥러닝관련/기초 이론

신경망 정리 5 (수치 미분, 편미분)

by 머리올리자 2021. 5. 23.

미분

 

10분동안 2km를 달렸다고 가정

 

1분에 0.2km만큼의 속도로 뛰었다고 할 수 있음

 

10분에 2km를 뛰었다는 것은, 정확하게는 10분 동안의 '평균 속도'를 구한 것임.

 

미분은 '특정 순간'의 변화량을 뜻함

 

수식으로 나타내면 아래와 같음

 

함수의 미분

 

좌변 : f(x)의 x에 대한 미분 (x에 대한 f(x)의 변화량)

 

결국, x의 '작은 변화'가 함수 f(x)를 얼마나 변화시키느냐를 의미

 

시간의 작은 변화, h를 한없이 0에 가깝게 한다는 의미로 limit으로 표현

 

간단히 미분 구현

 

def numerial_diff(f, x):
    h = 10e-50
    return (f(x + h) - f(x)) / h

 

위에는 개선해야 할 점이 2개 있음

 

h에 가급적 작은 값을 대입하고 싶었기 때문에 h를 무한히 작은 값인 10e-50으로 표현

 

이는 0이 50개라는 의미

 

그러나 반올림 오차의 문제를 일으킨다

 

아래는 반올림 오차 예시

 

import numpy as np

print(np.float32(1e-50))

결과

0.0

 

1e-50을 float32형으로 나타내면 0.0이 되어 올바르게 표현할 수가 없다.

 

너무 작은 값을 이용하면 컴퓨터로 계산하는 데 문제가 발생

 

이 미세한 값을 10^-4 정도로 이용하면 좋은 결과를 낼 수 있음

 

 

또한 f의 차분과 관련된 문제

 

x + h와 x 사이의 함수 f의 차분을 계산하고 있지만, 애당초 이 계산에는 오차가 있다.

 

'진정한 미분'은 x 위치의 함수의 기울기(접선)에 해당하지만

 

구현에서는 미분은 (x+h)와 x사이의 기울기에 해당.

 

그래서 진정한 미분과 구현의 값은 엄밀히는 일치하지 않음.

 

이는 오차를 발생시킬 수 있음.

 

이 오차를 줄이기 위해 (x + h)와 (x - h)일 때의 함수 f의 차분을 계산하는 방법을 쓰기도 함.

 

x를 중심으로 그 전후의 차본을 계산한다는 의미에서 중심 차분 혹인 중앙 차분이라 함.

 

다시 구현

 

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

 

아주 작은 차분으로 미분을 구하는 것을 수치 미분

 

수치 미분의 예

 

import numpy as np
import matplotlib.pyplot as plt


def numerial_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


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()

위 식의 그래프

import numpy as np
import matplotlib.pyplot as plt


def numerial_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


print(numerial_diff(function_1, 5))
print(numerial_diff(function_1, 10))

 

결과

0.1999999999990898
0.2999999999986347

 

위 계산한 미분 값이 x에 대한 f(x)의 변화량 → 함수의 기울기에 해당.

 

수치 미분 값을 기울기로 하는 직선

 

import numpy as np
import matplotlib.pylab as plt


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


def function_1(x):
    return 0.01*x**2 + 0.1*x 


def tangent_line(f, x):
    d = numerical_diff(f, x)
    print(d)
    y = f(x) - d*x
    return lambda t: d*t + y
     
x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")

tf = tangent_line(function_1, 5)
y2 = tf(x)

plt.plot(x, y)
plt.plot(x, y2)
plt.show()

 

위 식과 수치 미분을 그래프로 표현

 

 

편미분

 

아래 변수 2개인 함수가 있다면

 

 

아래와 같이 함수 구현 가능

 

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

 

x는 넘파이 배열이라 가정

 

위 식은 아래와 같은 3차원 그래프와 같은 형태로 나올 수 있다.

 

# 코드 참고 : https://jangpiano-science.tistory.com/114

import numpy as np
import matplotlib.pyplot as plt

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

X = np.arange(-3, 4, 0.1)
Y = np.arange(-3, 4, 0.1)
X, Y = np.meshgrid(X, Y)

Z = function_1(np.array([X, Y]))


fig = plt.figure()
ax = fig.gca(projection ="3d")
surf = ax.plot_wireframe(X, Y, Z , alpha = 0.5)

plt.xlabel("x0")
plt.ylabel("x1")
plt.show()

위와 같은 함수에서 미분을 구한다고 해봄

 

주의할 점은 변수가 2개라는 것.

 

따라서 '어느 변수에 대한 미분이냐', 즉 x_0과 x_1 중 어느 변수에 대한 미분이냐를 구별해야 한다.

 

이와 같이 변수가 여럿인 함수에 대한 미분을 편미분

 

편미분 수식은 아래와 같이 표현 가능

 

 

코드로 구현

 

x_0이 3이고 x_1 = 4일 때, x_0에 대한 편미분

 

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


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

print(numerial_diff(function_tmp1, 3.0))

출력

6.00000000000378

 

x_0이 3이고 x_1 = 4일 때, x_1에 대한 편미분

 

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


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

print(numerial_diff(function_tmp2, 4.0))

출력

7.999999999999119

 

이런 편미분 문제는 변수가 하나인 함수를 정의하고, 그 함수에 대한 미분을 구하는 형태로 구현

 

편미분은 변수가 하나인 미분과 마찬가지로 특정 장소의 기울기를 구한다.

 

단, 여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정

 

 

기울기 계산

 

위에서는 x_0과 x_1의 편미분을 변수별로 따로 계산.

 

그럼 x_0과 x_1의 편미분을 동시에 계산하고 싶다면

 

각 변수의 편미분을 벡터로 정의 한다.

 

이 벡터를 기울기라고 표현

 

기울기를 코드로 구현

import numpy as np

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

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

    for idx in range(x.size):
        tmp_val = x[idx]

        # f(x+h) 계산
        x[idx] = tmp_val + h # 편미분 구할 변수의 변화량만 검사
        fxh1 = f(x)

        # f(x-h) 계산
        x[idx] = tmp_val - h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2) / (2 * h)
        x[idx] = tmp_val # 값 복원

    return grad


print(numerical_gradient(function_2, np.array([3.0, 4.0]))) # (3, 4)에서의 기울기
print(numerical_gradient(function_2, np.array([0.0, 2.0]))) # (0, 2)에서의 기울기
print(numerical_gradient(function_2, np.array([3.0, 0.0]))) # (3, 0)에서의 기울기

 

결과

[6. 8.]
[0. 4.]
[6. 0.]

기울기가 의미하는 것

 

아래의 코드로 구현한 그래프 참고

 

# coding: utf-8
# cf.http://d.hatena.ne.jp/white_wheels/20100327/p3
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D


def _numerical_gradient_no_batch(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x) # x와 형상이 같은 배열을 생성
    
    for idx in range(x.size):
        tmp_val = x[idx]
        
        # f(x+h) 계산
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)
        
        # f(x-h) 계산
        x[idx] = tmp_val - h 
        fxh2 = f(x) 
        
        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val # 값 복원
        
    return grad


def numerical_gradient(f, X):
    if X.ndim == 1:
        return _numerical_gradient_no_batch(f, X)
    else:
        grad = np.zeros_like(X)
        
        for idx, x in enumerate(X):
            grad[idx] = _numerical_gradient_no_batch(f, x)
        
        return grad


def function_2(x):
    if x.ndim == 1:
        return np.sum(x**2)
    else:
        return np.sum(x**2, axis=1)


def tangent_line(f, x):
    d = numerical_gradient(f, x)
    print(d)
    y = f(x) - d*x
    return lambda t: d*t + y
     
if __name__ == '__main__':
    x0 = np.arange(-2, 2.5, 0.25)
    x1 = np.arange(-2, 2.5, 0.25)
    X, Y = np.meshgrid(x0, x1)
    
    X = X.flatten()
    Y = Y.flatten()
    
    grad = numerical_gradient(function_2, np.array([X, Y]) )
    
    plt.figure()
    plt.quiver(X, Y, -grad[0], -grad[1],  angles="xy",color="#666666")#,headwidth=10,scale=40,color="#444444")
    plt.xlim([-2, 2])
    plt.ylim([-2, 2])
    plt.xlabel('x0')
    plt.ylabel('x1')
    plt.grid()
    plt.legend()
    plt.draw()
    plt.show()

기울기 그림은 위 그림처럼 방향을 가진 벡터로 그려진다.

 

그림을 보면 기울기는 함수의 '가장 낮은 장소(최솟값)'을 가리키는 것처럼 보임

 

마치, 나침반처럼 화살표들은 한 점을 향하고 있다.

 

또한 '가장 낮은 곳에서 멀어질수록' 화살표의 크기가 커짐을 알 수 있다.

 

하지만 실제로는 항상 가장 낮은 장소를 가리킨다고 볼 수 없다.

 

각 지점에서 낮아지는 방향을 가리킨다고 표현하는 것이 더 옳다.

 

 더 정확히 말하면 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 줄이는 방향

 

 

 

내용 참고

book.naver.com/bookdb/book_detail.nhn?bid=11492334

 

밑바닥부터 시작하는 딥러닝

직접 구현하고 움직여보며 익히는 가장 쉬운 딥러닝 입문서!『밑바닥부터 시작하는 딥러닝』은 라이브러리나 프레임워크에 의존하지 않고, 딥러닝의 핵심을 ‘밑바닥부터’ 직접 만들어보며

book.naver.com