ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [PyTorchZerotoAll 내용정리] 04 Back propagation
    ML&DL 이야기/Pytroch 2023. 8. 16. 10:10

    Why Backpropagation?

    Neural Network 학습의 최종적인 목표는 Cost를 최소화시키는 Neural Network 내의 모든 Unit의 가중치 W 를 찾는 것이다.

    우리가 익히 아는 Gradient Descent Algorithm을 이용해 이를 최적화한다고 하다. 

    이를 위해서는 간단히 인공지능의 역사를 알아야하는데, 

    단일 퍼셉트론

    간단하게 말하면, 이전에 우리가 흔히 아는 퍼셉트론(NN의 한 unit)으로 문제를 해결하려고 했지만, 1969년, MIT의 마빈 민스키와 시모어 패퍼트는 저서 <퍼셉트론>에서, 퍼셉트론이 선형 분리가 불가능한 데이터들을 분류할 수 없음을 증명하면서 인공지능의 겨울이 찾아왔다.

    해당 저서에서 다층 퍼셉트론으로는 XOR 연산을 할 수 있다고 밝혔지만, ‘지구 상의 그 누구도 이를 학습시킬 방법을 찾아낸 바가 없다’며 다층 퍼셉트론의 구현에 회의감을 드러냈다. 

    그 이유는 단일 퍼셉트론은 원하는 결과 값과의 오차를 계산하여 이에 비례하게 가중치를 조정할 수 있다.

    단일 퍼셉트론 학습방법
    가중치 업데이트는 이전과 동일하게 gradinet descent를 이용한다. 
    퍼셉트론이 틀리는 샘플 집합을 y라고 하자. 그럼 손실함수는 
    $$J(\mathbf{w})=\sum_{\mathbf{x}_{k} \in Y}-y_{k}\left(\mathbf{w}^{\mathrm{T}} \mathbf{x}_{k}\right)$$
    이다(a를 1로 분류해야한다고 하자, 실제 가중치를 계산했더니 -1이 나왔다면, -1 * -1 = 1이다, 반대로 -1로 분류해야하는데 1이 나왔어도 1이 된다)

    그럼으로 손실함수를 가중치에 관해 편미분하고 gradient descent를 이용하면 아래와 같이 표기된다. 
    $$w_{i}=w_{i}+\alpha \sum_{\mathrm{x}_{k} \in Y} y_{k} x_{k i}, \quad i=0,1, \cdots, d$$
    즉, cost function을 특정 i번째 가중치에 관해 편미분하면 해당하는 y와 k번째 x의 i번째 요소와 곱한것의 시그마 형태로 나타나는데 이를 learning rate에 곱해 더한다. 

    하지만 다층 퍼셉트론은 데이터가 입력되는 입력층, 결과 값을 출력하는 출력층 사이에도 여러 은닉층이 존재한다. 은닉층에 속하는 노드의 출력값은 오차를 측정할 수 없기 때문에 각각의 unit에 해당하는 가중치를 어떻게 조정해야 하는지 알 수 없다.

    Back propagation 알고리즘은 출력층에서 발생한 오차를 출력층에서 입력층의 방향으로 보내면서, 은닉층의 노드 사이의 가중치를 재조정한다. Back propagation을 사용하면 수많은 노드층이 쌓인 다층 퍼셉트론도 학습시킬 수 있기에 이를 이용한다.

     

    NN is so complicated

    다시 말해, chain rule을 이용해 backpropagation을 이용하면 더 쉽게 계산할 수 있다. 

    다음과 같은 모델이 있을 때 우리는 각 layer 간 연결의 가중치를(wieght and bias) 업데이트해야한다. 

     

    이 말을 풀어서 쓰면, 우리는 결국 z의 최종 loss만 알지만 이 loss를 이용해 각 layer의 가중치를 조절하려면 이에 맞추어 각 가중치가 해당 loss 혹은 최종 output에 얼마나 영향을 끼치는지를 알아야한다.

    이것이

    $$ \frac{\partial L}{\partial x}$$로 표현된다.

    x가 L(손실)에 얼마나 영향을 끼쳤는가?? 라는 의미이다. 

     

    그럼 이를 구하려면 L -> z -> x 순으로 계산하면 된다.

    backward propagation

    이때 앞에서 계산한 값들이 chain rule에 의해서 그대로 사용되게 되는데 이것이 backpropagation이다. 

     

    Foward Propagation

    아래의 모델과 예시(forward, backward)는 모두 https://evan-moon.github.io/2018/07/19/deep-learning-backpropagation/#forward-propagation 글을 기반으로 작성되었습니다. 

    example NN model

    2개의 input, 2개의 output을 가지고 2개의 Hidden Layer를 가진 2-Layer NN 예시 모델을 이용해서 Foward와 backward를 모두 확인해보자. 

    우선 값을 할당하면 아래와 같다(각 가중치는 랜덤이다)

    Activation Function으로는 sigmoid함수를 Error function으로는 MSE를 사용한다고 하자. 

    Layer 0 

    layer0에서 받는 값들은 보통 행렬로 계산된다. 

     

    $$z_{10}=\begin{bmatrix} x_1 \\ x_2 \end{bmatrix}\times\begin{bmatrix} w^0_{10} \\ w^0_{20} \end{bmatrix}$$

    $$z_{11}=\begin{bmatrix} x_1 \\ x_2 \end{bmatrix}\times\begin{bmatrix} w^0_{11} \\ w^0_{21} \end{bmatrix}$$

     

    해당 행렬을 곱을 계산하면 

    $$ z_10 = (0.2 \times 0.1) + (0.5 \times 0.3) = 0.02 + 0.15 = 0.17 $$

    $$ z_11 = (0.2 \times 0.2) + (0.5 \times 0.1) = 0.04 + 0.05 = 0.09 $$

     

    이제 각각의 z를 activation function에 통과시킨 $a_{10}$와 $a_{11}$의 값을 구해보자. 

    sigmoid의 수식은 다음과 같다. 

    $$ σ = \frac{1}{1+e^{-x}}$$

     

    그럼으로 각각의 a의 값은 

    $a_{10} = σ(z_{10}) = 0.54$$

    $$a_{11} =σ(z_{11}) = 0.52$$

     

    같은 방식으로 값을 layer1에 통과시키면 아래와 같은 값들이 나온다. 

    $$z_{20}=0.27$$

    $$z_{21}=0.43$$

    $$a_{20}=0.57$$

    $$a_{21}=0.61$$

     

    우리는 2layer임으로 $y_1$과 $y_2는$a_{20}$,$a_{21}$과 같음으로 이를 이용해 loss function에 따른 Loss값을 구할 수 있다.

    MSE에 따라 우리의 loss L은 다음과 같다(t가 실제값, y가 예측값이라고 하자)

    $$L=\frac{1}{2} \sum\left(t_{i}-y_{i}\right)^{2}$$

    그럼으로 이 값은 0.072이다. 

    BackPropagation

    우리는 이제 각 가중치를 업데이트 하기 위해 전체 에러인 $L$에 얼마나 해당 가중치가 영향을 미쳤는지, 기여도를 구해야한다. 이때 Chain Rule을 위에서 설명한 것 같이 이용한다.

     

    우선 $w_{10}^1$을 업데이트 해보자. 

    이에 해당하는 가중치가 영향을 끼치는 부분만 그림을 잘라서 보면 다음과 같다. 

    w(1)10이 영향을 주는 부분

    기여도를 구하기 위한 chain rule적용식은 아래와 같다.

     

    $$\frac{\partial E}{\partial w_{10}^{1}}=\frac{\partial E}{\partial a_{20}} \frac{\partial a_{20}}{\partial z_{20}} \frac{\partial z_{20}}{\partial w_{10}^{1}}$$

     

    식을 보면 위 그림을 y부터 거꾸로 w까지 전개하는 식임을 알 수 있다. 

     

    그럼 이 식을 차례대로 풀어보면 다음과 같다. 

    - $\frac{\partial E}{\partial a_{20}}$

    $$L=\frac{1}{2} \sum\left(t_{i}-y_{i}\right)^{2}$$

    임으로 이를 $a_20$에 관한 식으로 바꾸면 다음과 같다. 

    $$L=\frac{1}{2}\left(\left(t_{1}-a_{20}\right)^{2}+\left(t_{2}-a_{21}\right)^{2}\right)$$

    이때 편미분이기 때문에 뒤에 항은 무시하면 결국 아래와 같이 풀리낟. 

    $$\frac{\partial L}{\partial a_{20}}=\left(y_{1}-a_{20}\right) *-1+0=(0.2-0.57) \times-1=0.37$$

     

    이 0.37이 바로 전체 에러 L에 관한 $a_20$의 기여도이다. 

    같은 방법으로 나머지를 계산해보자. 

    이때 sigmoid의 미분값은 

    $$ \begin{aligned}
    \frac{d}{d x} \operatorname{sigmoid}(x) & =\frac{d}{d x}\left(1+e^{-x}\right)^{-1} \\
    & =(-1) \frac{1}{\left(1+e^{-x}\right)^{2}} \frac{d}{d x}\left(1+e^{-x}\right) \\
    & =(-1) \frac{1}{\left(1+e^{-x}\right)^{2}}\left(0+e^{-x}\right) \frac{d}{d x}(-x) \\
    & =(-1) \frac{1}{\left(1+e^{-x}\right)^{2}} e^{-x}(-1) \\
    & =\frac{e^{-x}}{\left(1+e^{-x}\right)^{2}} \\
    & =\frac{1+e^{-x}-1}{\left(1+e^{-x}\right)^{2}} \\
    & =\frac{\left(1+e^{-x}\right)}{\left(1+e^{-x}\right)^{2}}-\frac{1}{\left(1+e^{-x}\right)^{2}} \\
    & =\frac{1}{1+e^{-x}}-\frac{1}{\left(1+e^{-x}\right)^{2}} \\
    & =\frac{1}{1+e^{-x}}\left(1-\frac{1}{1+e^{-x}}\right) \\
    & =\operatorname{sigmoid}(x)(1-\operatorname{sigmoid}(x))
    \end{aligned} $$

    이라는 것에 유의하자! 

     

    그럼으로 이어서 계산하면 

    $$\frac{\partial a_{20}}{\partial z_{20}}=a_{20} \times\left(1-a_{20}\right)=0.57 \times(1-0.57)=0.25$$

    $$\frac{\partial z_{20}}{\partial w_{10}^{1}}=a_{10}+0=0.54$$

    이기에 

    최종적으로 

    $$\frac{\partial E}{\partial w_{10}^{1}}=0.37 \times 0.25 \times 0.54=0.049$$

    이다. 

    따라서 L에 $w_{10}^1$이 기여한 값이 0.049라는 것을 계산했다. 이를 그러면 우리가 이전 시간에 보았던 gradient descent식에 넣으면 값을 업데이트 할수 있다. 

    이번에는 learning rate를 0.3로 가정했다. 

    그럼 가중치를 업데이트하면 다음과 같다. 

    $$w_{10}^{1+}=w_{10}^{1}-\left(L * \frac{\partial E}{\partial \nu^{1}}\right)=0.4-(0.3 \times 0.049)=0.3853$$

     

    이렇게 해서 새로운 $w_{10}^1$ 값을 얻었다 이를 이용해서 $w_{10}^0$를 업데이트 해보자. 

     

    - $w_{10}^0$

    보면 $w_{10}^0$은 더 많은 값에 영향을 미치고 있다. 전체 에러 L에 $w_{10}^0$의 기여도는 다음과 같다. 

    $$ \frac{\partial E_{t}}{\partial w_{10}^{0}}=\left(\frac{\partial E_{1}}{\partial a_{10}}+\frac{\partial E_{2}}{\partial a_{10}}\right) \frac{\partial a_{10}}{\partial z_{10}} \frac{\partial z_{10}}{\partial w_{10}^{0}} $$

     

    그럼 순서대로 $\frac{\partial E_{1}}{\partial a_{10}}$부터 구하자.

     

    $$\begin{array}{r}
    \frac{\partial E_{1}}{\partial a_{10}}=\frac{\partial E_{1}}{\partial a_{20}} \frac{\partial a_{20}}{\partial z_{20}} \frac{\partial z_{20}}{\partial a_{10}} \\
    =-\left(t_{1}-a_{20}\right) \times a_{20} \times\left(1-a_{20}\right) \times w_{10}^{1} \\
    =-(0.2-0.57) \times 0.57 \times(1-0.57) \times 0.4 \\
    =0.03627
    \end{array} $$

     

    같은 방법으로 $\frac{\partial E_{2}}{\partial a_{10}}$부터 구하자.

    $$\begin{array}{r}
    \frac{\partial E_{2}}{\partial a_{10}}=\frac{\partial E_{2}}{\partial a_{21}} \frac{\partial a_{21}}{\partial z_{21}} \frac{\partial z_{21}}{\partial a_{10}} \\
    =-\left(t_{2}-a_{21}\right) \times a_{21} \times\left(1-a_{21}\right) \times w_{11}^{1} \\
    =-(0.7-0.61) \times 0.61 \times(1-0.61) \times 0.5 \\
    =-0.0107
    \end{array}$$

     

    그럼 마지막으로 $\frac{\partial E_{t}}{\partial w^0_{10}}$을 구하면 아래와 같다. 

    $$\begin{array}{r}
    \frac{\partial E_{t}}{\partial w_{10}^{0}}=\left(\frac{\partial E_{1}}{\partial a_{10}}+\frac{\partial E_{2}}{\partial a_{10}}\right) \frac{\partial a_{10}}{\partial z_{10}} \frac{\partial z_{10}}{\partial w_{10}^{0}} \\
    =(0.03627+(-0.0107)) \times 0.2484 \times 0.54 \\
    =0.0034
    \end{array}$$

     

    이로써 최종적으로 $w_{10}^0$의 기여도는 0.0034라는 것을 알 수 있다. 이때 보면 기여도가 위에서보다 감소하는 것을 알 수 있는데 이는 미분한 값을 계속 곱하다보니 생기는 일이다(차후 gradient vanishing문제의 원인이 되기도 한다)

     

    마지막으로 값을 업데이트하면 다음과 같다. 

    $$w_{10}^{0+}=w_{10}^{0}-\left(L * \frac{\partial E_{t}}{\partial w_{10}^{0}}\right)=0.1-(0.3 \times 0.0034)=0.09897$$

     

     

    Code implementation

    위 내용(새로운 그림)을 코드로 구현하면 아래와 같다.

    이는 colab 링크를 첨부한다. 

    https://colab.research.google.com/drive/1QB72fSRQTZihbzbp8qf7cf-jRWZFy4Ax#scrollTo=8AR3FKTrvbff

    몇몇 확인해야할 부분은 아래와 같은데

    우선 우리가 실제로 하나씩 모두 미분하기에는 굉장히 많은 수고가 들기 때문에 pytorch에서는 autograd라는 기능을 통해 해당 계산을 자동으로 해준다는 것이다. 

    이를 위해 우리는 다음과 같이 작성하면 된다. 

    w가 가중치, 즉, autograd의 대상이 되는 변수임을 알려주고

    차후 학습시킬때 

    l은 w에 관한 식임으로 backward가 가능하다. 즉, 이를 통해서 w의 grad가 저장되고 이를 이용해 w를 업데이트 시킨다. 

    마지막으로 w의 gardinet값을 초기화시켜준다(다음 grad 계산을 위해)

     

    추가출처

    https://evan-moon.github.io/2018/07/19/deep-learning-backpropagation/

Designed by Tistory.