DeZero는 1차 미분에 한해서 미분을 자동으로 계산할 수 있다. 이번 단계에서는 DeZero가 2차, 3차, 4차 등 고차 미분까지 자동으로 계산할 수 있도록 확장할 것이다.
1. 현재 고차 미분을 할 수 없는 이유
foward계산과 달리 backward계산은 계산 그래프가 그려지지 않는다.
먼저 forward계산은 계산 그래프가 그려지는 것을 이해하기 위해 아래 Function클래스를 보자.
class Function:
def __call__(self, *inputs):
inputs = [as_variable(x) for x in inputs]
xs = [x.data for x in inputs]
ys = self.forward(*xs)
if not isinstance(ys, tuple):
ys = (ys,)
outputs = [Variable(as_array(y)) for y in ys]
if Config.enable_backprop:
self.generation = max([x.generation for x in inputs])
for output in outputs:
output.set_creator(self)
self.inputs = inputs
self.outputs = [weakref.ref(output) for output in outputs]
return outputs if len(outputs) > 1 else outputs[0]
예를 들어, a = sin(x)함수의 forward계산을 보자. 함수의 인자로 들어가는 input은 variable클래스이다. forward계산을 통해 도출된 output도 variable클래스를 적용하였다. 즉 sin(x)함수를 계산하면 그 input과 output의 연결고리가 이어진다.
다음으로 backward계산은 계산 그래프가 그려지지 않는 것을 이해하기 위해 아래 Variable클래스를 보자.
class Variable:
#생략
def backward(self, retain_grad=False):
if self.grad is None:
self.grad = np.ones_like(self.data)
funcs = []
seen_set = set()
def add_func(f):
if f not in seen_set:
funcs.append(f)
seen_set.add(f)
funcs.sort(key=lambda x: x.generation)
add_func(self.creator)
while funcs:
f = funcs.pop()
gys = [output().grad for output in f.outputs]
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 += gx
if x.creator is not None:
add_func(x.creator)
if not retain_grad:
for y in f.outputs:
y().grad = None
Sin클래스의 backward메서드에서 미분을 계산하는 식은 gx = gy * cos(x)이다. 이때 cos(x)는 input.data값이므로 ndarray이고, gy도 np.ones_like(self.data)에 의해 self.data의 타입과 같은 ndarray이다. 즉, Sin의 backward는 인자 gy와 반환값 gx모두 variable클래스가 아닌 ndarray이므로 계산 그래프가 만들어질 수 없다.
2. 고차 미분을 하기 위해 수정해야 하는 부분
역전파 계산에서도 순전파 계산과 마찬가지로 계산 그래프가 그려지도록 즉, 함수와 변수간 연결이 되도록 수정하면 된다. 그러기 위해 계산 그래프를 만들어주는 Variable클래스를 이용할 수 있다.
3. 고차 미분 구현
gx = gy * cos(x)에서 gy와 gx를 Variable클래스로 바꾸기 위해서는 gy를 Variable로 바꾸면 연산자 오버로드에 의해 gx도 Variable이 된다.
class Variable:
#생략
def backward(self, retain_grad=False):
if self.grad is None:
#self.grad = np.ones_like(self.data)
self.grad = Variable(np.ones_like(self.data))
#생략
gy를 설정하는 np.ones_like에 Variable클래스를 적용함으로써 간단하게 해결할 수 있다. 이제 미분값을 자동으로 저장하는 부분에서 self.grad가 Variable클래스를 담는다.
4. 함수 클래스의 backward 메서드 수정
[수정 전]
class Mul(Function):
#생략
def backward(self, gy):
x0 = self.inputs[0].data
x1 = self.inputs[1].data
return gy * x1, gy * x0
[수정 후]
class Mul(Function):
#생략
def backward(self, gy):
x0, x1 = self.inputs
return gy * x1, gy * x0
수정 전에는 inputs이라는 Variable인스턴스 안에 있는 ndarray인스턴스인 data를 꺼내야 했지만, 수정 후에는 inputs이라는 Variable인스턴스를 그대로 사용하였다.
5. 역전파를 더 효율적으로 (모드 추가)
만약 역전파를 처음 한 번 이후로 다시 할 일이 없다면 1회 후 역전파 계산을 '역전파 비활성 모드'로 실행하려 한다.
모드를 추가하기 위해 Variable클래스의 backward메서드에 코드를 추가할 것이다.
def backward(self, retain_grad=False, create_graph=False): #마지막 인자 추가
#생략
while funcs:
f = funcs.pop()
gys = [output().grad for output in f.outputs]
with using_config('enable_backprop', create_graph):
gxs = f.backward(*gys) #메인 backward
if not isinstace(gxs, tuple):
gxs = (gxs,)
for x, gx in zip(f.inputs, gxs):
if x.grad is None:
x.grad = gx
else:
x.grad += gx #이 계산도 대상
if x.creator is not None:
add_func(x.creator)
2차 이상의 미분이 필요하다면 create_graph=True로 설정한다. with블록을 통과할 때는 Function클래스에서 if Config.enable_backprop: 블록이 실행되지 않는다.
6. __init__.py 변경
지금까지의 수정을 새로운 dezero/core.py에 저장할 것이다. 앞으로는 core_simple.py 대신 core.py를 사용할 것이다.
따라서 dezero/__init__.py를 다음과 같이 수정한다.
is_simple_core = False #step23.py ~ step32.py까지는 simple_core이용
if is_simple_core:
from dezero.core_simple import Variable
from dezero.core_simple import Function
from dezero.core_simple import using_config
from dezero.core_simple import no_grad
from dezero.core_simple import as_array
from dezero.core_simple import as_variable
from dezero.core_simple import setup_variable
else:
from dezero.core import Variable
from dezero.core import Function
from dezero.core import using_config
from dezero.core import no_grad
from dezero.core import as_array
from dezero.core import as_variable
from dezero.core import setup_variable
'AI > 딥러닝 프레임워크 개발' 카테고리의 다른 글
34~35단계) sin 함수 미분 , tanh 함수 미분 (0) | 2021.07.12 |
---|---|
33단계) 뉴턴 방법 최적화 자동화 , 2차 미분 자동 계산 (0) | 2021.07.09 |
28~29단계) 경사하강법 , 뉴턴 방법 , 함수 최적화 (4) | 2021.07.03 |
27단계) 테일러 급수 미분 (0) | 2021.06.29 |
25~26단계) 계산 그래프 시각화 , DOT (0) | 2021.06.29 |
댓글