본문 바로가기
Deep Learning

[CS231n] 03. Backpropagation and Neural Networks

by TaekGeun 2022. 3. 6.

이번 포스팅에서는 오차 역전파에 대해 알아보도록 하겠습니다. 

 

지난 포스팅에서는 손실 함수에 대해서 알아보았고, 이를 최소화시킬 방법이 경사 하강법이라는 것까지 얘기했습니다. 경사 하강법은 결국 가장 가파른 방향으로 step을 조금씩 밟아나가 근처에 있는 최소점에 수렴하게 만들어주는 알고리즘입니다.

 

가장 가파른 방향이라는 것은 결국 Gradient로 정의가 되는데 이는 알고 보면 각 parameter에 대한 편미분 계수로 이루어진 벡터입니다. 즉, Gradient를 구하려면 Loss함수에 대해서 각 parameter에 대한 편미분 계수를 구해야 parameter를 업데이트할 수 있을 것 같습니다. 

Chain Rule

미분 계수야 뭐 미분 공식만 잘 적용해주면 구할 수야 있겠지만 문제는 복잡한 신경망을 통과해서 나온 output을 가지고 오차를 측정하는 손실 함수는 결국 엄청나게 복잡한 합성함수의 형태라는 점입니다.

 

간단한 예시를 들어보도록 하겠습니다.

출처 :  https://edgeaiguru.com/Feedforward-and-Backpropagation

 

자 2개의 입력을 받아서 처리하는 Network가 있다고 가정해보겠습니다. 결국 저 Network의 parameter 들은 $W^{[1]}_{11},W^{[1]}_{12},W^{[1]}_{21},W^{[1]}_{22},W^{[2]}_{12},W^{[2]}_{22}$ 이 녀석들이 되겠네요. 

 

Loss 함수를 Squared Error로 정의한다면 $(\hat{y}-y)^{2}$가 될 것 같습니다. 자 이제 이 Loss를 $W^{[1]}_{11},W^{[1]}_{12},W^{[1]}_{21},W^{[1]}_{22},W^{[2]}_{12},W^{[2]}_{22}$ 이 녀석들로 미분을 하려고 보면 바로 미분계수를 구할 수 없을 것 같습니다. 

 

당장 Loss만 놓고 보면 독립변수가 $\hat{y}$ 밖에 존재하지 않기 때문입니다.  $\hat{y}$ 는 $z^{[2]}$에 대한 함수이고 $z^{[2]}$는 $s^{[2]}$에 대한 함수이고 이렇게 엉켜있다 보니, 편미분 계수를 바로 구하기는 힘들어 보입니다.

 

이렇게 복잡한 합성 함수의 미분하는 규칙을 제공하는 것이 연쇄 법칙(chain rule)입니다. 오차 역전파법의 배경은 결국 연쇄 법칙입니다. 신경망이라 불리는 Neural Network는 결국 합성함수라 생각할 수 있습니다. Linear Layer를 거치고 비선형 함수인 활성화 함수(Activation Function)를 거치고 이러한 과정들의 반복입니다.

 

연쇄 법칙의 증명이나 복잡한 계산 상황에 대해서는 다루지 않고 어떻게 구하는 지만 같이 살펴보도록 하겠습니다.

 

예를 들어 $W^{[1]}_{11}$에 대한 미분계수를 구하고 싶다면 $W^{[1]}_{11}$가 입력으로 흘러가서 출력까지의 과정을 다시 거꾸로 tracking 하면 됩니다. 한번 구해보도록 하겠습니다.

 

당장 우리의 Loss함수는 $z^{[2]}$로만 구성이 되어있으니 $z^{[2]}$에 대한 미분을 계산해줍니다.

이제는 $z^{[2]}$에 대해서 살펴봐야 할 차례입니다. $z^{[2]}$는 $s^{[2]}$의 함수이므로 $z^{[2]}$를 $s^{[2]}$로 미분해주면 됩니다. 여기서 이전에 구했던 미분계수에 현재 구해준 미분계수를 곱해주어야 합니다.

 

여기서 살짝 헷갈릴 수 있으니 정리하겠습니다. 현재 우리는 $s^{[2]}$를 바라보고 있습니다. $s^{[2]}$에서 어떤 변수에 대해서 미분을 해야 하나? $s^{[2]}$는 $z^{[1]}_{1},z^{[1]}_{2},W^{[2]}_{12},W^{[2]}_{22}$에 대한 함수입니다.

 

