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