본문 바로가기
Study/책『밑바닥부터 시작하는 딥러닝』

5-1. 오차역전파법

by 코드포휴먼 2020. 3. 31.

4장에서 신경망의 가중치 매개변수의 기울기(정확히는 가중치 매개변수에 대한 손실 함수의 기울기)는 수치 미분을 사용해 구했다.

수치 미분은 단순하고 구현하기 쉽지만 계산 시간이 오래 걸린다. 

가중치 매개변수의 기울기효율적으로 계산하는 오차역전파법(backpropagation)을 알아보겠다.

 

오차역전파법을 이해하기 위해서는 수식이나 계산 그래프를 이용한다.

수식을 사용하면 정확하고 간결하게 이해할 수 있지만 시각적으로 이해하기 위해 계산 그래프를 사용한다.

 

 

5.1. 계산 그래프

계산 그래프(computational graph)는 계산 과정을 그래프로 나타낸 것이다.

여기서 그래프는 그래프 자료구조로, 복수의 노드(node)에지(edge)로 표현된다(노드 사이의 직선을 에지라고 한다).

 

 

5.1.1. 계산 그래프로 풀다.

간단한 문제를 풀면서 오차역전파법에 도달할 예정이다.

 

  • 문제 1 : 제시카는 슈퍼에서 1개에 100원인 사과를 2개 샀다. 이때 지불 금액을 구하라. 단, 소비세가 10% 부과된다.

계산 그래프는 계산 과정을 노드와 화살표로 표현한다.

노드는 원(O)으로 표기하고 원 안에 연산 내용을 적는다.

계산 결과를 화살표 위에 적어 각 노드의 계산 결과가 왼쪽에서 오른쪽으로 전해지게 한다.

 

계산 그래프로 풀어본 문제 1의 답

계산 그래프에 따르면 최종 답은 220원이 된다. 

위에서는 'x2'와 'x1.1'을 하나의 연산으로 취급해 원 안에 표기했는데, 곱셉인 'x'만을 연산으로 생각할 수도 있다.

 

계산 그래프로 풀어본 문제 1의 답 : '사과의 개수'와 '소비세'를 변수로 취급해 원 밖에 표기

'사과의 개수'와 '소비세' 변수가 되어 원 밖에 표기하게 된다.

 

 

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

문제 2의 계산 그래프는 아래처럼 된다.

계산 그래프로 풀어본 문제 2의 답

덧셈 노드인 '+'가 새로 등장하여 사과와 귤의 금액을 합산했다.

 

<계산 그래프를 이용한 문제풀이> 는 다음 흐름으로 진행한다.

1. 계산 그래프를 구성한다.

2. 그래프에서 계산을 왼쪽에서 오른쪽으로 진행한다.

 

 

5.1.2. 국소적 계산

계산 그래프의 특징은 '국소적 계산'을 전파함으로써 최종 결과를 얻는다는 점에 있다. 

국소적이란 '자신과 직접 관계된 작은 범위'라는 뜻이다. 

국소적 계산은 결국 전체에 어떤 일이 벌어지든 상관없이 자신과 관계된 정보만으로 결과를 출력할 수 있다는 것이다.

전체 계산이 아무리 복잡하더라도 각 단계에서 하는 일은 해당 노드의 국소적 계산이다. 

 

가령 슈퍼마켓에서 사과 2개를 포함한 여러 식품을 구입하는 경우에 아래와 같은 계산 그래프로 나타낼 수 있다.

사과 2개를 포함해 여러 식품을 구입하는 예

각 노드에서의 계산은 국소적 계산이다. 

사과와 그 외의 물품 값을 더하는 계산(4000 + 200 -> 4200)은 4000이라는 숫자가 어떻게 계산되었느냐와는 상관없이, 단지 두 숫자를 더하면 된다는 뜻이다. 

 

 

5.1.3. 왜 계산 그래프로 푸는가?

계산 그래프의 이점 중 하나는 국소적 계산이다. 

또 다른 이점으로 계산 그래프는 중간 계산 결과를 모두 보관할 수 있다. 

예를 들어 사과 2개까지 계산했을 떄의 금액은 200원, 소비세를 더하기 전의 금액은 650원이다. 

계산 그래프를 사용하는 가장 큰 이유는 역전파를 통해 미분을 효율적으로 계산할 수 있는 점에 있다.  

 