$s^{[2]}=W^{[2]}_{12}z^{[1]}_{1}+W^{[2]}_{22}z^{[1]}_{2}$ 여기서 $W^{[1]}_{11}$와 관련된 변수에 대해서 미분을 해야 $W^{[1]}_{11}$까지 연쇄 법칙을 이용해 미분 계수를 구할 수 있습니다.

 

$W^{[1]}_{11}$와 관련된 변수는 $s^{[2]}$는 $z^{[1]}_{1},z^{[1]}_{2},W^{[2]}_{12},W^{[2]}_{22}$ 중 무엇일까요?

 

그림 보면 알겠지만 $z^{[1]}_{1}$가 $s^{[1]}_{1}$를 입력으로 받고 $s^{[1]}_{1}$에 $W^{[1]}_{11}$가 관여를 하게 되는 구조인 것 같네요. 

 

따라서 $s^{[2]}$를 $z^{[1]}_{1}$에 대해서 편미분 해야 합니다. 

 

위의 과정과 동일하게 $z^{[1]}$에 대해서 살펴봐야 할 차례입니다. $z^{[1]}$는 $s^{[1]}$의 함수이므로 $z^{[1]}$를 $s^{[1]}$로 미분해주면 됩니다. 계속 Gradient는 곱해주면서 누적시켜줍니다.

드디어 끝에 도착했습니다. 우리가 현재 보고 있는 것은 $s^{[1]}$이네요. $s^{[1]}=W^{[1]}_{11}x_{1}+W^{[1]}_{21}x_{2}$ 이므로 $s^{[1]}$를 $W^{[1]}_{11}$에 대해서 미분해주면 끝이 나겠네요.

 

이렇게 Chain Rule을 이용하면 복잡한 신경망에서도 못 구할 미분계수는 없습니다. 구하는 과정을 한번 살펴보면 이전 Layer의 미분을 계속해서 누적시켜주는 구조였습니다. 

 

즉 가장 뒷단의 Layer로부터 미분계수를 구해서 넘겨주는 방식이라 뒤에서부터 역으로 계산해준다는 뜻으로 오차 역전파법이라 부르는 것 같습니다.

 

보다 더 깊은 이해를 원하신다면 벡터 미적분학에서 연쇄 법칙을 따로 공부하면 좋을 것 같습니다.

Computational Graph

위에서 살펴본 예제는 정말 간단해서 이렇게 손으로 다 구했지만 실제로 신경망은 훨씬 더 크고 복잡할 것입니다. 이를 간편하게 하기 위해서 Computational Graph를 이용하여 오차 역전파를 수행합니다. 그래프에 포함되는 각 노드들은 입력값이나 연산을 담고 있다 보면 됩니다.

 

Tensor Flow는 안 써봐서 모르겠지만 Pytorch는 Neural Network의 연산을 진행할 때 Autograd라는 기능을 구현해서 자동으로 Computational graph를 이용한 Forward Propagation과 Backpropagation 연산을 수행하게 해 줍니다. 

출처 : https://pytorch.org/blog/computational-graphs-constructed-in-pytorch/

 

Computational graph를 이용하여 Backpropgation을 진행하면 좋은 점이 간단하다는 점입니다. 복잡한 연산도 결국 쪼개고 쪼개다 보면 더하기나 곱셈 혹은 max gate 등 간단한 연산으로 분할할 수 있다는 점입니다. 따라서 사전에 몇 가지 Operation에 대해서 Pattern만 정의해주면 쉽게 쉽게 미분을 구할 수 있습니다.

 

 

 

위에서 연쇄 법칙에 대해서 알아보았으니 위의 사진을 잘 이해할 수 있을 것 같습니다. 결국 뒤에서부터 target variable에 대해서 gradient들을 보내주는 식으로 모든 모델 파라미터에 대해서 미분을 구할 수 있을 것 같습니다. 

 

이전 Layer로부터 전달된 gradient를 upstream gradient라 보통 불러주고, 현재의 Layer에서 계산된 gradient를 local gradient라 부릅니다. 이런 식으로 각 Layer에 해당하는 Variable로 미분을 구해주고 이전 Layer로 넘겨주다가 가장 앞단의 Layer에 도착하면 연쇄 법칙이 끝이 나겠죠.

 

