본문 바로가기
Study 공부할레나

[스터디/밑시딥3] 제 3고지 고차미분계산_32. 고차 미분(구현 편)

by LENA-cfg 2023. 9. 9.

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에서 임포트할 수 있습니다.