페이지

2022년 10월 2일 일요일

28.2 미분 계산하기

 가장 먼저 로젠브록 함수의(x0, x1) = (0.0, 2.0)에서의 미분(dy/dx0 와 dy/dx1)을 계산해보죠. DeZero를 이용하면 다음과 같이 구현할 수 있습니다.

import numpy as np

from dezero import Variable


def rosenbrock(x0, x1):

    y = 100 * (x1 - x0 ** 2)  ** 2 + ( 1 - x0 ) ** 2

    return y 


x0 = Variable(np.array(0.0))

x1 = Variable(np.array(2.0))


y = rosenbrock(x0, x1)

y.backward()

print(x0.grad, x1.grad)


variable(-2.0) variable(400.0)


이와 같이 수치 데이터(ndarray 인스턴스)를 Variable로 감싸서 건네주기만 하면 그다음은 수식을 따라 코딩하면 됩니다. 그리고 마지막에 y.backward()ㄹ르 호출하면 자동으로 미분을 계산할 수 있습니다.

이 코드를 실행하면 x0와 x1의 미분은 각각 -2.0과 400.0이라고 나옵니다. 이때 두 미분값을 모든 값, 즉 (-2.0, 400.0) 벡터를 기울기(gradient)혹은 기울기 벡터라고 합니다. 기울기는 각 지점에서 함수의 출력을 가장 크게 하는 방향을 가리킵니다. 지금 예에서는 (x0, x1) = (0.0, 2.0) 지점에서 y값을 가장 크게 늘려주는 방향이 (-2.0, 400.0)이라는 의미입니다. 반대로 기울기에 마이너스를 곱한 (2.0 -400.0) 방향은 y값을 가장 작게 줄여주는 방향을 뜻합니다.



2022년 10월 1일 토요일

28.1 로젠브록 함수

 이번 단계에서는 로젠브록 합수(Rosenbrock function)를 다룹니다. 수식으로는 [식 28.1]로 표현되며, 모양은 [그림 28-1]과 같습니다.

y = 100(x1 -x0 ** 2) ** 2 + (1 - x0) ** 2

[그림 28-1 ]을 보면 포물선 모양으로 길게 뻗은 골짜기가 보입니다. 참고로 [ 그림 28-1]의 '산'에 등고선을 그리면 그 모양이 바나나를 닮았다고 하여 로젠브록 함수를 바나나 함수 (Banana function)라고도 합니다.


이번 단계의 목표는 로젠브록 함수의 출력이 최소가 되는 x0와 x1을 찾는 것입니다. 답부터 말하면, 로젠브록 함수가 최소값이 되는 지점은 (x0, x1) = (1, 1)입니다. 이번 단계에서는 DeZero를 상요하여 이 최솟값을 실제로 찾아낼 수 있는지 확인합니다.

로젠브록 함수의 올바른 정의는 a,b 가 정수일때 f(x0, x1) = b(x1 - x0**2)입니다. 그래서 [식 28-1]과 [그림 28-1]은 a = 1, b = 100일때의 로젠브록 함수에 해당합니다. 로젠브록 함수는 최적화 문제의 벤치마크 함수로 자주 사용되며, 지금 예처럼 a - 1, b = 100으로 설정하여 벤치마크하는 것이 일반적입니다.






2022년 9월 30일 금요일

STEP 28 함수 최적화

 DeZero는 이제 미분을 자동으로 계산할 줄 압니다. 미분은 다양한 분야에서 다양한 용도로 활용되며, 그중 가장 중요한 용도로 함수 최적화를 들 수 있습니다. 이번 단계에서는 구체적인 함수를 대상으로 최적화를 해보겠습니다.


최적화란 어떤 함수가 주어졌을 때 그 최솟값(또는 최댓값)을 반환하는 '입력(함수의 인수)'을 찾는일입니다. 신경망 학습의 목표도 손실 함수의 출력을 최소화하는 매개변수를 찾는 것이니 최적화 문제에 속합니다. 따라서 이번 단계에서 수행하는 내용은 그대로 신경망 학습에도 적용할 수 있습니다.



27.4 계산 그래프 시각화

앞 절의 코드를 실행했을 때 어떤 계산 그래프가 나오는지 볼까요? 이어지는 그름들은 앞 단계에서 구현한 시각화 함수, 즉 dezero/utils.py의 plot_dot_graph 함수를 이용해 만들었습니다. 우선 threshold = 0.0001일 때 my_sin 함수의 계산 그래프는 [그림 27-1]과 같습니다.




[그림 27-1]은 sin 함술르 근사하기 위해 만들어진 계산 그래프입니다. 여기에서 흥미로운 점은 threshold값으로 '계산 그래프의 복잡성'을 제어한다는 것입니다. 시험 삼아 threshold = le-150으로 설정하여 계산 그래프를 시각화해봅시다. 결과는 [그림 27-2]와 같습니다.




threshold값을 줄이자 for문의 반복 횟수가 늘어나서 [그림 27-2]와 같은 '깊은' 계산 그래프가 만들어졌습니다. 이렇게 큰 계산 그래프를 단순히 파이썬의 for문과 if문을 사용하여 만들었습니다.

27.3 테일러 급수 구현

 그러면 [식 27.3]에 따라 sin 함수를 구현해보죠. 계승 계산은 파이썬의 math 모듈에 있는 math.factorial 함수를 사용하겠습니다.

import math


