본문 바로가기
Study 공부할레나

[스터디/밑시딥3] 제 2고지 자연스러운 코드로_20. 연산자 오버로드(1)

by LENA-cfg 2023. 8. 27.

이번장의 목표: Variable 인스턴스 a, b가 있을 때, y = a * b처럼 연산할 수 있게 확장하는 것입니다.

 

20.1 Mul 클래스 구현

y = x0 * x1 일때 x0을 y에 대해 미분하면 ∂y/∂x0 = x1, x1을 y에 대해 미분하면 ∂y/∂x1 = x0 입니다.

 

그러나, 우리는 최종 출력인 'L'에 대해 x0, x1의 미분을 각각 구할 것입니다.

* 여기서 말하는 L은 스칼라를 출력하는 합성 함수로 가정하고 Loss인 오차(=손실)을 의미합니다.

 

L에 대한 곱셈의 순전파

 

우리는 최종 출력인 L에 대해 미분을 한다고 했을 때

즉, L의 변수에 대한 미분을 전파한다고 했을 했을때는 ∂L/∂y이 전파되어 

∂y/∂x0 = ∂L/∂y *  x1

∂y/∂x1 = ∂L/∂y *  x0 이 됩니다.

 

Mul 클래스 구현

class Mul(Function):
    def forward(self, x0, x1):
        y = x0 * x1
        return y

    def backward(self, gy):
        x0, x1 = self.inputs[0].data, self.inputs[1].data
        return gy * x1, gy * x0


def mul(x0, x1):
    return Mul()(x0, x1)
    
a = Variable(np.array(3.0))
b = Variable(np.array(2.0))
c = Variable(np.array(1.0))

y = add(mul(a, b), c) # add와 mul함수 사용
y.backward()

print(y)
print(a.grad)
print(b.grad)

Mul 클래스의 forward를 진행할 때는 x0, x1을 곱해주면 됩니다.

backward는 전파 받을 미분값 gy를 x1과 x0을 각각 곱해주어 리턴합니다.

 

mul 클래스를 파이썬 함수로 사용할 수 있도록

def mul(x0, x1): 에서 Mul() 인스턴스 만들어주고 x0, x1 값을 넣어줍니다.

20.2 연산자 오버로드

매번 y = add(mul(a, b),c)를 만들어주기는 번거로운 것 같아 

y = a * b + c 형태처럼 +와 * 연산자를 사용할 수 있도록 "연산자 오버로드"를 사용하겠습니다.

 

연산자 오버로드는 파이썬의 __add__, __mul__같은 특수 메서드를 사용하여 사용자 지정함수가 호출되도록 합니다.

 

1. 곱셈 연산자 * 를 오버로드

특수 메서드 __mul__(self, other)를 사용하여 Variable 클래스의 메서드로 구현합니다.

__mul__은 인수 self, other를 받는데

y = a * b일때 self는 a, other는 바로 옆에 있는 b를 받습니다.

만약, a에 __mul__메서드가 구현되어 있지 않으면 인스턴스 b의 * 연산자 특수 메서드가 호출되며 

b는 *오른쪽에 위치하기때문에 mul이 아닌 __rmul__이라는 특수 메서드가 호출됩니다.

 

코드 구현

__mul__ 메서드 일때

Variable:
	def __mul__(self, other):
    	return mul(self, other)

a = Variable(np.array(3.0))
b = Variable(np.array(2.0))
y = a * b # 내부적으로 a.__mul__(b)를 호출함
print(y)

__rmul__메서드 일때

# aVariable은 __mul__과 __rmul__ 둘다 없는 상태

Variable:
	def __rmul__(self, other):
    	return mul(self, other)

a = aVariable(np.array(3.0))
b = Variable(np.array(2.0))
y = a * b
print(y)

 

위의 작업을 더 간단하게 처리하고 싶을때

# 연산자 오버로딩
Variable.__add__ = add
Variable.__mul__ = mul

a = Variable(np.array(3.0))
b = Variable(np.array(2.0))
c = Variable(np.array(1.0))

y = a * b + c # add(mul(a, b), c)가 호출됨
y.backward()

print(y)
print(a.grad)
print(b.grad)

곱셈에 대해서 먼저 설명하자면

Variable.__mul__ = mul 은 연산자 오버로딩 기능을 활용하는데

Variable 클래스의 객체 두개를 * 연산자로 곱할 수 있게 됩니다.

이때 연산은 mul 함수를 통해 이루어진 다는 것을 의미합니다.

 

동작 원리

1. y = a * b 를 실행 하면 python은 a.__mul__(b)를 호출
2. Variable.__mul__은 mul 함수로 재정의 했음

3. 따라서 mul(a, b)가 실행되고 곱셈을 수행한 결과를 y라는 이름의 Variable 객체로 반환

* 덧셈 오버로딩도 동일한 동작을 가집니다.