Computational graph에서 자주 보는 몇 가지 패턴에 대해서 살펴보도록 하겠습니다.

 

 

add gate는 이전 layer에서 전달받은 미분을 단순히 분배하면 된다고 나와있네요. 연쇄 법칙을 생각하면 (x+y)의 각각 미분은 1이기 때문에 이전 Layer의 미분을 그대로 받아 온다고 보면 됩니다.

 

max gate는 이전 layer에 전달받은 미분을 max 값에만 전달하는 라우터 역할을 한다고 보면 됩니다. max 값만 입력으로 전달되고 나머지 값은 그다음 layer로부터 영향을 끼치지 않았기 때문에 미분이 0으로 나오기 때문입니다. 미분이 0으로 나오면 Computational graph에서 연결이 끊어진다고 생각하면 됩니다.

 

mul gate는 이전 layer에서 전달받은 미분을 가지고 입력을 스위칭해 곱해주면 그만입니다. xy가 있을 때 x에 대한 미분은 y이고 y에 대한 미분은 x이기 때문에 스위칭되는 구조라 볼 수 있겠네요.

 

 

 

스칼라 값만을 입력으로 하는 역전 파는 Chain Rule만 이해한다면 사실 계산하기 쉽습니다. 하지만 우리의 Neural Network Layer는  복잡한 matrix나 vector를 입력으로 받아서 처리하죠 따라서 Vectorized operation에 대한 Backpropagation을 한번 자세히 알아보도록 하겠습니다.

 

그전에 자코비안 행렬이 어떤 것인지 간단하게 짚고 넘어가겠습니다.

 

Jacobian Matrix

 

자코비안 행렬의 깊은 의미까지는 서술할 예정은 아니고, Backpropagation 시에 어떤 상황에서 필요한지 얘기해보도록 하겠습니다. 자코비안 행렬 자체의 정의는 아래와 같습니다.

 

우리의 입력 벡터가 n차원이라 가정하고 이를 m차원으로 projection 시키는 선형 레이어가 있다고 가정하겠습니다. $x\in R^{n},f(x)\in R^{m}$ 선형 레이어라면 당연히 미분 가능하기 때문에 편미분 계수가 존재할 수 있습니다. 자 이 선형 레이어를 구성하는 매개변수(parameter)는 몇 개 존재할까요? $R^{n\times m}$ 의 차원을 가지게 되겠죠? 이 자코비안 행렬의 원소들은 결국 이 선형 레이어의 매개변수들의 미분계수를 담고 있는 행렬이라 볼 수 있습니다. 

출처 : https://angeloyeo.github.io/2020/07/24/Jacobian.html

 

즉, 벡터를 입력받아서 벡터를 출력하는 레이어가 있을 때, Jacobian matrix를 계산해서 Backpropagation이 진행되게 됩니다. 

 

강의의 예제에서는 max gate를 담당하는 레이어가 있다고 가정할 때 Jacobian matrix를 구해보라는 질문을 했었죠. 이건 간단하게 자코비안 행렬은 대각 행렬로 구성이 될 것이고 대각 성분들은 모두 1이 될 것이다 가볍게 유추할 수 있습니다.

 

이제 벡터로 받아왔을 때 Graph를 통한 Backpropgation을 진행해보겠습니다. $x=<x_{1},x_{2},\ldots ,x_{n}>,x\in R^{n}$ 그리고 간단한 Linear Layer를 생각해보도록 하겠습니다. 이 Layer의 Parameter는 Weight 밖에 존재하지 않으며 그 parameter 개수는 n by n이라 가정하겠습니다. 

 

  • $W\in R^{n\times n}$
  • $f(x,W)=\parallel W\cdot x\parallel^{2} =\sum^{n}_{i=1} (W\cdot x)^{2}_{i}$

이렇게 간단한 Linear Layer를 정의하였고 Computational Graph로 나타내면 아래와 같은 그래프로 표현할 수 있습니다.

 

이제 여기서 임의의 입력을 넣어서 값을 계산해보도록 하겠습니다. 

 

 

