1. 계산 그래프(Computatuinal graph)
- 계산 그래프는 계산 과정을 그래프로 나타낸 것
- 그래프 자료구조로 복수의 노드(node)와 간선(edge)로 표현됨
1.1. 계산 그래프로 풀다.
- 문제 1 : 슈퍼에서 1개에 100원인 사과 2개를 샀습니다. 이때 지불 금액을 구하세요. 단, 소비세가 10% 부과됩니다.
- 그림과 같이 처음에 사과의 100원인 'x 2' 노드로 흐르고, 200원이 되어 다음 노드로 전달된다.
- 이제 200원이 'x 1.1' 노드를 거쳐 220원이 된다.
- 따라서 이 계산 그래프에 따르면 최종 답은 220원
계산 그래프를 이용한 문제풀이의 흐름
- 계산 그래프를 구성한다.
- 그래프에서 계산을 왼쪽 -> 오른쪽으로 진행한다.
- 계산을 왼쪽 -> 오른쪽으로 진행하는 단계를 순전파(forward propagation 라고 한다.
- 반대로 오른쪽 -> 왼쪽으로 진행하는 단계를 역전파(backward propagation 라고 한다.
- 역전파는 이후에 미분을 계산할 때 중요한 역할을 한다.
1.2. 국소적 계산
계산 그래프의 특징은 _국소적 계산_을 전파함으로써 최종 결과를 얻는다는 점이다.
국소적 계산은 결국 전체에서 어떤 일이 벌어지든 상관없이 자신과 관계된 정보만으로 결과를 출력할 수 있다는 것이다.
- 가령 사과와 그 외의 물품 값을 더하는 계산 * 4000(물품 값) + 200(사과 값) -> 4200 * 은 4000이라는 숫자가 어떻게 계산되었느냐와는 상관없이, 단지 두 숫자를 더하면 된다는 뜻이다.
- 이런 특징으로 계산 그래프에서 각 노드는 자신과 관련한 계산 외에는 아무것도 신경 쓸 게 없다.
역전파
- 그림과 같이 역전파는 순전파와는 반대 방향의 화살표(굵은 선)로 그린다.
- 이 전파는 '국소적 미분'을 전달하고 그 미분 값은 화살표의 아래에 적는다.
- 이 예에서 역전파는 왼쪽에서 * 1 -> 1.1 -> 2.2 * 순으로 미분 값을 전달한다.
- 사과 가격에 대한 지불 금액의 미분 = 2.2
- 이처럼 계산 그래프의 이점은 순전파와 역전파를 활용해 각 변수의 미분을 효율적으로 구할 수 있다는 것이다.
이처럼 계산 그래프는 국소적 계산에 집중한다.
- 전체 계산이 아무리 복잡하더라도 각 단계에서 하는 일은 해당 노드의 _국소적 계산_이다.
- 국소적 계산은 단순하지만, 그 결과를 전달함으로써 전체를 구성하는 복잡한 계산을 해낼 수 있다.
2. 연쇄법칙
- 역전파에서 국소적인 미분*을 전달하는 원리는 *연쇄법칙(chain rule) 에 따른 것이다.
계산 그래프의 역전파
- 다음 그림은 $y = f(x)$ 라는 계산의 역전파이다.
- 그림과 같이 역전파의 계산 절차는 신호 E에 노드의 국소적 미분($ \frac{\partial y}{\partial x} $) 을 곱한 후 다음 노드로 전달하는 것이다.
- 여기서의 국소적 미분은 순전파 때의 $y = f(x)$ 계산의 미분을 구한다는 것이며, 이는 x에 대한 y의 미분($ \frac{\partial y}{\partial x} $)을 구한다는 뜻이다.
- 가령 $y = f(x) = x^2$ 라면 $ \frac{\partial y}{\partial x} = 2x $가 된다.
2.1. 합성 함수
연쇄법칙을 설명하려면 먼저 합성 함수에 대해 알아야 한다.
- 합성 함수란 여러 함수로 구성된 함수이다.
- 예를 들어 $ z = (x + y)^2 $ 라는 식은 다음과 같은 두 개의 식으로 구성된다.
$$
z = t^2
$$
$$
t = x + y
$$ - 연쇄법칙은 합성 함수의 미분에 대한 성질이며, 다음과 같이 정의된다.
- 합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다.
- 예를 들면, $ \frac{\partial z}{\partial x} $ (x에 대한 z의 미분)은 $ \frac{\partial z}{\partial t} $ (t에 대한 z의 미분)과 $ \frac{\partial t}{\partial x} $ (x에 대한 t의 미분)의 곱으로 나타낼 수 있다.
$$
\frac{\partial z}{\partial x} = \frac{\partial z}{\partial t}\frac{\partial t}{\partial x}
$$
그럼 연쇄법칙을 써서 위 식의 미분 $\frac{\partial z}{\partial x}$을 구해보자.
- 가장 먼저 $z = t^2$ , $t = x + y$ 의 국소적 미분(편미분)을 구한다.
$$
\frac{\partial z}{\partial t} = 2t
$$
$$
\frac{\partial t}{\partial x} = 1
$$
- $\frac{\partial z}{\partial t}$ 는 $2t$이고, $\frac{\partial t}{\partial x}$는 $1$ 이다.
- 최종적으로 구하고 싶은 $\frac{\partial z}{\partial x}$는 두 미분을 곱해 계산한다.
$$
\frac{\partial z}{\partial x} = \frac{\partial z}{\partial t} \cdot \frac{\partial t}{\partial x} = 2t \cdot 1 = 2(x + y)
$$
위의 연쇄법칙을 계산 그래프로 나타내보자.
- 순전파와는 반대방향으로 국소적 미분을 곱하여 전달한다.
- 역전파 계산 그래프의 예
덧셈 노드의 순전파, 역전파
- 덧셈 노드의 역전파는 입력 값을 그대로 흘려보낸다.
곱셈 노드의 순전파, 역전파
- 곱셈 노드의 역전파는 순전파의 입력 신호가 필요하다. 따라서 곱셈 노드 구현시에는 순전파의 입력 신호를 변수에 저장해둔다.
- 곱셈 노드의 역전파는 상류의 값에 순전파 때의 입력 신호들을 '서로 바꾼 값'을 곱해서 하류로 보낸다.
- '서로 바꾼 값'이란 순전파 때 x 였다면 역전파에서는 y, 순전파 때 y 였다면 역전파에서는 x로 바꾼다는 뜻이다.
- 예를 들어, '10 x 5 = 50'이라는 계산이 있고, 역전파 때 상류에서 1.3 값이 흘러온다고 하면 다음 그림과 같이 된다.
- 입력 신호를 바꾼 값을 곱하여 하나는 1.3 x 5 = 6.5, 다른 하나는 1.3 x 10 = 13 이 된다.
3. 계층 구현하기
- 계산 그래프의 곱셈 노드를 'MulLayer', 덧셈 노드를 'AddLayer'로 구현해보자
- 모든 계층은 forward() 와 backword()라는 공통의 메서드를 갖는다.
- forward()는 순전파, backword()은 역전파를 처리
3.1. 곱셈 계층
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
apple = 100
apple_num = 2
tax = 1.1
mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()
# forward
apple_price = mul_apple_layer.forward(apple, apple_num)
price = mul_tax_layer.forward(apple_price, tax)
# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print("price:", int(price))
print("dApple:", dapple)
print("dApple_num:", int(dapple_num))
print("dTax:", dtax)
"""
출력
price: 220
dApple: 2.2
dApple_num: 110
dTax: 200
"""
3.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()에서는 아무 일도 하지 않는다.
apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1
# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()
# forward
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)
# backward
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:", int(price))
print("dApple:", dapple)
print("dApple_num:", int(dapple_num))
print("dOrange:", dorange)
print("dOrange_num:", int(dorange_num))
print("dTax:", dtax)
"""
출력
price: 715
dApple: 2.2
dApple_num: 110
dOrange: 3.3000000000000003
dOrange_num: 165
dTax: 650
"""
'TIL > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
계산그래프 신경망에 적용하기(ReLU, Sigmoid) (0) | 2024.06.28 |
---|---|
학습 알고리즘 (1) | 2024.06.18 |
경사법 (1) | 2024.06.16 |
손실 함수 (1) | 2024.06.15 |
소프트맥스 함수 (0) | 2024.06.15 |