계산 그래프의 역전파를 설명하기 위해 문제 1을 다시 꺼내보겠다.

문제 1에서 사과 가격이 오르면 최종 금액에 어떤 영향을 끼치는지 알고 싶다면 '사과 가격에 대한 지불 금액의 미분'을 구하는 문제에 해당한다.

기호로 나타내면 아래와 같다. 

 

이 값은 계산 그래프에서 역전파를 하면 구할 수 있다. 

역전파에 의한 미분 값의 전달

역전파는 순전파와는 반대 방향의 화살표(굵은 선)로 그린다.

역전파는 '국소적 미분'을 오른쪽에서 왼쪽으로 전달하고 그 미분 값은 화살표의 아래에 적는다.

오른쪽에서 왼쪽으로 '1->1.1->2.2' 순으로 미분 값을 전달한다. 이 결과로부터 '사과 가격에 대한 지불 금액의 미분' 값은 2.2라 할 수 있다. 

사과가 1원 오르면 최종 금액은 2.2원 오른다는 뜻이다. 

 

 

5.2. 연쇄법칙

역전파가 왼쪽으로 '국소적 미분'을 전달하는 원리는 연쇄법칙(chain rule)에 따른 것이다.

 

5.2.1. 계산 그래프의 역전파

y = f(x) 라는 계산의 역전파를 그리면 아래와 같다.

계산 그래프의 역전파 : 순방향과는 반대 방향으로 국소적 미분을 곱한다.

역전파의 계산 절차는 신호 E에 노드의 국소적 미분을 곱한 후 다음 노드로 전달하는 것이다.

국소적 미분은 순전파 때의 y = f(x) 계산의 미분을 구한다는 것이다.

 

예를 들어 국소적 미분은 아래와 같으며, 이 미분값을 상류에서 전달된 값(이 예에서는 E)에 제곱해 앞쪽 노드로 전달한다. 

역전파의 계산 순서에 따르면 목표로 하는 미분 값을 효율적으로 구현할 수 있다. 

그 원리는 연쇄법칙에 있다. 

 

 

5.2.2. 연쇄법칙이란?

연쇄법칙을 설명하기 위해선 합성 함수를 이해해야 한다.

합성 함수란 아래처럼 여러 함수로 구성된 함수다. 

합성 함수 예시

 

연쇄법칙은 합성 함수의 미분에 대한 성질이다.

합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다.

 

'x에 대한 z의 미분'은 't에 대한 z의 미분'과 'x에 대한 t의 미분'의 곱으로 나타낼 수 있다. 

식 5.2) x에 대한 z의 미분

 

아래처럼 상쇄도 가능하다.

 

연쇄법칙을 써서 'x에 대한 z의 미분'을 구해본다. 가장 먼저 합성 함수에 대한 국소적 미분(편미분)을 구한다.

미분 공식에서 해석적으로 구한 결과다.

식 5.3

 

최종적으로 구하고 싶은 'x에 대한 z의 미분'은 위에서 구한 두 미분을 곱해 계산한다.

식 5.4

 

 

5.2.3. 연쇄법칙과 계산 그래프

연쇄법칙 계산을 계산 그래프로 나타내보면, 2제곱 계산을 '**2' 노드로 나타내면 아래처럼 그릴 수 있다.

'x에 대한 z의 미분'의 계산 그래프 : 순전파와는 반대 방향으로 국소적 미분을 곱하여 전달한다.

역전파의 계산 절차에서는 노드로 들어온 입력 신호에 그 노드의 국소적 미분(편미분)을 곱한 후 다음 노드로 전달한다.

 '**2' 노드의 역전파에 대한 해설은 아래와 같다. 

 

그런데 역전파가 하는 일은 연쇄법칙의 원리와 같다.

맨 왼쪽 역전파는 연쇄법칙에 따르면 'x에 대한 z의 미분'이 된다.

그림 5-7에 식 5.3의 결과를 대입하면 그림 5-8이 되며, 'x에 대한 z의 미분'은 2(x+y)임을 구할 수 있다.

그림 5-8

 

 

5.3. 역전파

이번 절에서는 '+'와 'x' 등의 연산을 예로 들어 역전파의 구조를 보겠다.

 

5.3.1. 덧셈 노드의 역전파

