본문 바로가기

밑바닥부터 시작하는 딥러닝

Backpropagation

공부를 하는 입장이기 때문에, 내용에 오류가 있을 수 있습니다. 오류가 있다면 적극적으로 알려주시면 감사합니다!
 

1. 오차역전법(backpropagation)

 오차 역전파란 신경망의 출력과 실제 값 간의 오차를 기반으로, 그 오차를 입력층까지 거꾸로 전달하며 가중치를 조정하는 방식을 말한다. 일반적으로는 순전파를 통해 입력 데이터를 네트워크에 전달하여 추론 값을 얻고, 역전파를 통하여 각 층의 가중치에 대한 기울기를 계산하여 경사하강법을 사용한다. 
 
 100원짜리 사과를 3개 샀을 때, 10%의 소비세가 붙는다면 최종 가격은 얼마일까? 이 문제를 풀기 위해, 먼저 사과의 개당 가격(100원)에 사과의 개수(3개)를 곱하고, 이후 소비세(10%)를 적용해 계산할 수 있다.
 
 이 과정을 신경망으로 표현하면, 순전파(Forward Propagation)는 입력(사과의 가격)을 이용해 중간 계산(가격 합산)과 가중치(소비세율)를 거쳐 최종 출력(최종 가격)을 계산하는 흐름이다. 순전파는 그림에서 검정 화살표로 표현되며, 입력 신호가 가중치(사과 개수와 소비세율)를 거쳐 출력 신호로 변환되는 과정이다.
 
 반면, 역전파(Backpropagation)는 계산된 최종 가격이 실제 기대 값과 차이가 있을 때, 이 오차를 줄이기 위해 가중치(여기서는 사과 개수와 소비세율)를 조정하는 과정이다. 역전파는 오차를 출력에서 입력 방향으로 거슬러 올라가며 각 단계에서의 기울기(Gradient)를 계산한다. 역전파는 이 그림에서 빨간 화살표로 표현된다. 이 기울기를 통해 각 가중치가 오차에 얼마나 기여했는지를 평가하고, 가중치를 조정하여 더 정확한 결과를 얻도록 만든다.
이 과정은 다음과 같은 단계로 이루어진다:

  1. 출력 오차 계산:
    최종 계산된 가격과 실제 가격의 차이를 계산한다.
  2. 오차 역전파:
    오차를 소비세율과 사과 개수 방향으로 거꾸로 전달하며, 각 가중치가 손실에 미친 영향을 계산한다.
  3. 가중치 조정:
    기울기를 기반으로 사과 개수나 소비세율과 같은 가중치를 조정하여, 다음 순전파에서 더 정확한 최종 가격이 나오도록 학습을 진행한다.

계산 그래프(학교 ppt)

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\)가 된다.

학교 ppt

 이처럼 이전 신호와 상관없이 해당 노드에서의 계산만 수행하는 것을 국소(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}\)를 구해야 한다.

batch data에서 편향에 대한 계산 노드

어파인 계층 또한 수식은 복잡했지만 결론으로는 \(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함수를 같이 사용하면 매우 간단하게 출력신호가 나오도록 설계가 되어 있기 때문이다. 

책에 있는 Softmax-with-Loss 계층의 계산 그래프

 출력 신호인 \(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