영넌 개발로그

[밑시딥3] 미분 자동 계산 3 - 함수를 더 편리하게 본문

코딩/ML , Deep

[밑시딥3] 미분 자동 계산 3 - 함수를 더 편리하게

영넌 2023. 8. 16. 06:32

개인 공부용 포스팅

 

1. square, exp 클래스를 함수화

함수를 파이썬 클래스로 정의하여 사용하다보니 인스턴스를 생성 후 호출하는 두 단계로 구분해 진행했어야 한다.

따라서 두 행동을 한번에 해주는 함수 생성 (square, exp)

def square(x):
    return Square()(x)

def exp(x):
    return Exp()(x)
    
#--------------------------------------------------------------
#위 함수와 같은 의미
def square(x):
    f = Square()
    return f(x)
#실행코드
x = Variable(np.array(0.5))
y = square(exp(square(x)))
y.grad = np.array(1.0)
y.backward()
print(x.grad)

 

2. Variable - backward() 수정

역전파를 할 때마다 시작 지점 y.grad = np.array(1.0) 을 적어주었음

이를 생략하는 코드 작성

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):
        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)

            if x.creator is not None:
                funcs.append(x.creator)
np.ones_like(self.data)

self.data와 형상과 데이터 타입이 같은 ndarray 인스턴스를 생성하는데, 모든 요소를 1로 채움
Variable의 data와 grad의 데이터 타입을 같게 만들기 위함
#실행코드
x = Variable(np.array(0.5))
y = square(exp(square(x)))
y.backward()
print(x.grad)

 

3. ndarray만 취급하기

Variable은 데이터로 ndarray 인스턴스만 취급하도록 의도하였으나 사용을 그렇게 하지 않을 수도 있음

ndarray 인스턴스만을 담도록 변경

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)

            if x.creator is not None:
                funcs.append(x.creator)
#실행코드
x = Variable(np.array(0.5))  #OK
x = Variable(None)   #OK

x = Variable(1.0)    #error

 

ndarray가 스칼라일 경우 (.ndim이 0일 경우) 제곱과 같은 수식을 이용하면 np.float64 등의 타입으로 변경됨

따라서 이를 대처하기 위해 편의 함수를 만든다.

 

스칼라 타입인지 확인하는 함수를 만들어 이를 이용하여 입력이 스칼라인 경우 ndarray 인스턴스로 변환함

이후 Function 클래스에서 순전파의 결과를 Variable로 감쌀 때 항상 as_array()를 이용하여 결과 output이 항상 ndarray 인스턴스가 되도록 보장함

def as_array(x):
    if np.isscallar(x):
        return np.array(x)
    return x
class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x) 
        output = Variable(as_array(y))  #y->as_array(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()
Comments