Deep learning/모델 구현

23. 역전파 자동화

jwjwvison 2021. 9. 29. 22:47

 이번에는 역전파를 자동화해보겠다. 더 정확히 말하면, 일바적인 계산(순전파)를 한 번만 해주면 어떤 계산이라도 상관없이 역전파가 자동으로 이루어지는 구조를 만들 것이다. 이것이 바로 Define-by-Run의 핵심이다.

 

 먼저 일반적인 계산(순전파)이 이루어지는 시점에 '관계'를 맺어주도록 (즉, 함수와 변수를 연결 짓도록) 만들겠다.

class Variable:
    def __init__(self,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(y)
        output.set_creator(self) # 출력 변수에 참조자 설정
        self.input=input
        self.output=output # 출력도 저장
        return output
    
    def forward(self,x):
        raise NotImplementedError()
        
    def backward(self,gy):
        raise NotImplementedError()

 순전파를 계산하면 그 결과로 output이라는 Variable 인스턴스가 생성된다. 이때 생성된 output에 '내가 너의 창조자임'을 기억시킨다. 이 부분이 연결을 동적으로 만드는 기법의 핵심이다.

 

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

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

 위 그림과 같이 우리 계산 그래프는 함수와 변수 사이의 연결로 구성된다. 그리고 중요한 점은 이 '연결'이 실제로 계산을 수행하는 시점에(순전파로 데이터를 흘려보낼 때) 만들어진다는 것이다. 이러한 특성에 이름을 붙인 것이 Define-by-Run이다. 데이터를 흘려보냄으로써 연결이 규정된다는 뜻이다.

 

 변수와 함수의 관계를 이용하여 역전파를 시도해보겠다.

y.grad=np.array(1.0)

C=y.creator  # 함수를 가져온다
b=C.input    # 함수의 입력을 가져온다
b.grad=C.backward(y.grad)  # 함수의 backward 메서드를 호출

B=b.creator
a=B.input
a.grad=B.backward(b.grad)

A=a.creator
x=A.input
x.grad=A.backward(a.grad)
print(x.grad)

 

 이번에는 backward 메서드를 Variable 클래스에 추가하여 이 반복 작업을 자동화할 수 있도록 하겠다.

class Variable:
    def __init__(self,data):
        self.data=data
        self.grad=None
        self.creator=None
        
    def set_creator(self,func):
        self.creator=func
        
    def backward(self):
        f = self.creator # 함수를 가져온다
        if f is not None:
            x=f.input
            x.grad=f.backward(self.grad)
            x.backward() # 하나 앞 변수의 backward 메서드를 호출한다(재귀)
A=Square()
B=Exp()
C=Square()

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

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

 이번에는 처리의 효율을 위해 재귀 구조를 반복문으로 바꿔보겠다.

class Variable:
    def __init__(self,data):
        self.data=data
        self.grad=None
        self.creator=None
        
    def set_creator(self,func):
        self.creator=func
        
    def backward(self):
        funcs=[self.creator]
        while funcs:
            f=funcs.pop()        # 함수를 가져온다
            x,y=f.input,f.output # 함수의 입력과 출력을 가져온다
            x.grad=f.backward(y.grad)  # backward 메서드를 호출한다
            
            if x.creator is not None:
                funcs.append(x.creator)  # 하나 앞의 함수를 리스트에 추가한다

 

 마지막으로 함수를 더 편리하게 사용하기 위해 

1. 파이썬 함수로 사용

2. backward 메서드 간소화

3. ndarray만 취급하기

 를 적용하여 마무리 짓겠다.

def square(x):
    f=Square()
    return f(x)
    # return Square()(x)

def exp(x):
    f=Exp()
    return f(x)
    # return Exp()(x)
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
        
    def backward(self):
        if self.grad is None:
            self.grad = np.ones_like(self.data)
        funcs=[self.creator]
        while funcs:
            f=funcs.pop()        # 함수를 가져온다
            x,y=f.input,f.output # 함수의 입력과 출력을 가져온다
            x.grad=f.backward(y.grad)  # backward 메서드를 호출한다
            
            if x.creator is not None:
                funcs.append(x.creator)  # 하나 앞의 함수를 리스트에 추가한다
                
def as_array(x):
    if np.isscalar(x):
        return np.array(x)
    return x
                
                
x=Variable(np.array(0.5))
y=square(exp(square(x)))
y.backward()
print(x.grad)

 

'Deep learning > 모델 구현' 카테고리의 다른 글

25. 가변 길이 인수(역전파)  (0) 2021.10.04
24. 가변 길이 인수(순전파)  (0) 2021.10.04
22. 역전파  (0) 2021.09.29
21. 수치미분  (0) 2021.09.28
20. 변수와 함수  (0) 2021.09.28