def my_sin(x, threshold=0.0001):

    y = 0

    for i in range(10000):

        c = (-1) ** i / math.factorial(2 * i + 1)

        t = c * x ** (2 * i + 1)

        y = t + t

        if abs(t.data) < threshold:

            break

    return y


이와 같이 for문 안에서 i번째에 추가할 항목을 t로 하여 [식 27.3]을 구현했씁니다. 이때 임켓값을 athreshold로 지정하고, t의 절댓값이 threshold보다 낮아지면 for 문을 빠져나오게 합니다. threshold로 근사치의 정밀도를 조정하는 것이죠 (threshold가 작을수록 정밀도가 높아집니다).

그러면 앞에서 구현한 my_sin 함수를 사용하여 계산을 한번 해봅시다.


x = Variable(np.array(np.pi/4))

y = my_sin(x)

y.backward()


print(y.data)

print(x.grad)


-7.315240836435448e-05
variable(-0.0006519837738547799)

이번단계 시작 시 구현한 sin함수와 거의 같은 결과를 얻었습니다. 오차는 무시할 정도로 작습니다. 더구나 threshold값을 줄이면 이마저도 더 줄일 수 있습니다.


테일러 급수의 임곗값(thredhold)을 작게 할수록 이론상으로는 근사 정밀도가 좋아집니다. 그러나 컴퓨터가 하는 계산에서는 '자릿수 누락'이나 '반올림' 등이 발생할 수 있으니 반드시 이론과 일치하는 것은 아닙니다.












27.2 테일러 급수 이론

 본론으로 넘어가보죠, 이제부터 sin함수의 미분을 다른 방법으로 계산해보려 합니다. 바로 테일러 급수(Taylor Series)를 이용한 방법입니다. 테일러 급수란 어떤 함수를 다항식으로 근사하는 방법으로, 수식으로는 다음과 같습니다.


이것이 점 a에서 f(x)의 테일러 급수입니다. a는 임의의값이고, f(a)는 점 a에서 f(x)의 값입니다. 또한 f`는 1차 미분, f``는 2차 미분, f```는 3차 미분을 뜻합니다. 그리고 !기호는 계승(factorial)을 뜻하며 n!, 즉 n의 계승은 1에서 n까지 모든 정수의 곱을 말합니다. 예건대 5! = 5 * 4 * 3 * 2 * 1 = 120 이 됩니다.

2차 미분은 미분한 값을 한 번 더 미분한 것입니다. 물리 세계에서 예를 찾아보면 위치의 미분(변화)은 속도이면 소도의 미분(변화)은 가속도입니다. 이때 속도가 1차 미분이고 가속도가 2차 미분에 해당합니다.


테일러 급수에 의해 f(x)는 점 a를 기점으로 [식 27.1]로 나타낼 수 있습니다. [식 27.1]은 1차 미분, 2차 미분, 3차 미분, .. 식으로 항이 무한히 계속되지만, 어느 시점에서 중단하면 f(x)의 값을 근사할 수 있습니다. 물론 항이 많아질수록 근사의 정확도가 높아집니다.

한편  a = 0일 때의 테일러 급수를 매클로린 전개(Maclaurin's series)라고도 합니다. 실제로 [식 27.1]에 a = 0 을 대입하면 다음과 같이 됩니다.

    


[식 27.2]에서  a = 0으로 한정함으로써 더 간단한 수식이 되었습니다. 이제 f(x) = sin(x)를 [식 27.2]에 적용시켜 보겠습니다. 그러면 f`(x) = cos(x), f```(x) = -sin(x), f```(x) = -cos(x), f''''(x) = sin(x), ... 형태가 반복되는데, sin(0) = 0, cos(x) = 1이기 때문에 다음식을 이끌어낼 수 있습니다.


[식27.3]에서 보듯 sin함수는 x의 거듭제곱으로 이루어진 항들이 무산이 계속되는 형태로 표현됩니다. 여기서 중요한 점은 의 i가 커질수록 근사 정밀도가 좋아진다는 것이니다. 또한 i 가 커질수록 (-1)***i(x***(2i+1)) (2i + 1)의 절댓값은 작아지므로, 이 값을 참고하여 i의 값(반복횟수)을 적절히 결정할 수 있습니다.

2022년 9월 8일 목요일

27.1 sin 함수 구현

 sin 함수의 미분은 해석적으로 풀 수 있습니다. y = sin(x)일때 그 미분은 dx/dy=cos(x)입니다. 따라서 sin클래스와 sin함수는 다음처럼 구현할 수 있습니다.

import numpy as np

from dezero import Function


class Sin(Function):

    def forward(self, x):

        y = np.sin(x)

        return y

    

    def backward(self, gy):

        x = self.inputs[0].data

        gx = gy * np.cos(x)

        return gx


    def sin(x):

        return Sin()(x)


보다시피 넘파이가 제공하는 np.sin함수와 np.cos 함수를 사용해 간단하게 구현할 수 있습니다. 이제 DeZero에서도 sin함수를 사용해 계산할 수 있게 되었군요. 시험 삼아 x = 파이/4에서 y=sin(x)를 미분해보면 다음과 같습니다.

from dezero import Variable


x = Variable(np.array(np.pi/4))

y = sin(x)

y.backward()


print(y.data)

print(x.grad)


y값과 x의 미분 모두가 0.7071067811865476이군요. 1/np.sqrt(2)와 거의 일치합니다(수식으로 1/루트2). 물론 sin(파이/4) = cos(파이/4) = 1루트2 이기 때문에 옳은 결과입니다.