밑바닥부터 시작하는 딥러닝3 - STEP 20
Mul 클래스 구현
이번 단계의 목표는 Variable
인스턴스 a와 b가 있을 때 mul(a,b)
또는 add(a,b)
가 아닌 a+b
또는 a*b
로 작성 가능하게 코드를 수정하는 것이다. 이를 하기 전 우선 Mul 클래스부터 구현하였다.
$y=x_0*x_1$에서 각각을 편미분한 결과는 ${\partial y\over \partial x_0} = x_1$ , ${\partial y\over \partial x_1} = x_0$ 이다.
위의 역전파 그래프를 보면 역전파는 최종 출력인 L의 각 변수에 대한 미분을 전파합니다. 이 때 변수 $x_0$ 과 $x_1$ 에 대한 미분은 각각 ${\partial L\over \partial x_0} = x_1{\partial L\over \partial y}$ 과 ${\partial L\over \partial x_1} = x_0{\partial L\over \partial y}$ 이다. 곱하기 계산을 동작하기 위해 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)
y.backward()
print(y)
print(a.grad)
print(b.grad)
실행 결과
variable(7.0)
2.0
3.0
이제 mul
함수까지 구현은 끝낫지만 add(mul(a, b), c)
처럼 매번 코딩하기에는 다소 번거로움이 존재한다. 이를 y = a + b * c
이런식으로 구현할 수 있으면 더욱 직관적으로 보일 것이고, 이를 하기 위해 연산자 오버로드를 이용할 예정이다. 일반적으로 직접 정의한 클래스에 의해 생성된 객체는 기본적으로 연산이 불가하지만 연산자 오버로드를 통해 가능해진다.
연산자 오버로드
곱셈 연산자를 오버로드 한 코드는 다음과 같다.
class Variable:
....
def __mul__(self, other):
return mul(self, other)
a = Variable(np.array(3.0))
b = Variable(np.array(2.0))
y = a * b
print(y)
실행 결과
variable(6.0)
이제 더 이상 mul(a, b)
와 같은 코드를 작성할 필요 없이 바로 *
연산자를 이용해 원하는 기능을 구현할 수 있게 된다.
그런데 이와 같은 작업을 더욱 간단하게 구현하는 방법이 존재한다.
Variable.__mul__ = mul
Variable.__add__ = add
Variable
클래스를 구현한 후 다음과 같은 코드들을 작성하면 끝이다. 파이썬에서는 함수도 객체이므로 이와 같이 함수 자체를 할당할 수 있고, Variable
인스턴스에 __mul__
메서드를 호출할 때 자동으로 mul
함수가 불리게 된다.
a = Variable(np.array(3.0))
b = Variable(np.array(2.0))
c = Variable(np.array(1.0))
# y = add(mul(a, b), c) -> 기존 코드
y = a * b + c
y.backward()
print(y)
print(a.grad)
print(b.grad)
실행 결과
variable(7.0)
2.0
3.0
출처:- 사이토 고키, 『밑바닥부터 시작하는 딥러닝3』, 개앞맵시, 한빛미디어(2020)