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

[스터디/밑시딥3] 제 1고지 미분 자동 계산_7.역전파 자동화

by LENA-cfg 2023. 8. 13.

7.1 역전파 자동화의 시작

class Variable:
    def __init__(self, data):
        if data is not None:
            if not isinstance(data, np.ndarray):
                raise TypeError('{}은(는) 지원하지 않습니다.'.format(type(data)))

        self.data = data # 통상값
        self.grad = None # 미분값
        self.creator = None 

    def set_creator(self, func):
        self.creator = func
        
 class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x)
        output = Variable(as_array(y))
        output.set_creator(self) # 함수와의 관계 설정
        self.input = input # 입력 변수 기억
        self.output = output # 출력 변수 기억
        return output

- 해당 방식이 파이토치 같은 프레임워크에서 사용되는 동적 계산 그래프 방식임

- 동적 계산 그래프는 딥러닝 프레임워크에서 연산을 표현하고 그래디언트를 계산하기 위한 방법중 하나임 

- 연산이 실행될 때마다 그래프를 동적으로 생성함

-(장점) 다양한 모델 구조와 제어 플로우를 쉽게 구현할 수 있고 디버깅이 쉬우며 코드와 결과를 직관적으로 이해할 수 있음

-(단점) 동일 연산을 여러번 반복해야해서 정적 계산 그래프보다 비효율적이고 정적 계산 그래프(전체 그래프를 미리 알아 연산 최적화가 쉬움)에 비교해 실행 시점에만 그래프를 알기 때문에 최적화의 범위가 제한적임

파란색 글씨가 Variable 클래스안에 변경된 부분인데 

self.creator = None으로 creator가 담길 곳을 선언해주고

def set_creator 메소드를 통해 self.creator에 func를 담아줌

* backward 메소드는 7.3에서 마저 설명하기로~~

 

Function 클래스에서 output.set_creator(self)를 해주면 self 해당하는 연산 클래스(Square or Exp)를 저장해줌

위 이미지처럼 print('set_creator:', self.creator)찍어서 보면

Square, Exp가 각각 Variable 상자안에 self.creator로 잘 저장된 것을 알 수 있음

 

7.3 backward 메서드 추가

backward(self) 메서드는 변수에서 하나 앞의 변수로 거슬러 올라가는 로직(=반복 작업)을 자동화해줌

맨 마지막 줄인 x.backward()는 x 앞의 변수의 backward 메서드를 호출하여 재귀적으로 사용하게됨

 

이 방식을 실제로 print 해서 찍어서 보면 재귀적으로 사용한 방식 굉장히 비효율적인 것을 알 수 있음=> 다음장에서 For문으로 바꿔줌

x: 1.2840254166877414
x.grad: 2.568050833375483
x: 0.25
x.grad: 3.297442541400256
x: 0.5
x.grad: 3.297442541400256
x.backward() None


x: 0.5
x.grad: 3.297442541400256
x.backward() None


x.backward() None


x: 0.25
x.grad: 3.297442541400256
x: 0.5
x.grad: 3.297442541400256
x.backward() None


x: 0.5
x.grad: 3.297442541400256
x.backward() None

# 전체 소스 코드

import numpy as np


class Variable:
    def __init__(self, data):
        self.data = data
        self.grad = None
        self.creator = None

    def set_creator(self, func):
        self.creator = func
        print(self.creator)

    def backward(self):
        f = self.creator  # 1. Get a function
        if f is not None:
            x = f.input  # 2. Get the function's input
            x.grad = f.backward(self.grad)  # 3. Call the function's backward
            x.backward()


class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x)
        output = Variable(y)
        output.set_creator(self)# Set parent(function)
        print(output.set_creator(self))
        self.input = input
        self.output = output  # Set output
        return output

    def forward(self, x):
        raise NotImplementedError()

    def backward(self, gy):
        raise NotImplementedError()


class Square(Function):
    def forward(self, x):
        y = x ** 2
        return y

    def backward(self, gy):
        x = self.input.data
        gx = 2 * x * gy
        return gx


class Exp(Function):
    def forward(self, x):
        y = np.exp(x)
        return y

    def backward(self, gy):
        x = self.input.data
        gx = np.exp(x) * gy
        return gx


A = Square()
B = Exp()
C = Square()

x = Variable(np.array(0.5))
a = A(x)
b = B(a)
y = C(b)

# backward
y.grad = np.array(1.0)
y.backward()
print(x.grad)