32.1 새로운 DeZero로!
수정 전
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)
위에 표시한 부분인 self.grad = np.ones_like(self.data) 여기를 수정할 건데
미분값을 자동 저장할 수 있게 Variable 인스턴스가 되도록 변경할 겁니다.
변경된 부분은 아래처럼 Variable로 담아주면 됩니다.
self.grad = Variable(np.ones_like(self.data))
수정 후
class Variable:
def backward(self, retain_grad=False):
if self.grad is None:
# 수정할 부분
self.grad = Variable(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)
32.2 함수 클래스의 역전파
DeZero의 함수들을 backward 메서드를 수정합니다.
지금까지 구현된 함수 클래스: Add, Mul, Neg, Sub, Div, Pow
위의 클래스의 backward 메서드를 수정하고 core.py로 옮겨줍니다.
class Add(Function):
def backward(self, gy):
return gy, gy
Add 클래스는 backward에서 gy, gy를 그대로 흘려주기 때문에(아무것도 계산을 안해서) 수정할 것이 없습니다.
Mul class 수정 전
class Mul(Function):
def backward(self, gy):
x0, x1 = self.inputs[0].data, self.inputs[1].data
return gy * x1, gy * x0
Mul 클래스는 Variable 인스턴스 안에 있는 데이터(ndarray 인스턴스)를 .data로 꺼내줘야했는데 이부분을 self.inputs으로 수정해줍니다.
Mul class 수정 후
class Mul(Function):
def backward(self, gy):
x0, x1 = self.inputs
return gy * x1, gy * x0
이렇게 수정해주면 gy, x1, x0이 다 Variable 인스턴스로 전달 되는데 gy * x1에서 * 연산자는 이미 오버로드 되어 있어서 gy * x1이 실행 될때 Mul 클래스의 순전파가 호출되고 이때, Function.__call__()이 호출되고 계산 그래프가 만들어집니다.
* 연산자 오버로드 된 부분
def setup_variable():
Variable.__add__ = add
Variable.__radd__ = add
Variable.__mul__ = mul
Variable.__rmul__ = mul
Variable.__neg__ = neg
Variable.__sub__ = sub
Variable.__rsub__ = rsub
Variable.__truediv__ = div
Variable.__rtruediv__ = rdiv
Variable.__pow__ = pow
책에서는 Sub, Div, Pow 클래스의 backward 메서드도 같은 방법으로 수정하면 된다고 했습니다.
근데 Sub은 수정할 부분이 없는거 같고 Div와 Pow만 아래 처럼 수정해주면 될 것 같습니다.
# Div
class Div(Function):
def backward(self, gy):
x0, x1 = self.inputs
gx0 = gy / x1
gx1 = gy * (-x0 / x1 ** 2)
if x0.shape != x1.shape: # for broadcast
gx0 = dezero.functions.sum_to(gx0, x0.shape)
gx1 = dezero.functions.sum_to(gx1, x1.shape)
return gx0, gx1
# Pow
class Pow(Function):
def backward(self, gy):
x = self.inputs
c = self.c
gx = c * x ** (c - 1) * gy
return gx
32.3 역전파를 더 효율적으로(모드 추가)
18 단계에서 구현했던 역전파 비활성 모드를 사용하여 역전파 처리(계산 그래프 생성과 입력 변수 유지 등)를 생략한 것 처럼 이번 절에서도 역전파에서 수행하는 계산에도 같은 전략을 사용합니다.
역전파를 1회만 한다면 '역전파 비활성 모드'로 실행하도록 하는 겁니다.

인수 create_graph를 추가하고 기본값을 False로 설정한 후
실제 역전파 처리를 with using_config(..)에서 수행합니다.
create_graph가 False면 역전파로 인한 게산은 '역전파 비활성 모드'에서 이루어집니다.
(추가 설명) Mul 클래스의 backward 메서드는 gy * x1 계산을 할때 * 연산자는 오버로드 되어 있어서 Mul()(gy, x1) 코드가 호출되고 -> 부모 클래스 Function의 __call__() 호출되고 Function.__call__() 메서드에서 Config.enable_backprop이 참조되고, 역전파 활성/비활성 모드가 전환됩니다.
create_graph=False로 기본 설정한 이유는 실무에서 역전파가 단 1회만 수행되는 경우가 압도적으로 많기 떄문이고
2차 이상의 미분이 필요하면 True로 설정하면 됩니다.
32.4 __init__.py 변경
is_simple_core = False
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
is_simple_core 부분을 False로 바꿔줍니다.
그러면 주요 기능을 dezero/core.py에서 임포트할 수 있습니다.
'Study 공부할레나' 카테고리의 다른 글
| [스터디/밑시딥3] 제 3고지 고차미분계산_34. sin 함수 고차 미분 (0) | 2023.09.11 |
|---|---|
| [스터디/밑시딥3] 제 3고지 고차미분계산_33. 뉴턴 방법으로 푸는 최적화(자동계산) (0) | 2023.09.10 |
| [스터디/밑시딥3] 제 3고지 고차미분계산_31. 고차 미분(이론 편) (0) | 2023.09.09 |
| [스터디/밑시딥3] 제 3고지 고차미분계산_30. 고차 미분(준비편) (0) | 2023.09.09 |
| [스터디/밑시딥3] 제 3고지 고차미분계산_29. 뉴턴 방법으로 푸는 최적화(수동 계산) (0) | 2023.09.09 |