덧셈 노드의 역전파에서는 z = x+y 라는 식을 대상으로 그 역전파를 살펴보겠다.

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

 

계산 그래프로는 아래처럼 그릴 수 있다.

덧셈 노드의 역전파 : 왼쪽이 순전파, 오른쪽이 역전파다. 덧셈 노드의 역전파는 입력 값을 그대로 흘려보낸다.

상류에서 전해진 미분에 1을 곱하여 하류로 흘린다(최종적으로 L이라는 값을 출력하는 큰 계산 그래프를 가정한다). 

즉, 덧셈 노드의 역전파는 1을 곱하기만 할 뿐이므로 입력된 값을 그대로 다음 노드로 보낸다. 

 

최종 출력으로 가는 계산의 중간에 덧셈 노드가 존재한다. 역전파에서는 국소적 미분이 가장 오른쪽의 출력에서 시작하여 노드를 타고 역방향(왼쪽)으로 전파된다.

 

구체적인 예로 '10+5=15'라는 계산이 있고, 상류에서 1.3이라는 값이 흘러오면 계산 그래프를 아래처럼 그린다.

덧셈 노드 역전파의 구체적인 예

덧셈 노드 역전파는 입력 신호를 다음 노드로 출력할 뿐이므로 1.3을 그대로 다음 노드로 전달한다.

 

 

5.3.2. 곱셉 노드의 역전파

z = xy 라는 식을 생각해보면 이 식의 미분은 아래와 같다.

식 5.6

 

계산 그래프는 다음과 같이 그릴 수 있다.

곱셈 노드의 역전파 : 왼쪽이 순전파, 오른쪽이 역전파다.

곱셈 노드 역전파는 상류의 값에 순전파 때의 입력 신호들을 '서로 바꾼 값'을 곱해서 하류로 보낸다. 

순전파 때 x였다면 역전파에서는 y, 순전파 때 y였다면 역전파에서는 x로 바꾼다.

 

구체적인 예로 '10x5=50'이라는 계산이 있고, 상류에서 1.3 값이 흘러오면 계산 그래프는 아래처럼 된다.

곱셈 노드 역전파의 구체적인 예

곱셈의 역전파에서는 입력 신호를 바꾼 값을 곱하여 하나는 1.3x5=6.5, 다른 하나는 1.3x10=13이 된다.

덧셈의 역전파에서는 상류의 값을 그대로 흘려보내서 순방향 입력 신호의 값은 필요하지 않았지만, 곱셈의 역전파는 순방향 입력 신호의 값이 필요하다. 그래서 변수에 저장해둔다.

 

 

5.3.3. 사과 쇼핑의 예

사과 쇼핑의 예를 다시 살펴보겠다.

이 문제에서는 사과의 가격, 사과의 개수, 소비세라는 세 변수 각각이 최종 금액에 어떻게 영향을 주느냐를 풀고자 한다.

즉, 구하는 것은 다음과 같다. 

  • '사과 가격에 대한 지불 금액의 미분'
  • '사과 개수에 대한 지불 금액의 미분'
  • '소비세에 대한 지불 금액의 미분'

이를 계산 그래프의 역전파를 사용해서 풀면 아래처럼 된다.

사과 쇼핑의 역전파 예

곱셈 노드의 역전파에서는 입력 신호를 서로 바꿔서 하류로 흘린다.

사과 가격의 미분은 2.2, 사과 개수의 미분은 110, 소비세의 미분은 200이다. 

이는 소비세와 사과 가격이 같은 양만큼 오르면 최종 금액에는 소비세가 200의 크기로, 사과 가격이 2.2 크기로 영향을 준다고 해석할 수 있다.

단, 이 예에서 소비세와 사과 가격은 단위가 다르니 주의한다(소비세 1은 100%, 사과 가격 1은 1원).

 

'사과와 귤 쇼핑'의 역전파를 풀어보겠다.

빈 상자에는 각 변수의 미분이 들어간다. 답은 다음 절에서 채운다.

사과와 귤 쇼핑의 역전파 예 : 빈 상자 안에 적절한 숫자를 넣어 역전파를 완성 하시오.

 

 

5.4. 단순한 계층 구현하기 

이번 절에서는 '사과 쇼핑' 예를 파이썬으로 구현한다.  

