영넌 개발로그
[밑시딥3] 자연스러운 코드로 1 - 가변 길이 인수, 같은 변수 반복 사용 본문
입출력 변수가 여러개인 경우가 있음
이를 고려하여 가변 길이 입출력을 처리할 수 있도록 코드 확장하기
순전파
변수들을 리스트에 넣어 처리
class Function:
def __call__(self, inputs):
xs = [x.data for x in inputs]
ys = self.forward(xs)
outputs = [Variable[as_array(y)) for y in ys]
for output in outputs:
output.set_creator(self)
self.inputs = inputs
self.outputs = outputs
return outputs
def forward(self, xs):
raise NotImplementedError()
def backward(self, gys):
raise NotImplementedError()
class Add(Funtion):
def forward(self, xs):
x0, x1 = xs
y = x0 + x1
return (y, ) #튜플
#사용
xs = [Variable(np.array(2)), Variable(np.array(3))]
f = Add()
ys = f(xs)
y = ys[0]
print(y.data)
개선
class Function:
def __call__(self, *inputs): #별표
xs = [x.data for x in inputs]
ys = self.forward(*xs) #별표
if not isinstance(ys, tuple): #튜플이 아닌 경우 지원
ys = (ys, )
outputs = [Variable[as_array(y)) for y in ys]
for output in outputs:
output.set_creator(self)
self.inputs = inputs
self.outputs = outputs
#리스트의 원소가 하나라면 첫 번재 원소를 반환한다.
return outputs if len(outputs) > 1 else outputs[0]
def forward(self, xs):
raise NotImplementedError()
def backward(self, gys):
raise NotImplementedError()
class Add(Function):
def forward(self, x0, x1):
y = x0 + x1
return y
def add(x0, x1):
return Add()(x0, x1)
#사용
x0 = Variable(np.array(2))
x1 = Variable(np.array(3))
y = add(x0, x1) #Add 클래스 생성 과정 x
print(y.data)
리스트 언팩(list unpack)
함수를 '호출'할 때 별표
리스트의 원소를 낱개로 풀어서 전달하는 기법
역전파
class Add(Funtion):
def forward(self, xs):
x0, x1 = xs
y = x0 + x1
return (y, )
def backward(self, gy):
return gy, gy
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 isinstace(gxs, tuple):
gxs = (gxs, )
#역전파로 전파되는 미분값을 Variable의 인스턴스 변수 grad에 저장
for x, gx in zip(f.inputs, gxs):
x.grad = gx
if x.creator is not None:
funcs.append(x.creator)
class Square(Function):
def forward(self, x):
return x**2
def backward(self, gy):
x = self.inputs[0].data #x = self.input.data
gx = 2 * x * gy
return gx
z = x^2 + y^2 계산 코드
x = Variable(np.array(2.0))
y = Variable(np.array(3.0))
z = add(square(x), square(y))
z.backward() #미분 계산 자동으로 이뤄짐
print(z.data)
print(x.grad)
print(y.grad)
문제점
같은 변수를 반복해서 사용할 경우 의도대로 동작하지 않을 수 있음
문제의 원인! Variable 클레스에 ' x.grad = gx ' 부분
출력 쪽에서 전해지는 미분 값을 그대로 대입 == 같은 변수를 반복해서 사용하면 전파되는 미분값이 덮어 써짐
class Variable:
...
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 isinstace(gxs, tuple):
gxs = (gxs, )
for x, gx in zip(f.inputs, gxs):
#x.grad = gx
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
#사용
x = Variable(np.array(3.0))
y = add(x, x)
y.backward()
print(x.grad) #2.0
x.cleargrad()
y = add(add(x, x), x)
y.backward()
print(x.grad) #3.0
'코딩 > ML , Deep' 카테고리의 다른 글
[밑시딥3] 자연스러운 코드로 3 - 메모리 관리, 절약 모드 (0) | 2023.09.06 |
---|---|
[밑시딥 3] 자연스러운 코드로 2 - 복잡한 계산 그래프 (0) | 2023.09.06 |
[밑시딥3] 미분 자동 계산 4 - 테스트 (0) | 2023.09.05 |
[밑시딥3] 미분 자동 계산 3 - 함수를 더 편리하게 (0) | 2023.08.16 |
Comments