Forwarding 과정을 거치면 0.116이라는 값이 계산이 되나 봅니다. 이제 우리는 가장 뒷단의 stream으로부터 Gradient를 계산하여 backpropgatation을 따라 해 보도록 하겠습니다.

 

뒷단부터 출발하겠습니다. $\left[ \begin{matrix}0.22\\ 0.26\end{matrix} \right]$ 벡터를 입력받아 0.116을 만들어내는 L2 layer를 거쳤을 때 gradient를 계산해보겠습니다. 벡터를 입력받아 스칼라를 반환하는 스칼라장의 미분은 gradient로써 각 독립변수에 대한 편미분 계수를 가지는 벡터가 gradient라고 이전 포스팅에서 다룬 적이 있습니다. 

 

$z=y^{2}_{1}+y^{2}_{2}$ 일 때 gradient는 $\nabla z=[\frac{\partial z}{\partial y_{1}} ,\frac{\partial z}{\partial y_{2}} ]=[2y_{1},2y_{2}]$ 이렇게 계산이 되니 대입하면 $\left[ \begin{matrix}0.44\\ 0.52\end{matrix} \right]$ 를 구할 수 있겠네요.

다음으로는 $\left[ \begin{matrix}\frac{\partial z}{\partial w_{11}} &\frac{\partial z}{\partial w_{12}} \\ \frac{\partial z}{\partial w_{21}} &\frac{\partial z}{\partial w_{22}} \end{matrix} \right] $ 을 구할 차례가 온 것 같습니다.

 

chain rule을 통해 각 미분계수를 구해주면 다음과 같이 계산할 수 있습니다. $\frac{\partial z}{\partial w_{11}} =\frac{\partial z}{\partial y_{1}} \times \frac{\partial y_{1}}{\partial w_{11}} =2y_{1}x_{1}$ 쉽게 정리하면 $x^{T}\nabla z$

 

결국 vector를 입력으로 받아도 mul gate에서는 스위칭하는 형식으로 gradient가 계산이 되네요.

 

여기까지 해서 오차 역전파에 대해서 알아보았습니다. 본 포스팅에서는 말고 따로 넘파이로 구현한 MLP 학습시키기 코드 실습을 진행해봐야겠습니다. 이론으로 아는 거랑 코드로 구현하는 거는 또 느낌이 다르기 때문이죠.

Neural Network

우리가 신경망이라 부르는 Neural Network의 형태를 갖추기 위해서는 이제 활성화 함수가 등장하게 됩니다. 단순히 Linear Layer를 여러 개 쌓으면 되는 거 아니냐 이렇게 생각할 수 있지만 Linear Layer를 여러 개 쌓아봤자 어차피 Matrix 연산 취해주면 Linear Layer 하나 있는 것과 동일한 구조를 가지게 됩니다.

 

Linear Layer 하나 있는 구조는 결국 결정 경계가 Linear 하게 결정되므로 한계가 명확했습니다. 모델의 복잡도를 높이기 위해 활성화 함수가 등장하게 됐고 이는 비선형성을 가하기 위해 사용한다고 볼 수 있습니다.

 

 

Layer를 더 깊게 쌓는 것은 모델이 더욱 복잡해지고 복잡한 경계를 가지게 되므로 복잡한 문제를 풀 수 있음을 의미합니다.

 

 

Neural Network에서 사용될 수 있는 활성화 함수 종류입니다. ReLU를 보편적으로 많이 사용하지만 또 요즘 논문을 찾아보면 ReLU의 변형도 많이 사용하고 있는 것 같습니다. 활성화 함수에 대한 자세한 내용은 다다음 포스팅에서 다루는 것으로 하겠습니다.

 

CS231n 강의에서도 지금 다루고 있는 이 Neural Net은 실제 뇌의 비하면 한~참 단순한 구조라 얘기를 합니다. 단순히 여러 개의 함수만 통과시키는 구조 이기 때문입니다. 

 

다음 포스팅에서는 이제 이미지 처리에 특히 효과적인 합성곱 신경망에 대해서 알아보도록 하겠습니다.

'Deep Learning' 카테고리의 다른 글

[CS231n] 02. Loss Functions and Optimization  (0) 2022.01.26
[CS231n] 01. Image Classification Pipeline  (0) 2022.01.25