계산 그래프의 곱셉 노드를 'MulLayer', 덧셈 노드를 'AddLayer'라는 이름으로 구현한다.

더보기

다음 절(5.5절)에서는 신경망을 구성하는 '계층' 각각을 하나의 클래스로 구현한다.

계층이란 신경망의 기능 단위를 뜻한다. 예를 들어 시그모이드 함수를 위한 Sigmoid, 행렬 곱을 위한 Affine 등의 기능을 계층 단위로 구현한다. 그래서 이번 절에서도 곱셈 노드와 덧셈 노드를 계층 단위로 구현한다.

 

 

5.4.1. 곱셉 계층

모든 계층은 forward()와 backward()라는 공통의 메서드(인터페이스)를 갖도록 구현할 것이다.

 

먼저 MulLayer 클래스로 곱셈 계층을 구현해본다. 

#곱셈 계층
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):
        dx = dout * self.y   #x와 y를 바꾼다
        dy = dout * self.x
        
        return dx, dy

__init__()에서는 인스턴스 변수인 x와 y를 초기화한다. 이 두 변수는 순전파 시의 입력 값을 유지하기 위해 사용한다.

forward()에서는 x와 y를 인수로 받고 두 값을 곱해서 반환한다.

backward()에서는 상류에서 넘어온 미분(dout)에 순전파 때의 값을 서로 바꿔 곱한 후 하류로 흘린다.

 

MulLayer를 사용해서 '사과 쇼핑'을 구현해본다.

앞 절(5.3.절)에서는 계산 그래프의 순전파와 역전파를 써서 아래처럼 계산할 수 있었다.

사과 2개 구입

 

위 그림의 순전파는 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)   #220

 

각 변수에 대한 미분은 backward()에서 구할 수 있다.

#역전파
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)

print(dapple, dapple_num, dtax)   #2.2  110  200

backward() 호출 순서는 forward() 때와는 반대다. 

backward()가 받는 인수는 '순전파의 출력에 대한 미분'임에 주의한다.

가령 mul_apple_layer라는 곱셈 게층은 순전파 떄는 apple_price를 출력하지만, 역전파 때는 apple_price의 미분 값인 dapple_price를 인수로 받는다.

 

 

5.4.2. 덧셈 계층

덧셈 노드인 덧셈 계층을 구현한다.

#덧셈 계층
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

덧셈 계층에는 초기화가 필요없으니 __init__()에서는 아무 일도 하지 않는 pass 명령을 수행한다.

forward()에서는 입력받은 두 인수 x, y를 더해서 반환한다.

backward()에서는 상류에서 내려온 미분(dout)을 그대로 하류로 흘릴 뿐이다.

 

덧셈 계층과 곱셈 계층을 사용하여 사과 2개와 귤 3개를 사는 상황을 구현해보겠다.

사과 2개와 귤 3개 구입

 

위 계산 그래프를 파이썬으로 구현해본다.

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)   # (1)
orange_price = mul_orange_layer.forward(orange, orange_num)   # (2)
all_price = add_apple_orange_layer.forward(apple_price, orange_price)   # (3)
price = mul_tax_layer.forward(all_price, tax)   # (4)

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


print(price)   #715
print(dapple_num, dapple, dorange, dorange_num, dtax)   #110  2.2  3.3  165  650

필요한 계층을 만들어 순전파 메서드를 적절한 순서로 호출한다.

그 후 순전파와 반대 순서로 역전파 메서드를 호출한다. 

 

 

계산 그래프에서의 계층(여기에선 곱셈과 덧셈)은 쉽게 구현할 수 있으며, 이를 사용해 복잡한 미분도 계산할 수 있다.

다음 절에서는 신경망에서 사용하는 계층을 구현하겠다.

5.5. 활성화 함수 계층 구현하기 는 다음 포스트에서 이어진다.

 


<참고 문헌>

사이토 고키(2019), 『밑바닥부터 시작하는 딥러닝』, 한빛미디어, pp.147-164.

 

'Study > 책『밑바닥부터 시작하는 딥러닝』' 카테고리의 다른 글

6-1. 학습 관련 기술들  (0) 2020.05.11
5-2. 오차역전파법  (0) 2020.04.09
4-2. 신경망 학습  (0) 2020.03.25
4-1. 신경망 학습  (0) 2020.03.16
3-2. 신경망  (0) 2020.03.09

댓글