방금 역전파 시 미분값을 더해주도록 코드를 수정했습니다. 그런데 이 변경으로 인해 새로운 주의사항이 뛰어나옵니다. 바로 같은 변수를 사용하여 '다른' 계산을 할 경우 계산이 꼬이는 문제입니다. 다음 코드르 예로 살펴봅시다.
# 첫 번째 계산
x = Variable(np.array(3.0))
y = add(x, x)
y.backward()
print(x.grad)
# 두 번째 계산(같은 x를 사용하여 다른 계산을 수행)
y = add(add(x, x), x)
y.backward()
print(x.grad)
2.0
5.0
앞의 코드는 서로 다른 두 가지 미분 계산을 수행했습니다. 그러면서 메모리를 절약하고자 Variable 인스턴스인 x를 재사용했다고 해봅시다. 그 결과 두 번째 x의 미분값에 첫 번째 미분값이 더해지고, 5.0이라는 잘못된 값을 돌려줍니다(3.0이 되어야 합니다).
이문제를 해결하기 위해 Variabvle 클래스에 미분값을 초기화하는 cleargrad메서드를 추가하겠습니다.
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()
gys = [output.grad for output in f.outputs]
gxs = f.backward(*gys)
if not isinstance(gxs, tuple):
gxs = (gxs,)
for x, gx in zip(f.inputs, gxs):
if x.grad is None:
x.grad = gx
else:
x.grad = x.grad + gx
if x.creator is not None:
funcs.append(x.creator)
def cleargrad(self):
self.grad = None
cleargrad는 미분값을 초기화하는 메서드로, 단순히 self.grad에 None을 대입합니다. 이 메서드를 사용하면 여러가지 미분을 연달아 계산할 때 똑같은 변수를 재사용할 수 있습니다. 앞의 예는 다음과 같이 수정하면 됩니다.
# 첫 번째 계산
x = Variable(np.array(3.0))
y = add(x, x)
y.backward()
print(x.grad)
# 두 번째 계산(같은 x를 사용하여 다른 계산을 수행)
x.cleargrad() #미분값 초기화
y = add(add(x, x), x)
y.backward()
print(x.grad)
2.0
3.0
이번에는 두 번째 미분값도 재대로 구했습니다(두 번째 미분의 올바른 값은 3.0입니다). 예시처럼 두 번째 x.backward()를 호출하기 전에 x.cleargrad()를 호출하면 변수에 누적된 미분값이 초기화 됩니다. 이것으로 다른 계산에 똑같은 변수를 재사용할 때 생기던 문제가 사라졌습니다.
DeZero의 cleargrad메서드는 최적화 문제를 풀 때 유용하게 사용할 수 있습니다. 최적화 문제란 함수의 최솟값과 최댓값을 찾는 문제를 말합니다. 실제로 28단계에서 로젠브록 함수(Rosenbrock function)를 최적화할 때 cleargrad메서드를 사용합니다.
이상으로 이번 단계를 마무리 합니다. 이번 단곌르 거치며 Variable 클래스는 한층 성장했습니다. 그러나 아직도 중요한 문제가 하나 남아 있습니다. 그 문제는 이어지는 15, 16단계에서 해결하겠슷ㅂ니다. 16단계에서 마치면 마침내 Variable클래스가 완성됩니다.
댓글 없음:
댓글 쓰기