영넌 개발로그

[밑시딥3] 자연스러운 코드로 1 - 가변 길이 인수, 같은 변수 반복 사용 본문

코딩/ML , Deep

[밑시딥3] 자연스러운 코드로 1 - 가변 길이 인수, 같은 변수 반복 사용

영넌 2023. 9. 6. 11:58

입출력 변수가 여러개인 경우가 있음

이를 고려하여 가변 길이 입출력을 처리할 수 있도록 코드 확장하기

 

순전파

변수들을 리스트에 넣어 처리

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)
함수를 '호출'할 때 별표
리스트의 원소를 낱개로 풀어서 전달하는 기법

 

 

역전파

순전파 : 입력 2, 출력 1 / 역전파 : 입력 1, 출력 2

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)

 

 

문제점

같은 변수를 반복해서 사용할 경우 의도대로 동작하지 않을 수 있음

y = add(x, x)

문제의 원인! 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
Comments