이번 단계에서는 고차 미분을 자동으로 게산 할 수 있도록 DeZero를 확장합니다.
30.1 확인 1: Variable 인스턴스 변수
class Variable:
__array_priority__ = 200
def __init__(self, data, name=None):
if data is not None:
if not isinstance(data, np.ndarray):
raise TypeError('{} is not supported'.format(type(data)))
self.data = data
self.name = name
self.grad = None
self.creator = None
self.generation = 0
Variable 클래스에는 인스턴스 변수가 여러개 인데 그 중에 data, grad는 각각 순전파 계산과 역전파 계산 때 사용합니다.
둘다 ndarray 인스턴스를 저장합니다.

이 그림은 Variable를 새롭게 그리는 방법입니다.

데이터를 참조 했을때 위의 그림처럼 상자의 색상이 바뀌는 방식으로 표현합니다.
30.2 확인 2: Function 클래스
class Function:
def __call__(self, *inputs):
inputs = [as_variable(x) for x in inputs]
# 1. 순전파 계산(메인 처리)
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])
# 2. '연결'을 만듦
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]
1. 순전파 계산(메인 처리)
xs = [x.data for x in inputs] 부분에서 Variable 인스턴스 변수 data를 꺼내 리스트 xs로 모으고 forward(*xs)를 호출해 계산을 수행합니다.
2. '연결'을 만듦
여기서는 Variable과 Function의 '관계'가 만들어집니다.
변수에서 함수로의 '연결'은 set_creator 메서드가 만들어주고 새로 생성된 Variable에 부모 함수(자신)를 알려주는 것이죠. 또한 함수의 입력과 출력 변수를 inputs과 outputs라는 인스턴스 변수에 저장해 함수에서 변수로의 '연결'을 유지합니다.
DeZero의 함수는 모두 Function 클래스를 상속하고 구체적인 계산은 상속된 클래스의 forward 메서드에서 구현됩니다.
아래의 sin 함수를 계산하는 Sin 클래스의 코드를 보면
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 forward(self, x):
y = np.sin(x)
return y
forward의 인수 x와 반환값 y는 모두 ndarray 인스턴스 입니다.
def backward(self, gy):
x = self.inputs[0].data
gx = gy * np.cos(x)
return gx
backward의 인수 gy와 반환값 gx도 ndarray 인스턴스 입니다.
def sin(x):
return Sin()(x)
x = Variable(np.array(1.0))
y = sin(x)
해당 sin 함수의 순전파만 진행해봤습니다.
변수와 함수의 동작을 시각화 하면 아래 그림 처럼 됩니다.

순전파의 계산은 Sin 클래스의 forward 메서드에서 진행되고 변수와 함수의 연결이 만들어집니다.
실제 연결은 Function 클래스의 __call__메서드에서 만들어집니다.
30.3 확인 3: Variable 클래스의 역전파
역전파는 Variable 클래스의 backward 메서드에서 구현합니다.
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] # 1
gxs = f.backward(*gys) # 2
if not isinstance(gxs, tuple):
gxs = (gxs,)
for x, gx in zip(f.inputs, gxs): # 3
if x.grad is None:
x.grad = gx
else:
x.grad = 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
gys = [output().grad for output in f.outputs] # 1
여기서 Variable의 인스턴스 변수 grad를 리스트로 모으는데 grad는 ndarray 인스턴스를 참조하고 있습니다.
gxs = f.backward(*gys) # 2
gys는 ndarray 인스턴스가 담긴 리스트가 전달 되고
for x, gx in zip(f.inputs, gxs): # 3
if x.grad is None:
x.grad = gx
else:
x.grad = x.grad + gx
출력 쪽에서 전차하는 미분값 gxs를 함수의 입력 변수(f.inputs)의 grad로 설정합니다.
x = Variable(np.array(1.0))
y = sin(x)
y.backward(retain_grad = True)
sin 함수의 순전파, 역전파를 모두 진행하고 retain_grad = True를 해줌으로써 모든 변수가 미분 결과를 메모리에 유지합니다.
해당 변수, 함수의 동작을 시각화 하면 아래처럼 변합니다.



y = sin(x)가 실행될 때 계산 그래프가 만들어지고, Variable 인스턴스 변수인 data가 채워집니다.
역전파 시 Sin 클래스의 backward 메서드가 불리고 Variable의 인스턴스 변수인 grad가 채워집니다.
'Study 공부할레나' 카테고리의 다른 글
| [스터디/밑시딥3] 제 3고지 고차미분계산_32. 고차 미분(구현 편) (0) | 2023.09.09 |
|---|---|
| [스터디/밑시딥3] 제 3고지 고차미분계산_31. 고차 미분(이론 편) (0) | 2023.09.09 |
| [스터디/밑시딥3] 제 3고지 고차미분계산_29. 뉴턴 방법으로 푸는 최적화(수동 계산) (0) | 2023.09.09 |
| [스터디/밑시딥3] 제 3고지 고차미분계산_28. 함수 최적화 (1) | 2023.09.06 |
| [스터디/밑시딥3] 제 3고지 고차미분계산_27. 테일러 급수 미분 (0) | 2023.09.06 |