Deep learning/모델 구현

7. 오차역전파법(1)

jwjwvison 2021. 4. 21. 23:07

이 포스팅은 밑바닥부터 시작하는 딥러닝을 공부하고 정리한것 입니다.


  이번 장에서는 가중치 매개변수의 기울기를 효율적으로 계산하는 오차역전파법(backpropagation)을 공부한다.

 

 

1. 계산 그래프

 다음 문제를 계산 그래프로 나타내보자.

 문제: 현빈 군은 슈퍼에서 사과를 2개, 귤을 3개 샀다. 사과는 1개에 100원, 귤은 1개 150원 이다. 소비세가 10%일 때 지불 금액을 구하라.

 계산을 왼쪽에서 오른쪽으로 진행하는 단계를 순전파라고 한다. 순전파는 계산 그래프의 출발점부터 종착점으로의 전파이다. 오른쪽에서 왼쪽 방향으로의 전파는 역전파라고 한다. 역전파는 이후에 미분을 계산할 때 중요한 역할을 한다.

 

 계산 그래프의 특징은 '국소적 계산' 을 전파함으로써 최종 결과를 얻는다는 점에 있다. 계산 그래프의 이점은 국소적 계산이다. 전체가 아무리 복잡해도 각 노드에서는 단순한 계산에 집중하여 문제를 단순화할 수 있다. 또 다른 이점으로, 계산 그래프는 중간 계산 결과를 모두 보관할 수 있다.

 

 앞에서 문제1은 사과 2개를 사서 소비세를 포함한 최종 금액을 구하는 것이였는데 여기서 사과 가격이 오르면 최종 금액에 어떤 영향을 끼치는지 알고 싶다고 하자. 이는 사과 가격에 대한 지불 금액의 미분을 구하는 문제에 해당한다. 기호로 나타낸다면 사과 값을 x, 지불 금액을 L이라 했을 때 dL/dx 를 구하는 것이다. 이 미분 값은 사과 값이 아주 조금 올랐을 때 지불 금액이 얼마나 증가하느냐를 표시한 것이다. 사과 가격에 대한 지불 금액의 미분은 계산 그래프에서 역전파를 하면 구할 수 있다.

 여기서는 소비세에 대한 지불 금액의 미분이나 사과 개수에 대한 지불 금액의 미분도 같은 순서로 구할 수 있다.

 

 

2. 연쇄법칙

 역전파는 국소적인 미분을 순방향과는 반대인 오른쪽에서 왼쪽으로 전달한다. 또한 이 국소적 미분을 전달하는 원리는 연쇄법칙(chain rule)에 따른 것이다.

 

 계산 그래프를 사용한 역전파의 예를 하나 살펴보자. y=f(x)라는 계산의 역전파를 그려보자.

 

 

3. 역전파

 

 덧셈 노드의 역전파

 z=x+y 이면 미분은 다음과 같이 해석적으로 계산할 수 있다.

 위 그림과 같이 역전파 때는 상류에너 전해진 미분(이 예에서는 dL/dz)에 1을 곱하여 하류로 흘린다. 즉, 덧셈 노드의 역전파는 1을 곱하기만 할 뿐이므로 입력된 값을 그대로 다음 노드로 보내게 된다.

 이 예에서는 상류에서 전해진 미분 값을 dL/dz 라 했는데 이는 최종적으로 L이라는 값을 출력하는 큰 계산 그래프를 가정하기 때문이다.

 

곱셈 노드의 역전파

 z=xy 라는 식을 생각해보자. 이 식의 미분은 다음과 같다.

 곱셈 노드 역전파는 상류의 값에 순전파 때의 입력 신호들을 '서로 바꾼 값'을 곱해서 하류로 보낸다. 서로 바꾼 값이란 위 그림처럼 순전파 때 x였다면 역전파에서는 y,순전파 때 y였다면 역전파에서는 x로 바뀐다는 의미이다.

 

4. 단순한 계층 구현하기

 곱셈 노드를 'MulLayer', 덧셈 노드를 'AddLayer' 라는 이름으로 구현한다.

class MulLayer:
    def __init__ (self):
        self.x=None
        self.y=None
        
    def forward(self,x,y):
        self.x=x
        self.y=y
        out=x*y
        
        return out
    
    def backward(self,dout):   #dout: 상류에서 넘어온 미분
        dx=dout * self.y       # x와 y를 바꾼다
        dy=dout * self.x
        
        return dx,dy

 

 MulLayer를 사용하여 위 그림의 순전파, 역전파를 다음과 같이 구현할 수 있다.

apple= 100
apple_num=2
tax= 1.1

# 계층들
mul_apple_layer=MulLayer()
mul_tax_layer=MulLayer()

# 순전파
apple_price=mul_apple_layer.forward(apple,apple_num)
price=mul_tax_layer.forward(apple_price,tax)

print(price)

dprice=1
dapple_price,dtax=mul_tax_layer.backward(dprice)
dapple,dapple_num=mul_apple_layer.backward(dapple_price)

print(dapple,dapple_num,dtax)

 

 덧셈 계층은 다음과 같이 구현할 수 있다.

class AddLayer:
    def __init__(self):
        pass
    def forward(self,x,y):
        out= x+y
        return out
    def backward(self,dout):
        dx=dout * 1
        dy=dout * 1
        return dx,dy

apple=100
apple_num=2
orange=150
orange_num=3
tax=1.1

# 계층들
mul_apple_layer=MulLayer()
mul_orange_layer=MulLayer()
add_apple_orange_layer=AddLayer()
mul_tax_layer=MulLayer()

# 순전파
apple_price=mul_apple_layer.forward(apple,apple_num)
orange_pirce=mul_orange_layer.forward(orange,orange_num)
all_price=add_apple_orange_layer.forward(apple_price,orange_pirce)
price=mul_tax_layer.forward(all_price,tax)

# 역전파
dprice=1
dall_price,dtax=mul_tax_layer.backward(dprice)
dapple_price,dorange_price=add_apple_orange_layer.backward(dall_price)
dorange,dorange_num=mul_orange_layer.backward(dorange_price)
dapple,dapple_num=mul_apple_layer.backward(dapple_price)

print(price)
print(dapple_num,dapple,dorange,dorange_num,dtax)

 이처럼 계산 그래프에서의 계층(여기에서는 곱셈과 덧셈)은 쉽게 구현할 수 있으며, 이를 사용해 복잡한 미분도 계산할 수 있다. 다음 절에서는 신경망에서 사용하는 계층을 구현하겠다.