공부를 하는 입장이기 때문에, 내용에 오류가 있을 수 있습니다. 오류가 있다면 적극적으로 알려주시면 감사합니다!
1. 오차역전법(backpropagation)
오차 역전파란 신경망의 출력과 실제 값 간의 오차를 기반으로, 그 오차를 입력층까지 거꾸로 전달하며 가중치를 조정하는 방식을 말한다. 일반적으로는 순전파를 통해 입력 데이터를 네트워크에 전달하여 추론 값을 얻고, 역전파를 통하여 각 층의 가중치에 대한 기울기를 계산하여 경사하강법을 사용한다.
100원짜리 사과를 3개 샀을 때, 10%의 소비세가 붙는다면 최종 가격은 얼마일까? 이 문제를 풀기 위해, 먼저 사과의 개당 가격(100원)에 사과의 개수(3개)를 곱하고, 이후 소비세(10%)를 적용해 계산할 수 있다.
이 과정을 신경망으로 표현하면, 순전파(Forward Propagation)는 입력(사과의 가격)을 이용해 중간 계산(가격 합산)과 가중치(소비세율)를 거쳐 최종 출력(최종 가격)을 계산하는 흐름이다. 순전파는 그림에서 검정 화살표로 표현되며, 입력 신호가 가중치(사과 개수와 소비세율)를 거쳐 출력 신호로 변환되는 과정이다.
반면, 역전파(Backpropagation)는 계산된 최종 가격이 실제 기대 값과 차이가 있을 때, 이 오차를 줄이기 위해 가중치(여기서는 사과 개수와 소비세율)를 조정하는 과정이다. 역전파는 오차를 출력에서 입력 방향으로 거슬러 올라가며 각 단계에서의 기울기(Gradient)를 계산한다. 역전파는 이 그림에서 빨간 화살표로 표현된다. 이 기울기를 통해 각 가중치가 오차에 얼마나 기여했는지를 평가하고, 가중치를 조정하여 더 정확한 결과를 얻도록 만든다.
이 과정은 다음과 같은 단계로 이루어진다:
- 출력 오차 계산:
최종 계산된 가격과 실제 가격의 차이를 계산한다. - 오차 역전파:
오차를 소비세율과 사과 개수 방향으로 거꾸로 전달하며, 각 가중치가 손실에 미친 영향을 계산한다. - 가중치 조정:
기울기를 기반으로 사과 개수나 소비세율과 같은 가중치를 조정하여, 다음 순전파에서 더 정확한 최종 가격이 나오도록 학습을 진행한다.
2. 역전파
조금 더 간단한 예시를 통해 일반화를 해보자. 입력 \(x\)가 \(f\)함수 노드를 지나 출력 \(y\)가 된다고 하자. 순전파에서는 입력 \(x\) 를 함수 \(f(x)\) 에 넣어서 \(y\)를 얻었다면, 역전파에서는 이전 노드에서 넘어온 신호와는 상관없이, 출력 \(y\) 가 입력 \(x\) 에 따라 얼마나 변하는지를 계산해 전달한다. 이전 노드에서 넘어온 신호를 \(E\) 라고 하면, 전달되는 값은 \(E\frac{\partial y}{\partial x}\)가 된다. 예를 들어, \(f(x) = x^2\)라면 \(\frac{\partial y}{\partial x} = 2x\)이므로, 이 경우 전달되는 값은 \(E*2x\)가 된다.
이처럼 이전 신호와 상관없이 해당 노드에서의 계산만 수행하는 것을 국소(local) 계산이라고 한다. 수치미분을 사용해 매개변수의 기울기를 구하는 방법과 달리, 오차 역전파는 국소 계산의 특징 덕분에, 아무리 복잡한 신경망이라도 작은 단위의 연산을 반복하기 때문에 계산이 상대적으로 단순하고 매우 빠르게 이루어진다.
이제 해야 할 일은 각 계층이 역전파를 통해 오차를 어떤 방식으로 전달하는지 이해하고 이를 구현하는 것이다.
2.1 덧셈 계층
입력신호 x와 y가 덧셈 계층을 거쳐 출력 신호가 z가 되었다고 해보자. 그러면 순전파와 역전파는 다음과 같다.
그림을 보면 역전파에서는 각신호에 대한 손실함수의 변화량을 연쇄법칙을 통하여 구할 수 있다. 또한 각 노드는 상류층(이전노드)에서 흘러든 신호의 상관없이 입력신호에 대한 출력 신호만 계산하면 된다는 것 또한 알 수 있다.
$$\frac{\partial z}{\partial x}, \frac{\partial z}{\partial y} 는\,\, z = x \,+\,y \, 로\,부터\,각각\,\,1이라는\,것을\,알\,수\,있다.$$
역전파에서는 연쇄법칙을 통해 상류 노드(이전 노드)에서 전달된 신호를 그대로 입력 신호 방향으로 전달할 수 있다.
덧셈 계층의 중요한 특징은, 순전파에서는 입력 신호들을 더하고, 역전파에서는 상류 노드에서 전달된 미분 값을 그대로 하류로 전달한다는 점이다.
코드를 보면 국소 계산의 간단함을 잘 확인할 수 있다.
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
2.2 곱셈 계층
곱셈 계층의 특징은 순전파에서 입력 신호들을 곱하고, 역전파에서는 입력신호를 서로 반대로 내보낸다.
입력신호의 값을 저장해 놓았다가, 역전파에서 서로 신호를 바꿔서 내보내준다.
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
2.3 ReLU 계층
ReLU함수는 순전파에서는 입력신호가 0을 넘으면 입력신호를 그대로 넘겨주었던 것처럼 역전파에서도 순전파일때의 신호가 0보다 컸으면 신호(미분 값)를 그대로 전달하는 역할을 하고 0 이하면 신호를 보내지 않는다.
ReLU함수 또한 순전파의 값에 따라 역전파의 값이 바뀌므로 미리 저장해 놓았다가, 역전파에서 0 이하인 것만 신호를 보내지 않는다. 아래 코드는 batch 데이터를 다룰 수 있도록 하였다.
class Relu:
def __init__(self):
self.mask = None
def forward(self, x):
self.mask = (x <= 0)
out = x.copy()
out[self.mask] = 0
return out
def backward(self, dout):
dout[self.mask] = 0
dx = dout
return dx
2.4 Sigmoid 계층
시그모이드 같은 경우는 계산 노드가 여러 개 포함된 계층이라 생각하면 편하다. 국소 계산을 하나하나 진행하다 보면 시그모이드 계층의 역전파에서의 출력을 구 할 수 있다.
이를 조금 더 간단히 해보면 다음과 같다.
계산이 복잡했지만 결론적으로는 역전파에서 출력 신호는 y(1-y)이다. (y는 순전파에서의 출력값)
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = sigmoid(x)
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
2.5 Affine 계층
신경망의 순전파 때 수행하는 행렬의 내적은 기하학에서는 어파인 변환(affine transformation)이라고 한다. 어파인 계층에서는 순전파에서는 입력 데이터가 들어오면 입력 데이터와 가중치를 곱하고 편향을 더하는 계산이 진행된다.
역전파에서의 출력 신호는 다음과 같다.
$$ \frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y}W^T $$ $$\frac{\partial L}{\partial W} = X^T\frac{\partial L}{\partial Y} $$
책에서는 이 식의 유도과정을 설명하지 않아 나의 생각대로 유도를 해보겠다.
$$ [y_{1}, y_{2}, y_{3}] = [x_{1},x_{2}]\times\begin{bmatrix}w_{11} & w_{12}& w_{13} \\ w_{21} & w_{22}& w_{23} \end{bmatrix} + [b_{1},b_{2},b_{3}] $$
예시를 통하여 유도를 해보겠다. 우리가 계산해야 하는 식은 \(\frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y}\frac{\partial Y}{\partial X}\)과 \(\frac{\partial L}{\partial W} = \frac{\partial L}{\partial Y}\frac{\partial Y}{\partial W}\)이다.
\(\frac{\partial L}{\partial X}\)란 \(X\)에 대한 손실 함수의 변화량이다. 그렇다면 \(X\)에 대한 영향력을 표현하기 위하여 \(X\)의 크기가 1 x 2 이라면 \(\frac{\partial L}{\partial X}\)또한 1 x 2이다. $$ X = [x_{1}, x_{2}] $$ $$ \frac{\partial L}{\partial X} = \begin{bmatrix}\frac{\partial L}{\partial X_1}, \frac{\partial L}{\partial X_2}\end{bmatrix} $$
추론 식을 풀어서 표현해 보면 다음과 같다. $$ y_{i} = (\sum_k^2 x_{k}w_{ki})+b_{i} = x_{1}w_{1i}\,+\,x_{2}w_{2i}\,+b_{i} $$
이를 \(X\)에 대하여 미분하면 가중치가 나오게 된다. $$ \frac{\partial y_{i}}{\partial x_{1}}=w_{1i}\,,\,\frac{\partial y_{i}}{\partial x_{2}}=w_{2i} $$
우리가 구해야 하는 \(\frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y}\frac{\partial Y}{\partial X}\)을 행렬 곱에서 시그마로 표현하면 \(y\)가 1차원이기 때문에 다음과 같다.$$ \frac{\partial L}{\partial x_{i}}=\sum_{j=1}^3 \frac{\partial L}{\partial y_{j}}\frac{\partial y_{j}}{\partial x_{i}} $$
오른쪽 식을 직접 풀어보면,
i ) j = 1 $$\frac{\partial L}{\partial y_{1}}\frac{\partial y_{1}}{\partial x_{i}} = \begin{bmatrix}\frac{\partial L}{\partial y_{1}}w_{11}\,,\,\frac{\partial L}{\partial y_{1}}w_{21}\end{bmatrix}$$
ii ) j = 2 $$ \frac{\partial L}{\partial y_{2}}\frac{\partial y_{2}}{\partial x_{i}} = \begin{bmatrix}\frac{\partial L}{\partial y_{2}}w_{12}\,,\,\frac{\partial L}{\partial y_{2}}w_{22}\end{bmatrix} $$
iii ) j = 3 $$ \frac{\partial L}{\partial y_{3}}\frac{\partial y_{3}}{\partial x_{i}} = \begin{bmatrix}\frac{\partial L}{\partial y_{3}}w_{13}\,,\,\frac{\partial L}{\partial y_{3}}w_{23}\end{bmatrix} $$
i + ii + iii ) $$ \frac{\partial L}{\partial X} = \begin{bmatrix}\frac{\partial L}{\partial y_{1}}w_{11}\,+\,\frac{\partial L}{\partial y_{2}}w_{12}\,+\,\frac{\partial L}{\partial y_{3}}w_{13}\,,\,\frac{\partial L}{\partial y_{1}}w_{21}\,+\,\frac{\partial L}{\partial y_{2}}w_{22}\,+\,\frac{\partial L}{\partial y_{3}}w_{23}\end{bmatrix} $$
이를 다시 행렬곱으로 표현하면 간단한 식으로 다시 나온다.
$$ \frac{\partial L}{\partial X} = \begin{bmatrix}\frac{\partial L}{\partial y_{1}}&\frac{\partial L}{\partial y_{2}}&\frac{\partial L}{\partial y_{3}}\end{bmatrix} \times \begin{bmatrix}w_{11}&w_{21} \\ w_{12}&w_{22}\\ w_{13}&w_{23} \end{bmatrix} $$
$$ \frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y}W^T $$
Matrix calculus - Wikipedia
From Wikipedia, the free encyclopedia Specialized notation for multivariable calculus In mathematics, matrix calculus is a specialized notation for doing multivariable calculus, especially over spaces of matrices. It collects the various partial derivative
en.wikipedia.org
사실 \(\frac{\partial Y}{\partial X}\)는 야코비 행렬로 위에 사이트에서 vector-by-vector 부분을 보고 계산하면 \(\frac{\partial Y}{\partial X}\)는 \(W^T\)가 나온다.
위에 예시를 통해 \(\frac{\partial L}{\partial W}\)를 구해보자. \(W\)는 2 x 3 행렬이기 때문에, \(\frac{\partial L}{\partial W}\)도 2 x 3 행렬이다. $$ W = \begin{bmatrix}w_{11} & w_{12} & w_{13} \\w_{21} & w_{22} & w_{23} \end{bmatrix} $$
$$ \frac{\partial L}{\partial W} = \begin{bmatrix}\frac{\partial L}{\partial w_{11}} & \frac{\partial L}{\partial w_{12}} & \frac{\partial L}{\partial w_{13}} \\ \frac{\partial L}{\partial w_{21}} & \frac{\partial L}{\partial w_{22}} & \frac{\partial L}{\partial w_{23}} \end{bmatrix} $$
추론식을 \(w\)에 대하여 미분을 할 경우 \(x\)가 나오게 된다. $$ y_{i} = (\sum_k^2 x_{k}w_{ki})+b_{i} = x_{1}w_{1i}\,+\,x_{2}w_{2i}\,+b_{i} $$ $$ \frac{\partial y_{i}}{\partial w_{1i}} = x_{1}\,\,,\frac{\partial y_{i}}{\partial w_{2i}} = x_{2} $$
\(\frac{\partial L}{\partial W} = \frac{\partial L}{\partial Y}\frac{\partial Y}{\partial W}\)를 행렬 곱에서 시그마로 표현하면 다음과 같다. $$ \frac{\partial L}{\partial w_{ab}} = \sum_{i=1}^3\frac{\partial L}{\partial y_{i}}\frac{\partial y_{i}}{\partial w_{ab}} $$
i ) i = 1 $$ \frac{\partial L}{\partial y_{1}}\frac{\partial y_{1}}{\partial w_{ab}} = \begin{bmatrix}\frac{\partial L}{\partial y_{1}}x_{1} & 0 & 0 \\ \frac{\partial L}{\partial y_{1}}x_{2} & 0 & 0 \end{bmatrix} $$
ii ) i = 2 $$ \frac{\partial L}{\partial y_{2}}\frac{\partial y_{2}}{\partial w_{ab}} = \begin{bmatrix}0 & \frac{\partial L}{\partial y_{2}}x_{1} & 0 \\ 0 & \frac{\partial L}{\partial y_{2}}x_{2} & 0 \end{bmatrix} $$
iii ) i = 3 $$ \frac{\partial L}{\partial y_{3}}\frac{\partial y_{3}}{\partial w_{ab}} = \begin{bmatrix}0 & 0 & \frac{\partial L}{\partial y_{3}}x_{1} \\ 0 & 0 & \frac{\partial L}{\partial y_{3}}x_{2} \end{bmatrix} $$
i + ii + iii ) $$ \frac{\partial L}{\partial W} = \begin{bmatrix}\frac{\partial L}{\partial y_{1}}x_{1} & \frac{\partial L}{\partial y_{2}}x_{1} & \frac{\partial L}{\partial y_{3}}x_{1} \\ \frac{\partial L}{\partial y_{1}}x_{2} & \frac{\partial L}{\partial y_{2}}x_{2} & \frac{\partial L}{\partial y_{3}}x_{2} \end{bmatrix} $$
다시 행렬곱으로 표현하면 결론을 얻는다.
$$ \frac{\partial L}{\partial W} = \begin{bmatrix}x_{1}\\x_{2}\end{bmatrix}\times\begin{bmatrix}\frac{\partial L}{\partial y_{1}} & \frac{\partial L}{\partial y_{2}} & \frac{\partial L}{\partial y_{3}} \end{bmatrix} $$ $$ \frac{\partial L}{\partial W} = X^T\frac{\partial L}{\partial Y} $$
batch 데이터에 대한 어파인 계층의 경우는 방금은 에서 그냥 데이터의 차원만 n차원이라고 생각하고 계산하면 되므로 간단하다. 차이점은 편향이다. 순전파에서 각각의 데이터에 편향이 더해지기 때문에 역전파에서 상류에 전달된 \(\frac{\partial L}{\partial Y}\)의 각 열을 더해 \(\frac{\partial L}{\partial b}\)를 구해야 한다.
어파인 계층 또한 수식은 복잡했지만 결론으로는 \(X^T\)와 \(W^T\)로만 계산이 되기 때문에 국소 계산이 매우 간단한다.
class Affine:
def __init__(self, W, b):
self.W = W
self.b = b
self.x = None
self.original_x_shape = None
# 가중치와 편향 매개변수의 미분
self.dW = None
self.db = None
def forward(self, x):
# 텐서 대응
self.original_x_shape = x.shape
x = x.reshape(x.shape[0], -1)
self.x = x
out = np.dot(self.x, self.W) + self.b
return out
def backward(self, dout):
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
dx = dx.reshape(*self.original_x_shape) # 입력 데이터 모양 변경(텐서 대응)
return dx
2.6 Softmax-with-Loss 계층
Softmax-with-Loss 계층은 출력층에서 softmax함수를 거쳐 나온 출력 신호를 cross entropy error함수를 통해 오차까지 출력이 되는 계층이다. 이 둘을 묶는 이유는 역전파에서 softmax함수와 cross entropy error함수를 같이 사용하면 매우 간단하게 출력신호가 나오도록 설계가 되어 있기 때문이다.
출력 신호인 \(y-t\)는 예측 결과와 정답 레이블 간의 차이이다. 계산은 단순 계산이므로 하지 않겠다.
class SoftmaxWithLoss:
def __init__(self):
self.loss = None
self.y = None
self.t = None
def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
if self.t.size == self.y.size: # 정답 레이블이 원-핫 인코딩 형태일 때
dx = (self.y - self.t) / batch_size
else:
dx = self.y.copy()
dx[np.arange(batch_size), self.t] -= 1
dx = dx / batch_size
return dx
'밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
[논문 리뷰]An overview of gradient descent optimization algorithms*SebastianRuder (0) | 2024.12.31 |
---|---|
Backpropagation Example (0) | 2024.12.24 |
Neural Network Learning Example (0) | 2024.12.22 |
Neural Network Learning (0) | 2024.12.22 |
Neural Network example (1) | 2024.12.20 |