본문 바로가기

Data Science/밑바닥부터시작하는딥러닝(3)

[밑시딥] 나만의 딥러닝 프레임워크 만들기, 고차 미분 계산(2)

반응형

🔊 해당 포스팅은 밑바닥부터 시작하는 딥러닝 3권을 개인적으로 공부하면서 배운 내용을 기록하고 해당 책을 공부하시는 다른 분들에게 조금이나마 도움이 되고자 하는 목적 하에 작성된 포스팅입니다. 포스팅 내용의 모든 근거는 책의 내용에 기반하였음을 알립니다.

 

출처: Yes24


저번 포스팅에서는 고차 미분 계산을 수학적으로 어떻게 구현해낼 수 있는지, 또 미분을 사용하는 이유는 목적함수를 최적화하는 것인데, 최적화 하는 방법론으로서 경사 하강법(Gradient Descent)과 뉴턴 방법(Newton's Method)의 개념이 무엇이고 차이점이 무엇인지도 알아보았다.

 

이번 포스팅에서는 뉴턴 방법 최적화 기법의 근간이 되는 고차 미분을 우리의 Dezero 프레임워크에 어떻게 녹여낼지에 대해서 배워보도록 하자. 그리고 고차미분이 가능해진 Dezero를 활용해서 간단하게 고차미분을 테스트해보고 고차미분을 활용해 뉴턴 방법 최적화 기법도 구현시켜보자. 해당 포스팅 마지막에서는 이러한 고차 미분이 뉴턴 최적화 기법에 사용되는 것 이외에 어떤 다른 용도로 사용이 가능한지도 배워보도록 하자.

1. 기존 Dezero의 변수와 함수의 연결

본문에 들어가기에 앞서 기존에 만들어왔던 Dezero 프레임워크에 1차 미분을 어떻게 녹여냈는지 잠시 살펴보자. 변수와 함수 간의 관계 그림을 보면서 회고해보자. 

 

Variable(변수)를 나타내는 방법

 

먼저 왼쪽 그림은 x 라는 변수를 의미하고 이 변수는 우리가 만들었던 Variable 클래스 인스턴스 객체이다. 그래서 x 라는 변수 안에는 data 와 grad 라는 속성값이 존재한다. 이 때, 투명색 칸은 어떤 데이터를 참조하고 있지 않다는 의미이고, 노란색 칸으로 칠해져있다면 어떤 데이터를 참조하고 있다는 의미이다. 예시를 들어보자. 왼쪽 그림은 현재 data는 노란색 칸으로 채워져 있으므로, 특정 데이터(넘파이 배열이 되었건 스칼라 값이 되었건)를 참조하고 있음을 의미한다. 반면에, grad는 투명색 칸이며, 현재 grad에는 어떤 데이터도 참조하지 않고 있음을 의미하며 파이썬 코드로 나타내면 grad = None 을 의미한다.

 

그러면 기존 Dezero 프레임워크에서 Sin 함수를 사용해 순전파/역전파를 수행했을 때 각 변수간의 데이터 참조 상태를 그림으로 나타내면 아래처럼 된다.

 

$sin(x)$의 순전파, 역전파

 

먼저 순전파를 보자. 순전파 시에는 x, y 변수 모두 data라는 속성만 특정 데이터를 참조하고 있다. 순전파 시에는 당연히 미분이라는 개념을 활용하지 않기 때문에 현재 x, y 변수 모두 grad 속성은 투명색 칸으로 어떤 데이터도 참조하고 있지 않다.(값이 None임을 의미)

 

그러면 이제 역전파일 때를 보자. 역전파가 진행되는 순간 x, y 변수 모두 grad가 어떤 데이터를 참조하고 있는 상태로 변경되었다. 이렇게 기존의 Dezero는 위와 같은 형태로 변수(들)와 함수간의 연결을 만들어왔다.

 

우리가 주목해야 할 부분은 다음과 같다. 역전파는 미분을 활용한다. 그런데 역전파는 시간 순서 상으로 역전파 이전에 수행하는 순전파에 의해 만들어지는 계산 그래프 형태를 기반으로 미분을 한다. 즉, 한 번의 미분을 가능하게 하는 것은 한 번의 계산 그래프이며 이 계산 그래프는 순전파를 수행할 때 생성된다는 것이 핵심이다!

2. Dezero에 고차 미분을 녹여내자, 역전파를 또 하나의 순전파로!

그러면 기존 Dezero 프레임워크에는 2차 미분 이상의 고차 미분을 적용시킬 순 없을까? 현재 상태로는 불가능하다. 코드 상으로 어떤 개선로직이 추가되어야 가능해진다. 개선 로직을 직접 코드로 구현하기 전에 개선 로직의 이론을 먼저 이해해보자. 결론적으로 말하면 기존 Dezero 코드는 계산 그래프가 순전파 시에만 만들어지고 역전파 시에는 만들어지지 않기 때문에 현재로서는 고차 미분을 적용하기가 불가능하다. 이 점을 머리에 두고 아래 내용을 읽어나가보자.

 

예시로 $sin(x)$ 함수를 다시 들어보자. $sin(x)$의 순전파를 계산 그래프로 나타내면 아래와 같다.

 

gy의 g는 기울기(grad)를 의미한다

 

우리는 지금까지 위와 같은 형태로 역전파(빨간색 화살표)를 표현했다. 실질적으로 $sin(x)$의 역전파 공식은 아래와 같다.

$$gx = gy * cos(x)$$

우리는 지금까지 단순히 위 공식을 코드에서 Numpy 배열 인스턴스를 받도록 했다. 즉, 위 $sin(x)$ 역전파 공식에 대한 계산 그래프는 생성하지 않았다는 것이다. 우리는 이제 이 역전파 공식에다가도 계산 그래프를 생성해주어야 한다. 먼저 $sin(x)$의 역전파 즉, 미분 공식을 계산 그래프 형태로 나타내면 아래와 같다.

 

$sin(x)$의 역전파 식에 대한 계산 그래프

 

위 계산 그래프 그림과 $gx = gy * cos(x)$ 식을 비교해보면 맞는 계산 그래프 그림이라는 것을 알 수 있다. 그런데 잘 생각해보면 위 $sin(x)$의 역전파 식에 대한 계산 그래프도 관점을 달리하면 또 하나의 순전파 식이 된다. 그렇다면 이 계산 그래프에다가도 다시 한 번 역전파를 적용해볼 수 있게 된다.

 

다시 말해, 최초의 $sin(x)$ 순전파가 있었을 때, $sin(x)$의 역전파가 있다. 그리고 이 $sin(x)$의 역전파를 또 하나의 순전파로 간주하고 이 순전파에 대한 역전파를 수행해줄 수 있다는 것이다. 이렇게 되면 최초 $sin(x)$ 순전파 관점에서 봤을 때 2차 미분 즉, 고차 미분이 수행된 것임을 깨달을 수 있다. 그림으로 나타내보면 아래와 같다.

 

역전파를 또 하나의 순전파로 간주하는 것이 핵심!

 

그렇다면 위와 같이 역전파를 또 하나의 순전파로 간주하려면 어떤 방식으로 코드에 반영할 수 있을까? 바로 처음 역전파(역전파 ①)를 수행할 때 국소적인 미분 값을 전달해줄 때도 Variable 클래스 인스턴스 객체로 만들어주면 된다. 기존 Dezero 코드는 역전파를 수행할 때 국소적인 미분 값을 단순히 스칼라나 Numpy array 객체 형태로 전달해주었다. 하지만 이 미분값을 Variable 클래스 객체로 만들어서 전달해주면 순전파 시에 계산 그래프가 만들어지는 로직을 적용할 수 있게 된다. 그래서 역전파 시 계산 그래프가 만들어졌으니 또 하나의 순전파로 간주할 수 있게되고 다시 이에 대한 역전파를 수행함으로써 2차 이상의 고차 미분이 가능하게 된다.

3. 고차 미분을 Dezero에 적용시켜보자!

이제 고차 미분을 적용하는 이론에 대해 배워보았으니, 우리가 만든 Dezero 프레임워크의 코드 개선을 통해 확장시켜보도록 하자. 코드 개선의 핵심도 동일하다. 역전파 시에 전달되는 미분값들을 넘파이 배열 객체가 아닌 Variable 클래스 객체로 만들어주는 것! 가장 먼저 개선할 코드는 Variable 클래스이다. Variable 클래스에서는 다음의 코드 개선을 먼저 해주자.

 

class Variable:
    ...(생략)...

    def backward(self, retain_grad=False, use_heap=False):
        if self.grad is None:
            self.grad = Variable(np.ones_like(self.data))

        funcs = []
        seen_sets = set()
        flag = 0
    
    ...(생략)...

 

그리고 난 후, 오버로딩한 각 연산자(덧셈, 곱셈, 뺄셈, 나눗셈 등)마다 구현해준 역전파 backward() 메소드에서 캐싱된 인풋값들을 가져올 때 Variable 클래스 그대로 가져오도록 수정하도록 한다. Mul() 클래스에서는 아래처럼 수정해주면 된다.

 

class Mul(Function):
    def forward(self, x0, x1):
        return x0 * x1

    def backward(self, gy):
        # 원래는 아래처럼 되어있었음
        # x0 = self.inputs[0].data
        # x1 = self.inputs[1].data
        
        # 넘파이 배열이 아닌 Variable 클래스 객체 그대로 갖고오도록!
        x0, x1 = self.inputs
        return x1 * gy, x0 * gy

 

이와 동일하게 나머지 연산자들의 backward() 메소드에다가도 적용해주도록 하자. 

 

다음은 고차 미분이 아닌 1차 미분 즉, 1번의 역전파만 수행해줄 경우를 대비해서 역전파 활성/비활성 모드를 하나 더 추가해주도록 하자. 해당 모드는 Variable 클래스의 backward() 메소드에다가 추가해주도록 하자. 이 추가해준 부분은 우리가 기존에 만들었던 역전파 활성/비활성 모드의 동일한 값을 변경해주도록 한 것이다. 기존에 만들었던 부분은 Function 클래스에 존재한다. 이 추가된 모드를 사용하는 구체적인 방법은 이후 고차미분을 여러번 실습해보는 목차에서 다루도록 한다.

 

class Variable:
    ...(생략)...

    def backward(self, use_heap=False, retain_grad=False, create_graph=False):
        if self.grad is None:
            self.grad = Variable(np.ones_like(self.data))

        funcs = []
        seen_sets = set()
        flag = 0

        def add_func(f):
            if f not in seen_sets:
                if not use_heap:
                    funcs.append(f)
                    seen_sets.add(f)
                    funcs.sort(key=lambda x: x.generation)
                else:
                    heapq.heappush(funcs, (-f.generation, flag, f))
                    seen_sets.add(f)
        add_func(self.creator)

        while funcs:
            if not use_heap:
                f = funcs.pop()
            else:
                f = heapq.heappop(funcs)[2]
            gys = [y().grad for y in f.outputs]

			# 변경된 부분!
            with using_config('enable_backprop', create_graph):
                gxs = f.backward(*gys)
                if not isinstance(gxs, tuple):
                    gxs = (gxs,)
                for x, gx in zip(f.inputs, gxs):
                    if x.grad is None:
                        x.grad = gx
                    else:
                        x.grad = x.grad + gx

                    if x.creator is not None:
                        add_func(x.creator)
                        flag += 1
                flag = 0

                if not retain_grad:
                    for y in f.outputs:
                        y().grad = None

4. 개선된 Dezero로 고차미분 실습해보기

이번에는 3번 목차까지 다루면서 고차미분이 가능하도록 확장한 Dezero 프레임워크를 사용해서 실제 N차 미분을 계산할 수 있도록 해보자. 이번 예시에서는 $y = x^4 - 2x^2$ 이라는 4차항 식을 1차, 2차 미분하도록 해보자.

 

import numpy as np
from dezero import Variable

def f(x):
    y = x ** 4 - 2 * x ** 2
    return y

x = Variable(np.array(2.0))
y = f(x)

# 1차 미분 수행 및 미분값
y.backward(use_heap=True, create_graph=True)
print(x.grad)

gx = x.grad
# 2차 미분 수행 및 미분값
x.clear_grad()
gx.backward()
print(x.grad)

 

참고로 주의할 점은 1차 미분값을 구해주기 위해 역전파(backward)를 수행해준 후, $x$에 대한 미분값을 초기화시켜야 한다는 점에 주의하자. 해당 기능은 x.clear_grad() 이 담당한다. 만약 해당 코드가 없다면 2차 미분의 값은 1차 미분의 값에 2차 미분의 값이 누적되어 계산되게 된다.

 

그러면 이제 Dezero를 활용해서 고차 미분을 적용해보았으니 이번엔 고차 미분을 사용해 목적 함수를 최적화하는 뉴턴 방법(Newton Method)를 사용해보도록 하자. 참고로 저번 포스팅에서도 배웠지만 뉴턴 방법으로 $x$(파라미터)를 업데이트 하는 수식은 아래와 같다.(단, 여기서는 뉴턴 방법에서 사용되는 테일러 급수에서 2차항 즉, 2차 미분까지만 사용할 것임을 가정한다)

$$x \longleftarrow x - {f'(x) \over f''(x)}$$

위 뉴턴 방법 최적화 수식을 기반으로 $y = x^4 - 2x^2$ 라는 목적함수를 최적화시켜보도록 하자.

 

import numpy as np
from dezero import Variable

def f(x):
    y = x ** 4 - 2 * x ** 2
    return y

x = Variable(np.array(2.0))
iters = 10

for i in range(iters):
    print(i, x)
    
    y = f(x)
    x.clear_grad()
    
    # 1차 미분값
    y.backward(use_heap=True, create_graph=True)
    gx = x.grad
    x.clear_grad()
    
    # 2차 미분값
    gx.backward()
    gx2 = x.grad
    
    x.data -= gx.data / gx2.data

 

다음은 고차 미분을 테스트 해보기 위해서 잠시 Dezero 만의 함수를 만드는 시간을 가져보자. 다시 말해, Dezero의 Function 클래스를 상속받아서 Variable 인스턴스 객체를 처리할 수 있도록 할 것이다. 이번에 추가할 함수의 종류는 $sin, cos, tanh$ 함수 3가지이다. 먼저 $sin$ 함수부터 만들어보자. 참고로 $x$에 대한 $sin(x)$의 미분 값은 $cos(x)$이다.

 

import numpy as np
from dezero.core import Function


class Sin(Function):
    def forward(self, x):
        y = np.sin(x)
        return y

    def backward(self, gy):
        x, = self.inputs
        gx = gy * cos(x) # 주의! np.cos(x)이 아님!
        return gx
        
        
 def sin(x):
     return Sin()(x)

 

여기서 주의해야 할 부분은 bakcward() 메소드에 보면 코사인 함수를 적용하는 부분이 있는데, 이 때 넘파이의 코사인 함수가 아닌 그냥 cos(x)라고 되어 있는 부분이다. 여기서 넘파이의 코사인 함수를 사용하지 않는 이유는 뭘까? 우리는 위에서 고차 미분을 사용하기 위해 역전파(backward) 시에 넘겨받는 국소적인 미분값이나 순전파 시 사용한 캐싱된 입력값들을 Variable 클래스 객체로 처리하도록 했기 때문이다. 따라서, 위 코드에 적혀있는 cos(x) 라는 함수는 Variable 인스턴스 객체를 처리할 수 있도록 우리가 '직접' cos(x) 라는 함수를 만들어주어야 한다는 뜻이다! 그러면 이제 cos(x)를 아래처럼 만들어보자. 참고로 $x$에 대한 $cos(x)$의 미분값은 $-sin(x)$이다.

 

import numpy as np
from dezero.core import Function


class Cos(Function):
    def forward(self, x):
        y = np.cos(x)
        return y

    def backward(self, gy):
        x, = self.inputs
        gy = gy * -sin(x)
        return gy
        
        
def cos(x):
    return Cos()(x)

 

여기서도 backward() 메소드 안에서 넘파의 sin 함수가 아닌 그냥 sin(x)라고 쓰는데, 이것도 마찬가지이다. 마지막으로는 $tanh$ 함수를 만들어보자. 예전 책 시리즈 2권에서 배울 때 $tanh$ 함수의 미분에 대해서 배웠던 적이 있다. 어쨌건 $tanh$ 함수의 수식과 미분 공식은 다음과 같다. $$y = tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}$$ $$\begin{matrix} \partial tanh(x) \over\partial x &=& 1 - tanh(x)^2 \\ &=& 1 - y^2 \end{matrix}$$

 

import numpy as np
from dezero.core import Function


class Tanh(Function):
    def forward(self, x):
        y = np.tanh(x)
        return y

    def backward(self, gy):
        y = self.outputs[0]()  # 캐싱된 출력은 현재 약한 참조 중!
        gx = 1 - y ** 2
        return gx
        
        
def tanh(x):
    return Tanh()(x)

5. 고차 미분을 다른 곳에서도 써보자!

우리는 지금까지 고차 미분이 무엇인지, 그리고 코드로 어떻게 구현해내는지 살펴보았다. 더 나아가 N차 미분을 사용하는 최적화 기법인 뉴턴 방법도 구현해보았다. 이번 목차에서는 이러한 고차 미분이 또 다른 어떤 용도로 사용될 수 있는지에 대해 살펴보자.

 

고차 미분의 핵심은 계속 언급했지만 '역전파의 역전파'이다. 그래서 이를 double backpropagation 이라고 부른다. 이 double backpropagation 기능은 현대의 딥러닝 프레임워크 대부분에서 지원을 해준다.

 

double backpropagation은 일반적으로 미분의 식이 항에 존재하는 식을 미분해야 하는 문제에 사용이 된다. 이게 무슨 말인지 모르는게 당연할(?)테니, 다음과 같은 수학문제를 푼다고 가정해보자. $$ y = x^2$$ $$x = {\left( {\partial y} \over {\partial x} \right)}^3 + y$$

단, 여기서 $\left( {\partial y} \over {\partial x} \right)$은 어떤 숫자값이 아닌 '식'이라는 것을 주의하자. 위 문제를 해석적으로 풀면 아래처럼 풀 수 있다. $${{\partial y} \over {\partial x}} = 2x$$ $$z = {\left( {\partial y} \over {\partial x} \right)}^3 + y = 8x^3 + x^2$$ $${{\partial z} \over {\partial x}} = 24x^2 + 2x$$

그러면 위와 같은 문제를 이번에 Dezero를 활용해서 풀면 아래처럼 풀 수 있다.

 

import numpy as np
from dezero import Variable

x = Variable(np.array(2.0))
y = x ** 2
y.backward(create_graph=True)
gx = x.grad
x.clear_grad()

z = gx ** 3 + y
z.backward()
print(x.grad)

 

다음으로 double backpropagation이 또 다른 용도로 사용하는 예시를 살펴보자. 바로 딥러닝 연구인데, 아래 수식은 GAN 모델의 종류인 WGAN-GP 라는 논문에서 나오는 최적화 함수 수식이다.

 

double backpropagation이 사용되는 최적화 함수

 

위 수식에서 빨간색 밑줄쳐진 부분이 바로 1번의 역전파 즉, 미분을 사용해서 얻을 수 있는 기울기를 의미한다. 따라서 이미 1번의 역전파가 사용된 것이고, 이렇게 정의된 목적함수 식인 $L$을 최적화 하기 위해 이 $L$ 식에 대해 역전파 미분을 수행한다. 따라서 최종적으로는 총 2번의 미분이 사용된 것이다. 

 

double backpropagation의 또 다른 용도로는 헤세(Hessian) 행렬과 벡터의 곱을 구할 때 효율적인 연산을 수행하는 데 도움을 준다. 이에 대해서는 아래 목차에서 자세히 알아보도록 하자.

6. 뉴턴 방법과 double backpropagation 보충

마지막 목차는 책의 칼럼부분으로 수학적인 이론 내용이 주를 이룬다. 첫 번째로는 입력이 벡터인 경우의 뉴턴 방법에 대해 설명한다. 두 번째로는 이러한 뉴턴 방법을 대체할 수 있는 또 다른 방법을 소개하고, 마지막으로 double backpropagation의 실용적인 쓰임 예시 즉, 해세 행렬과 백터의 곱을 구할 때 효율적인 용도로 사용되는 예시를 알아보도록 하자.

 

먼저 입력이 벡터인 경우의 뉴턴 방법이다. 입력이 벡터라는 것은 입력 $x$들이 여러개라는 뜻이다. 지금까지 우리는 위에서 뉴턴 방법을 적용할 때 단순히 하나의 값 즉, 스칼라값 만으로 사용해보았다. 그런데 현실의 경우에는 이 $x$들이 한 개인 경우는 극히 드물다. 따라서 $x$들이 여러개인 벡터일 때 뉴턴 방법 수식이 어떻게 되는지 살펴보자. 우선 뉴턴 방법의 최적화 수식은 아래처럼 수식 표기법이 약간 변한다.

 

입력이 벡터일 경우의 최적화 수식

 

위 수식에서 기울기를 의미하는 $\nabla f(x)$ 은 아래와 같이 각 원소에 대한 미분을 의미한다.

 

$\nabla f(x)$는 각 원소에 대한 미분을 의미

 

그리고 ${\nabla}^2 f(x)$은 헤세 행렬을 의미한다. 해세 행렬의 식은 벡터의 두 원소에 대한 미분이다. 두 원소의 조합이 이루어지기 때문에 아래와 같이 행렬 상태로 정의된다.

 

${\nabla}^2 f(x)$는 해세 행렬을 의미

 

따라서 벡터일 경우의 뉴턴 메소드 최적화 수식은 벡터(x)를 기울기(1차 미분값) 방향으로 갱신하고 그 진행 거리를 헤세 행렬의 역행렬을 사용해 조정한다. 즉, 헤세 행렬이라는 2차 미분 정보를 이용해 더 공격적으로 최적화를 진행해서 우리가 원하는 최적화 목적지에 더 빠르게 도달할 수 있게 된다. 

 

하지만 이러한 뉴턴 방법은 머신러닝 특히, 딥러닝에서는 잘 사용되지 않는다. 왜냐하면 매개변수가 많아짐에 따라 즉, 위에서 설명한 벡터의 원소가 많아질수록 뉴턴 방법의 헤세 행렬 계산과 헤세 행렬의 역행렬 계산에 너무 많은 메모리가 소비되기 때문이다. 구체적으로, 벡터의 원소 개수가 $n$개라면 헤세 행렬이 $n \times n$ 크기이기 때문에 $n^2$만큼의 메모리를 사용하고, $n \times n$의 역행렬 계산에는 $n^3$만큼의 메모리를 사용하게 된다. 그래서 이런 뉴턴 방법에 대한 대안으로서 준 뉴턴 방법(QNM, Quasi-Newton Method)이 존재한다. QNM은 헤세 행렬의 역행렬을 '근사'해서 사용하는 방법이다. 

 

준 뉴턴 방법은 지금까지 여러개가 제안되었지만, 그 중에서 유명한 것이 L-BGFS라고 한다. L-BGFS는 기울기만으로 헤세 행렬을 근사한다. 말 그대로 '근사'이기 때문에 계산 비용과 메모리 공간을 절약하는 전략이다. 하지만 이러한 준 뉴턴 방법도 실제 딥러닝을 사용하는 경우에는 자주 사용되지 않고 여전히 기울기(1차 미분)만을 사용해서 최적화하는 SGD, Momentum, Adagrad, Adam 같은 최적화 기법이 주류로 사용된다.

 

마지막으로는 double backpropagation의 또 다른 유용한 용도로 사용되는 헤세 행렬과 벡터의 곱이다. 아까 위에서 입력이 벡터일 경우, 뉴턴 방법 수식을 정의할 때 헤세 행렬과 벡터의 곱이 계산된다고 했다. 그런데 벡터 원소 개수가 많아질수록 헤세 행렬의 계산 비용이 엄청나게 증가한다는 사실도 배웠다. 하지만 만약 헤세 행렬과 벡터간의 '곱셈 결과만 필요'하다면 double backpropagation을 활용해서 효율적으로 계산할 수 있다.

 

헤세 행렬과 벡터간의 곱셈 수식을 아래 수식의 우변처럼 변환이 가능하다.

 

헤세 행렬과 벡터간의 곱셈 수식

 

위 변환이 가능하다는 것을 증명하기 위해서 벡터 원소 개수를 2개로 한정해서 전개하면 아래처럼 된다.

 

변환 증명 수식

 

위 증명 수식을 보고 다시 수식을 살펴보자.

 

헤세 행렬과 벡터간의 곱셈 수식

 

주목할 부분은 방금 증명하려고 했던 위 수식의 우변이다. 결국 벡터 $v$와 기울기(1차 미분) $\nabla f(x)$의 곱 즉, 벡터의 내적을 먼저 구하고, 그 결과로부터 다시 한번 기울기(2차 미분)를 구한다는 의미이다. 이렇게 하면 헤세 행렬을 구하지 않아도 되므로 계산 효율이 좋아지는 것이다.


이렇게 해서 제3고지의 챕터가 모두 끝이 났다. 우리는 이번 고지를 통해서 Dezero 프레임워크를 한층 더 성장시킬 수 있게 되었다. 1차 미분하는 것에서 멈추는 것이 아닌 2차, 3차, N차 미분까지 가능하도록 만들었다. 이제 다음 고지부터 본격적으로 신경망을 만들 수 있도록 Dezero를 한층 더 업그레이드 하는 방법에 대해 배워보도록 하자. 고지가 얼마 남지 않았다!

 

반응형