CS231n Lecture4 Review
04 Aug 2022 | CS231n
해당 게시물은 Standford 2017 CS231n 강의와 2022년 슬라이드를 바탕으로 작성되었습니다.
Lecture3 Recap
함수 $f$ 를 통해 어떻게 classifier를 정의하는지 다뤘습니다.
- weights W를 파라미터로 가집니다.
- 함수 f의 입력(input)은 데이터 $x$,
- 함수 f의 출력(output)은 분류하고자 하는 클래스들에 대한 score vector
또한 loss function을 정의했습니다.
- classifier 얼마나 badness한지 점수로 나타내기위해 Full loss 항(term)을 정의.
- Full loss = data loss + regularization
- regularization은 얼마나 우리의 모델이 단순한지, 더 나은 일반화를 위해 적합한 모델을 가지고 있는지를 표현.
또한 최적의 loss를 갖게 하는 파라미터 $W$ 를 찾고 싶어서, loss function의 W에 관한 gradient를 찾았습니다
- Gradient Descent를 통해 optimization을 하여 최적의 $W$ 를 찾습니다.
- gradient가 음수인 방향, 즉 경사가 하강하는 방향을 반복적으로 구해 아래로 나아가 가장 낮은 loss에 찾습니다.
또한 gradient를 구하는 다른 방법에 대해서도 이야기했습니다.
- 수학적으로 유한 차분 근사(finite differnce approximation)를 이용해 계산가능.
- 느리고 또한 근사치이지만 써내려가면서 하기에 가장 쉬운 방법
- analytic gradient를 어떻게 사용하지 그리고 계산하는 것까지 이야기했습니다.
- analytic gradient를 사용하는 것은 빠르고 정확합니다.
Problem: How to compute gradients?
analytic gradient를 사용하면 계산이 빠르고 정확하다 했는데, 그렇다면 gradient를 어떻게 구할 것인지 생각해봐야 합니다. 지금부터 왜 Computational graphs + Backpropagation 이 nice한지 부터 출발해보겠습니다.
- 우선 W에 대한 optimization이 까다로워지며, Gradient계산부터 복잡합니다.
- 2-layer neural networks Loss에 대해 W의 미분을 계산한다라고 했을때, W1, W2에 대한 gradient = loss를 W1, W2의 함수로 보고 각각에 대해 편미분을 합니다.
- 손으로 gradient를 직접 계산하려고 한다면, 매우 많은 수식과 종이가 필요합니다.
- 또한 Loss식(SVM 대신 cross-entropy loss를 사용)을 바꾸면, gradient를 새롭게 계산해야합니다.
- 매우 복잡한 모델에는 적합하지 않습니다.
Better Idea: Computational graphs + Backpropagation
따라서 아무리 복잡한 모델이 와도 컴퓨터가 계산할 수 있는 모듈을 만들고자 합니다. 이 때 모듈 계산을 도와주는 것이 computational graph 입니다.
임의의 복잡한 함수를 통해 어떻게 analytic gradient를 계산하는방법
- computational graph framework 사용합니다.
- computational graph를 이용하면 어떤 함수든지 표현할 수 있습니다.
그래프의 각 노드는 연산 단계를 나타냅니다. input이 $x$, $W$ 인 선형 classifier인 경우
- 처음 노드는 곱셈 노드입니다. 파라미터 $W$(weight)와 입력 데이터($x$)의 행렬 곱셈을 의미하고, 그 결과로 $s$(score) vector가 출력됩니다.
- 그 바로 다음 단계의 노드는 전단계에서 계산된 $s$ (score)을 이용해 loss 즉, $L_i$ 를 계산 하는 노드입니다. 이때 계산에 이용되는 손실함수는 hinge loss로 계산이 되어지는 것을 확인 할 수 있습니다.
- 또한 overfitting을 막기 위해 Regularization term을 더해주는 노드를 확인 할 수 있습니다.
- 그리고 최종 loss $L$ 은 regularization term과 data term의 합입니다.
computational graph를 사용해 함수를 표현하게 됨으로써, backpropagation을 사용할 수 있게 됩니다. backpropagation 은 gradient를 얻기위해 computational graph 내부의 모든 변수에 대해 chain rule을 재귀적으로 사용합니다.
computational graph는 매우 복잡한 함수를 이용하여 작업할때 아주 유용하게 사용됩니다. 예를들어 CNN은 가장 윗층(top)에 입력 이미지가 들어가고 아래(bottom)에는 loss가 있습니다. 입력 이미지는 loss function으로 가기까지 많은 layer를 거쳐 변형을 겪게 됩니다.
심지어 이렇게 복잡한 neural networks도 computational graph를 통해 손쉽게 계산이 가능합니다. 그렇다면 지금부터 backpropagation이 어떻게 동작하는지 간단한 예제를 살펴보겠습니다.
Backpropagation using computational graph
Forward Pass
$f(x,\ y,\ z)\ =\ (x\ +\ y)z$ 라는 함수가 있고 덧셈노드, 곱셈노드로 구성된 computational graph가 표현되어 있습니다. 그리고 $x=-2$, $y=5$, $z=-4$ 의 입력값을 넣어 각 연산 과정에서 계산된 값들을 적어두었습니다.
목표는 function $f$ 의 출력에 대한 어떤 변수의 gradient를 찾기 원합니다. 그렇기에 처음에는 항상 함수 $f$ 를 이용해서 computational graph로 나타내는 것입니다. 그리고 이 네트워크에 우리가 가지고 있는 값을 전달합니다.
- 우리가 찾기 원하는것은 $x$, $y$, $z$ 각각에 대한 $f$ 의 gradient입니다.
- 다음과 같이 정의한다면, $f(x,\ y,\ z)\ =\ (x\ +\ y)z\ =\ qz$ 라고 표현 가능합니다.
- $x\ +\ y$ 덧셈 노드를 $q$ 라고 정의
- $q\ *\ z$ 곱셈 노드를 $f$ 라고 정의
- 위 두 식에 각 변수에대해 편미분을 실행하면 다음과 같습니다.
- $x$ 변화량에 따른 $q$ 변화량, $\frac{∂q}{∂x}\ =\ 1$
- $y$ 변화량에 따른 $q$ 변화량, $\frac{∂q}{∂y}\ =\ 1$
- $q$ 변화량에 따른 $f$ 변화량, $\frac{∂f}{∂q}\ =\ z$
- $z$ 변화량에 따른 $f$ 변화량, $\frac{∂f}{∂z}\ =\ q$
Backward Pass
- backpropagation은 chain rule의 재귀적인 응용입니다.
- chiain rule에 의해 computational graph의 가장 끝, 뒤에서부터 gradient를 계산합니다.
- 우선 제일 뒷부분, 즉 출력 $f$ 에 대한 gradient를 계산하길 원합니다.
- 제일 뒷부분의 gradient는 $\frac{∂f}{∂f}\ =\ 1$ 입니다.
- 다음으로, 뒤로 이동해서 $z$ 에 대한 $f$ 의 gradient를 계산합니다.
- $z$ 에 대한 $f$ 의 gradient, 즉 $\frac{∂f}{∂z}$ 를 계산합니다.
- 해당 값은 이미 Forward Pass 과정에서 이미 $\frac{∂f}{∂z}\ =\ q$ 라고 계산해두었습니다.
- 따라서 $z$ 에 대한 $f$ 의 미분값은 $\frac{∂f}{∂z}\ =\ q\ =\ x\ +\ y\ =\ −2\ +\ 5\ =\ 3$ 가 됩니다.
- 다음으로, 뒤로 이동해서 $q$ 에 대한 $f$ 의 gradient를 계산합니다.
- $q$ 에 대한 $f$ 의 gradient, 즉 $\frac{∂f}{∂q}$ 를 계산합니다.
- 해당 값은 이미 Forward Pass 과정에서 이미 $\frac{∂f}{∂q}\ =\ z$ 라고 계산해두었습니다.
- 따라서 $z$ 에 대한 $f$ 의 미분값은 $\frac{∂f}{∂q}\ =\ z\ =\ -4$ 가 됩니다.
- 이제, 계산그래프 뒤로 이동해서 $y$ 에 대한 $f$ 의 gradient를 계산합니다.
- $y$ 에 대한 $f$ 의 gradient, 즉 $\frac{∂f}{∂y}$ 를 계산합니다.
- 하지만 여기의 $y$ 는 $f$ 와 바로 연결되어 있지 않습니다.
- 이때 이 값을 구할 때 chain rule 을 사용합니다.
- 즉, 해당 값을 $\frac{∂f}{∂y}\ =\ \frac{∂f}{∂q} \frac{∂q}{∂y}$ 로 나타낼 수 있으며, 이 것은 Forward Pass 에서 계산한 $\frac{∂f}{∂q}\ =\ z$, $\frac{∂q}{∂y}\ =\ 1$ 를 이용해 계산할 수 있습니다.
- 따라서 $y$ 에 대한 $f$ 의 미분값은 $\frac{∂f}{∂y}\ =\ \frac{∂f}{∂q} \frac{∂q}{∂y}\ =\ z\ ∗\ 1\ =\ −4$ 가 됩니다.
- 직관적으로 $y$ 가 $f$ 에 미치는 영향을 구하기 위한 것을 알 수 있습니다.
- 즉, $y$ 를 조금 바꿨을때 그것이 $q$ 에 대해 1만큼의 영향력을 미치는 것을 의미합니다.
- 또한 $f$ 에 대한 $q$ 의 영향력은 정확하게 -4 입니다.
- 결과적으로 이것들을 곱해보면 $f$ 에 대한 $y$ 의 영향력으로 $1\ *\ -4\ =\ -4$ 를 얻습니다.
- 이제, 계산그래프 뒤로 이동해서 $x$ 에 대한 $f$ 의 gradient를 계산합니다.
- $x$ 에 대한 $f$ 의 gradient, 즉 $\frac{∂f}{∂x}$ 를 계산합니다.
- 하지만 여기의 $x$ 는 $f$ 와 바로 연결되어 있지 않습니다.
- 이때 이 값을 구할 때 chain rule을 사용합니다.
- 즉, 해당 값을 $\frac{∂f}{∂x}\ =\ \frac{∂f}{∂q} \frac{∂q}{∂x}$ 로 나타낼 수 있으며, 이 것은 Forward Pass 에서 계산한 $\frac{∂f}{∂q}\ =\ z$, $\frac{∂q}{∂x}\ =\ 1$ 를 이용해 계산할 수 있습니다.
- 따라서 $x$ 에 대한 $f$ 의 미분값은 $\frac{∂f}{∂x}\ =\ \frac{∂f}{∂q} \frac{∂q}{∂x}\ =\ z\ ∗\ 1\ =\ −4$ 가 됩니다.
- 직관적으로 $x$ 가 $f$ 에 미치는 영향을 구하기 위한 것을 알 수 있습니다.
- 즉, $x$ 를 조금 바꿨을때 그것이 $q$ 에 대해 1만큼의 영향력을 미치는 것을 의미합니다.
- 또한 $f$ 에 대한 $q$ 의 영향력은 정확하게 -4 입니다.
- 결과적으로 이것들을 곱해보면 $f$ 에 대한 $x$ 의 영향력으로 $1\ *\ -4\ =\ -4$ 를 얻습니다.
Local gradient와 Upstream gradient
- 지금까지 Backpropagation을 통해 모든 지점에서의 gradient를 구해보았습니다.
- 각 단계에서의 과정을 살펴보면, 일정한 규칙 이 있다는 것을 알 수 있습니다.
- Computational graph의 모든 연산 단계에는 노드가 있습니다.
- $x$, $y$ 는 local input 입니다.
- 현재 지점에서의 노드의 출력에 대한 gradient를 local gradient 입니다.
- local input 인 $x$, $y$ 변화량에 따른 $z$ 의 변화량, 각각 $\frac{∂z}{∂x}$, $\frac{∂z}{∂y}$
- 다음 node에서 내려오는 gradient를 upstream gradient 입니다.
- 위 그림에서는 $\frac{∂L}{∂z}$
- Upstream gradient와 local gradient를 곱하면, chain rule에 따라 우리가 원하는 최종 출력에 대한 현재 지점에서의 gradient 를 구할 수 있습니다.
- $\frac{∂L}{∂x}\ =\ \frac{∂L}{∂z} \frac{∂z}{∂x}$, $\frac{∂L}{∂y}\ =\ \frac{∂L}{∂y} \frac{∂z}{∂y}$ 가 됩니다.
- 또한, 이 gradient는 다시 이전 노드의 Upstream gradient가 됩니다.
- 따라서, Backpropagation의 계산 은 recursive하게 구현이 가능 하기에, 우리가 원하는 모든 변수에 대한 gradient를 미적분계산 없이 효율적으로 구할 수 있습니다.
- 정리하면, 각 노드는 우리가 계산한 local gradient를 가지고 있으며, backpropagation을 통해 gradient을 얻습니다. 우리는 이것을 받아서 local gradient와 곱하기만 하면됩니다. 노드와 연결된, 노드 이외의 다른 어떤 값에 대하여 신경쓰지 않아도 됩니다.
Backpropagation using computational graph: Sigmoid
또 다른 예제로 $w$, $x$에 대한 함수 $f$ 는 $\frac{1}{1\ +\ e^{-(w0x0 + w1x1 + w2)}}$ 와 같습니다.
- 이전 예제와 마찬가지로, Computational graph를 그린 후, 차례대로 출력을 계산합니다.
- Sigmoid함수의 Forward Pass입니다. 위 슬라이드를 바탕으로 Backward Pass를 진행하겠습니다.
- 우선 최종 출력에서의 gradient를 구합니다.
- $\frac{∂f}{∂f}\ =\ 1$
- 노드들은 recursive하므로, 지금부터 upstream gradient $\frac{∂L}{∂f}$ 와 local gradient $\frac{∂f}{∂x}$ 로 생각하겠습니다.
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ 1$
- 그리고 local gradient는 위 슬라이드의 미분식에 따라 다음과 같습니다.
- $\frac{∂f}{∂x}\ =\ \frac{−1}{x^2}\ =\ \frac{−1}{1.37^2}$
- 따라서, 최종 출력에 대한 현재 지점에서의 gradient는 다음과 같습니다.
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (1)(\frac{−1}{1.37^2})\ =\ −0.53$
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ −0.53$
- 그리고 local gradient는 위 슬라이드의 미분식에 따라 다음과 같습니다.
- $\frac{∂f}{∂x}\ =\ 1$
- 따라서, 최종 출력에 대한 현재 지점에서의 gradient는 다음과 같습니다.
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (-0.53)(1)\ =\ −0.53$
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ −0.53$
- 그리고 local gradient는 위 슬라이드의 미분식에 따라 다음과 같습니다.
- $\frac{∂f}{∂x}\ =\ e^x\ =\ e^{-1}$
- 따라서, 최종 출력에 대한 현재 지점에서의 gradient는 다음과 같습니다.
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (-0.53)(e^{-1})\ =\ −0.20$
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ −0.20$
- 그리고 local gradient는 위 슬라이드의 미분식에 따라 다음과 같습니다.
- $\frac{∂f}{∂x}\ =\ a\ =\ -1$
- 따라서, 최종 출력에 대한 현재 지점에서의 gradient는 다음과 같습니다.
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (-0.20)(-1)\ =\ 0.20$
- 이제 덧셈 노드에 도달했습니다. 이번에는 위 아래로 나뉘어지는데, 각각에 대해 지금까지와 동일하게 수행하면 됩니다.
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ 0.20$
- 그리고 local gradient는 $+$ 연산자이므로, 다음과 같습니다.
- 윗쪽 노드
- $\frac{∂f}{∂x}\ =\ 1$
- 아래쪽 노드($w_2$)
- $\frac{∂f}{∂w_2}\ =\ 1$
- 그러므로 최종 출력에 대한 각 지점에서의 gradient는 다음과 같습니다.
- 윗쪽 노드
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (0.20)(1)\ =\ 0.20$
- 아래쪽 노드($w_2$)
- $\frac{∂L}{∂f} \frac{∂f}{∂w_2}\ =\ \frac{∂L}{∂w_2}\ =\ (0.20)(1)\ =\ 0.20$
- 이제 곱셈 노드에 도달했습니다. 이번에는 위 아래로 나뉘어지는데, 각각에 대해 지금까지와 동일하게 수행하면 됩니다.
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ 0.20$
- 그리고 local gradient는 $*$ 연산자이므로, 다음과 같습니다.
- 윗쪽 노드($w_0$)
- $\frac{∂f}{∂w_0}\ =\ x_0\ =\ -1$
- 아래쪽 노드($x_0$)
- $\frac{∂f}{∂x_0}\ =\ w_0\ =\ 2$
- 그러므로 최종 출력에 대한 각 지점에서의 gradient는 다음과 같습니다.
- 윗쪽 노드($w_0$)
- $\frac{∂L}{∂f} \frac{∂f}{∂w_0}\ =\ \frac{∂L}{∂w_0}\ =\ (0.20)(x_0)\ =\ (0.20)(-1)\ =\ -0.20$
- 아래쪽 노드($x_0$)
- $\frac{∂L}{∂f} \frac{∂f}{∂x_0}\ =\ \frac{∂L}{∂x_0}\ =\ (0.20)(w_0)\ =\ (0.20)(2)\ =\ 0.40$
위와 마찬가지로, $w1$ 과 $x1$ 도 구해보겠습니다.
- local gradient는 $*$ 연산자이므로, 다음과 같습니다.
- 윗쪽 노드($w_1$)
- $\frac{∂f}{∂w_0}\ =\ x_1\ =\ -2$
- 아래쪽 노드($x_1$)
- $\frac{∂f}{∂x_0}\ =\ w_1\ =\ -3$
- 그러므로 최종 출력에 대한 각 지점에서의 gradient는 다음과 같습니다.
- 윗쪽 노드($w_1$)
- $\frac{∂L}{∂f} \frac{∂f}{∂w_1}\ =\ \frac{∂L}{∂w_1}\ =\ (0.20)(x_1)\ =\ (0.20)(-2)\ =\ -0.40$
- 아래쪽 노드($x_1$)
- $\frac{∂L}{∂f} \frac{∂f}{∂x_1}\ =\ \frac{∂L}{∂x_1}\ =\ (0.20)(w_1)\ =\ (0.20)(-3)\ =\ -0.60$
지금까지의 과정을 정리하면 다음과 같습니다.
- 먼저, 식을 computational graph로 표현합니다.
- forward pass 계산합니다. (초록색 숫자들)
- backward gradient 계산합니다. (빨간색 숫자들)
- local gradient와 upstream gradient의 곱을 계속해서 구하면 됩니다.
- 즉 chain rule을 사용했습니다.
- 이때, local gradient는 각 연산에 따른 미분을 통해 구합니다.
Computational Graph can be easily expressed
- 위 슬라이드의 파란색 박스 부분 연산들은 사실 Sigmoid 함수를 풀어서 나타낸 것입니다.
- 따라서, Computational Graph로 생각하는 것의 장점은 Sigmoid와 같은 복잡한 함수도 computational graph를 작성하고 simple한 연산들의 조합으로 (local gradient를 구하는 과정을 반복해서) gradient를 구할 수 있습니다.
- 즉 computational graph를 만들 때, computational 노드에 대해 우리가 원하는 세분화된 정의를 할 수 있습니다.
- 다시 예를 들어 sigmoid 함수를 볼 때 gradient를 계산할 수 있습니다.
- 분석적으로 이를 살펴본다면 sigmoid의 gradient는 $(1 - sigma(x)) * sigma(x)$ 와 같습니다.
- 하나의 big 노드, sigmoid로 바꿀 수 있습니다.
- 입력이 1(초록색) 출력은 0.73을 갖습니다.
- gradient를 얻기 위해 하나의 완전한 sigmoid 노드를 이용한다면, (1-sigmoid(x)) * sigmoid(x)를 이용할 수 있습니다.
- x는 0.73이고 이 값을 위의 식에 대입하면 gradient로 0.2를 얻게 됩니다.
- upstream gradient인 1과 곱하면 sigmoid gate로 바꾸기 이전의 작은 노드들로 계산된 값과 정확히 같은 값을 얻을 수 있습니다.
- 만약, 아주 복잡한 식에 backpropagation을 적용하기 위해 gradient를 구해야 한다면, Computational graph를 작성하고 하나하나의 local gradient를 구한 후, chain rule을 적용하면 빠르게 문제해결이 가능합니다.
Patterns in gradient flow
- Backpropagation에서 backward pass 과정을 하다보면 일관된 패턴 을 발견할 수 있습니다.
- add gate : upstream gradient가 연결된 브랜치에 똑같이 같은 값으로 분배됩니다.(gradient distributor)
- max gate : 큰 값의 local gradient는 1, 작은 값의 local gradient는 0이 되므로 여러 개 중에 가장 큰 값에만 gradient를 전달합니다. 즉, gradient가 한쪽으로만 흐릅니다.(gradient router)
- mul gate : local gradient가 다른 반대 편 변수의 값이므로, upstream gradient를 받아 다른 변수 값으로 scaling 합니다. 즉, gradient가 switch되며, swithing + scaling의 역할을 합니다.(gradient switcher)
- copy gate : gradient가 단순히 더해집니다.(gradient adder)
Backprop Implementation: “Flat” code
위 과정들을 실제 Flat한 code 형태로 진행과정을 살펴보겠습니다.
def f(w0, x0, w1, x1, w2):
# Forward pass: Compute output
s0 = w0 * x0
s1 = w1 * x1
s2 = s0 * s1
s3 = s2 * w2
L = sigmoid(s3)
#Backward pass: Compute grads
grad_L = 1.0
grad_s3 = grad_L * (1 - L) * L
grad_w2 = grad_s3
grad_s2 = grad_s3
grad_s0 = grad_s2
grad_s1 = grad_s2
grad_w1 = grad_s1 * x1
grad_x1 = grad_s1 * w1
grad_w0 = grad_s0 * x0
grad_x0 = grad_s0 * w0
Vector derivatives
vector to vector의 경우에 derivative는 Jacobian입니다.
- Input, output이 모두 scala일때는, 미분도 scala로 정의가 됩니다.
- 가장 많이 보는 형태로, output은 scala, input은 vector인 경우입니다.
- 이런 경우, gradient는 input과 차원수가 같습니다.
- gradient의 n번째 component는 n번째 input이 조금 변화했을 때, 전체 output이 얼마나 변하는 지에 대한 값입니다.
- 다변수함수의 경우, Input, output이 모두 다변수인 경우에, 미분(derivative)은 jacobian으로 표현됩니다.
- 즉, jacobian의 (n,m) component는 n번째 input이 변화할 때, m번째 output의 변화 정도를 의미합니다.
Gradients for vectorized code
- 위 슬라이드는 Upstream gradient, local gradient, Downstream gradient의 관계를 보여줍니다.
- 위 슬라이드는 $x$, $y$, $z$ 가 모두 vector인 상황이며, 차원(dimension)이 각각 $D_X$, $D_Y$, $D_Z$ 입니다.
- Gradient는 input값과 차원이 같기 때문에, upstream gradient는 똑같이 $D_Z$ 차원, downstream gradient는 각각 $D_X$, $D_Y$ 차원 입니다.
- Local gradient를 구하는 과정을 살펴볼때, 다변수 input, 다변수 output이므로, jacobian 행렬의 형태가 됩니다.
- 따라서 chain rule로 downstream gradient를 구하는 과정은 행렬과 벡터의 곱으로 표현됩니다.
- [Downstream] = [Local] * [Upstream] = $(D_x\ ×\ D_z)$ * $D_z$ = $D_x$
- 이때, 행렬이 매우 커질 가능성이 있기때문에, 메모리에 모두 올리는 것이 바람직하지않을 수 있습니다.
- 따라서, 연산의 패턴을 파악한 후, 메모리를 절약할 수 있는 방향으로 코드를 작성하는 방법을 살펴보겠습니다.
- 즉 x, y, z는 scalar 대신에 vector를 가지는것이 일반적이며, vector인 경우에는 local gradient가 Jacobian Matrix가 됩니다.
- Loss L은 여전히 scalar입니다.
- Gradients of variables wrt loss have same dims as the original variable
- gradient는 Jacobian 행렬이 되며, 각 요소의 미분을 포함하는 행렬이 됩니다.(예를들어 x의 각 원소에 대해 z에 대한 미분을 포함하는)
정리하자면 지금까지는 scalar값에 대해 backpropagation을 진행해 gradient를 구하는 방법에 대해 살펴봤지만, 실제로 입력값으로 넣어주는 파라미터 $W$ 는 matrix로 되어 있기 때문에 입력값이 vector로 들어가는 경우가 많습니다.
- 입력이 vector로 들어가게 되어도 전체적인 흐름은 같고 gradient의 형태, Jacobian matrix(자코비안 행렬, 다변수 벡터 함수의 도함수 행렬)로 표현됩니다.
- 이때, 행렬의 크기가 매우 커질 가능성이 있기때문에, 연산의 패턴을 파악한 후, 메모리를 절약할 수 있는 방법을 살펴보겠습니다.
What is the Jacobian matrix?
The Jacobian matrix is a matrix composed of the first-order partial derivatives of a multivariable function.
The formula for the Jacobian matrix is the following:
Example of Jacobian matrix
Having seen the meaning of the Jacobian matrix, we are going to see step by step how to compute the Jacobian matrix of a multivariable function.
- Find the Jacobian matrix at the point (1,2) of the following function:
- $f(x,\ y) = (x^4\ +\ 3y^2x,\ 5y^2\ -\ 2xy\ +\ 1)$
- First of all, we calculate all the first-order partial derivatives of the function:
- $\frac{∂f_1}{∂x}\ =\ 4x^3\ +\ 3y^2$
- $\frac{∂f_1}{∂y}\ =\ 6yx$
- $\frac{∂f_2}{∂x}\ =\ -2y$
- $\frac{∂f_2}{∂y}\ =\ 10y\ -\ 2x$
정리하면 다음과 같이 나타낼 수 있습니다.
Once we have found the expression of the Jacobian matrix, we evaluate it at the point (1,2):
Vectorized operations
$4096×1$ 크기를 가진 vector가 입력값(input)으로 들어간다고 하고 $4096×1$ 크기의 vector가 결과값(output)으로 나온다고 할때, 요소별로(elementwise) 최대값을 취하는 예제입니다.
- Jacobian matrix의 사이즈는 얼마인가요?
- $4096\ ∗\ 4096$ size가 됩니다.
- 만약 100개의 input을 동시에 입력으로 갖는 배치를 이용해 작업할 수 있습니다.
- 노드를 더욱 효율적으로 만들지만 100배 커지게 만듭니다.
- 즉 Jacobian은 $4096000\ *\ 4096000$ 입니다.
- 정리하면 데이터의 차원이 커질때, Jacobian matrix의 크기가 제곱으로 커지게 됩니다. 이는 실용적이지 않으며, 실제로는 이렇게 큰 Jacobian을 계산할 필요가 없습니다.
Backprop with Vectors
또한 Jacobian matrix의 형태(구조)를 살펴볼 수 있습니다.
- 위 슬라이드를 보면, $x ∈ ℝ^4, y ∈ ℝ^4$ 이며, $y = max(0, x)$ 로 elementwise하게 max를 취합니다.
- forward pass는 함숫값을 취하면 되므로 $x = (1, −2, 3, −1)$ 일 때, $y = (1, 0, 3, 0)$ 입니다.
- Backwardpass를 생각해볼때, Upstream gradient를 계산합니다.
- upstream gradient인 $dL/dy$ 는 4차원입니다. (Loss는 scala, y는 4차원이므로, scala를 4차원의 y로 미분하면 4차원벡터.)
- $dL/dx$ 를 구하기위해, local gradient인 Jacobian 행렬을 구합니다.
- Jacobian($dy/dx$)의 off-diagonal(비대각)요소들은 전부 0값을 갖게 됩니다.
- 결론적으로, downstream gradient는 “jacobian의 대각요소가 1인 행”에 대해서는 upstream gradient값을 가져오고, 0인 행에 대해서는 0값을 갖습니다.
- 현재의 Jacobian은 매우 sparse한 형태입니다. 비대각요소들이 전부 0이므로, 코드를 짤 때 메모리에 모두 업로드하는 것은 매우 비효율적입니다.
- [Downstream]의 i번째 성분 : input 데이터(x)가 양수(+)인지, 음수(-)인지에 따라서 upstream데이터를 그대로 유지할 지, 0으로 truncate할 지 정해집니다.
- 즉 Jacobian matrix의 형태를 보면 대각성분을 제외한 나머지 성분들이 0이 되는 diagonal matrix가 되므로, Jacobian matrix를 모두 작성하지 않고 diagonal 성분만을 사용해서 gradient를 구할 수 있습니다.
- input의 n번째 차원의 element는 output의 n번째 차원의 element에만 영향을 미칩니다.
- 따라서, 각 차원의 element끼리만 영향을 주므로, Jacobian matrix에서 대각성분만 모두 남게되는 것 입니다.
- 즉 Jacobian행렬은 대각행렬이 됩니다.
- 출력에 대한 x의 영향에 대해서, 그리고 이 값을 사용하는 것에 대해서만 알면 됩니다.
Backprop with Matrices(or Tensors)
$x$, $y$, $z$ 가 모두 matrix or tensor인 상황에도 같은 개념으로 구현할 수 있습니다.
- Matrix를 모두 벡터로 펼쳐서 생각해본다면, Local gradient는 여전히 jacobian matrix를 갖습니다.
- 따라서, downstream gradient는 행렬-벡터간 곱셈이 됩니다.
- 여전히 jacobian 행렬의 메모리 문제를 안고있습니다.
- [Local]*[Upstream] 방식이 간략하게 표현하기 위해서, Local gate가 어떠한 방식으로 정의되는지가 중요합니다.
Example: Matrix Multiplication
Matrix Multiplication에 대한 예시를 살펴보겠습니다.
- Forward pass: 주어진 값으로 행렬간 곱셈을 계산합니다.
- Backward pass: Upstream gradient가 들어왔을 때, $dL/dy$ 는 y와 같은 차원이므로 $N * M$.
- Downstream gradient는 x와 같은 차원인 N * D를 가져야 합니다.
- Mul gate는 swap multipier 역할을 하기때문에, $dL/dx$ 는 w값과 upstream이 곱해진 값을 갖게됩니다.
- $\frac{∂L}{∂x} = (sth\ about\ w) * \frac{∂L}{∂y} = \frac{∂L}{∂y} * w^T$
- mulitplication operation의 특성상 전치.
일반적으로 어떻게 되는지, $\frac{dL}{dx_{1,1}}$ 을 먼저 채워보겠습니다.
- $y_{1,1} = x_{1,1}w_{1,1} + x_{1,2}w_{2,1} + x_{1,3}w_{3,1} = \sum_K x_{1,K}W_{K,1}$
- $\frac{dy_{1,1}}{dx_{1,1}} = w_{1,1} = 3$
- $y_{1,2} = x_{1,1}w_{1,2} + x_{1,2}w_{2,2} + x_{1,3}w_{3,2} = \sum_K x_{1,K}W_{K,2}$
- $\frac{dy_{1,2}}{dx_{1,1}} = w_{1,2} = 2$
- $y_{1,3} = x_{1,1}w_{1,3} + x_{1,2}w_{2,3} + x_{1,3}w_{3,3} = \sum_K x_{1,K}W_{K,3}$
- $\frac{dy_{1,3}}{dx_{1,1}} = w_{1,3} = 1$
- $y_{1,4} = x_{1,1}w_{1,4} + x_{1,2}w_{2,4} + x_{1,3}w_{3,4} = \sum_K x_{1,K}W_{K,4}$
- $\frac{dy_{1,4}}{dx_{1,1}} = w_{1,4} = -1$
- $y_{2,1} = x_{2,1}w_{1,1} + x_{2,2}w_{2,1} + x_{2,3}w_{3,1} = \sum_K x_{2,K}W_{K,1}$
- $\frac{dy_{2,1}}{dx_{1,1}} = 0$
- $y_{2,2} = x_{2,1}w_{1,2} + x_{2,2}w_{2,2} + x_{2,3}w_{3,2} = \sum_K x_{2,K}W_{K,2}$
- $\frac{dy_{2,1}}{dx_{1,1}} = 0$
- $y_{2,3} = x_{2,1}w_{1,3} + x_{2,2}w_{2,3} + x_{2,3}w_{3,3} = \sum_K x_{2,K}W_{K,3}$
- $\frac{dy_{2,1}}{dx_{1,1}} = 0$
- $y_{2,4} = x_{2,1}w_{1,4} + x_{2,2}w_{2,4} + x_{2,3}w_{3,4} = \sum_K x_{2,K}W_{K,4}$
- $\frac{dy_{2,1}}{dx_{1,1}} = 0$
$x_{1,1}$ 에 대한 local gradient가 완성되었습니다.
- $\frac{∂L}{∂x_{1,1}} \ = \frac{∂y}{∂x_{1,1}} \frac{∂L}{∂y} \ = [row\ 1\ of\ matrix\ w] \frac{∂L}{∂y{1,:}} \ = (w_{1,:}) (\frac{∂L}{∂y{1,:}})$
- $\frac{dy_{1,K}}{dx_{2,3}} = 0$
- $\frac{dy_{2,1}}{dx_{2,3}} = w_{3,1} = 3$
- $\frac{dy_{2,2}}{dx_{2,3}} = w_{3,2} = 2$
- $\frac{dy_{2,3}}{dx_{2,3}} = w_{3,3} = 1$
- $\frac{dy_{2,4}}{dx_{2,3}} = w_{3,4} = -2$
- $\frac{∂L}{∂x_{2,3}} = (w_{3,:}) (\frac{∂L}{∂y{2,:}})$
위와 같은 방식 그대로 계산한다면, 모든 값을 채울 수 있습니다.
- 또한 일반화된 식을 도출 할 수 있습니다.
- $\frac{∂L}{∂x_{i,j}} \ = \frac{∂y}{∂x_{i,j}} \frac{∂L}{∂y} \ = (w_{j,:}) (\frac{∂L}{∂y_{i,:}})$
- $dL/dx = (dL/dy)w^T$
- w에 대해서도 마찬가지로 도출됩니다.
- $dL/dw = x^T (dL/dy)$
- 위 슬라이드와 같이 영향력을 볼 수 있습니다.
- $y$ 의 어떤 부분이 $x$ 의 한 요소에 의해 영향을 받습니까?
- $x_{n, d}$ 는 전체 행 $y_{n, .}$ 에 영향을 미칩니다.
- $\frac{∂L}{∂x_{n,d}} = \sum_m \frac{∂L}{∂y_{n,m}} \frac{∂y_{n,m}}{∂x_{n,d}}$
- $x_{n,d} 는 y_{n,m} 에 얼마나 영향을 미칩니까?
- $w_{d,m}$
- $\frac{∂L}{∂x_{n,d}} = \sum_m \frac{∂L}{∂y_{n,m}} \frac{∂y_{n,m}}{∂x_{n,d}} = \sum_m \frac{∂L}{∂y_{n,m}} w_{d,m}$
A vectorized example
computational graph보다 구체적인 벡터화 된 예제를 살펴보겠습니다.
- Matrix에서 Backpropagation을 수행합니다.
- 출력에 대한 upstream gradient는 이전과 동일하게 1입니다.
- $\frac{∂f}{∂f}\ =\ 1$
- local gradient는 L2 연산에서 제곱을 하므로 q2의 미분인 2q입니다.
- $W⋅x$ 연산을 $q$ 라고 한다면, $f(q)\ =\ q^2_1\ +\ q^2_2\ +\ …\ +\ q^2_n$ 으로 나타낼 수 있고, 각 $q_i$ 에 대해 편미분하면 $\frac{∂f}{∂q}\ =\ 2q_i$ 로 나타낼 수 있습니다.
- 즉, gradient 는 $∇_qf\ =\ 2q$ 입니다.
- $\frac{∂f}{∂q}\ =\ 2q\ =\ 2 \begin{bmatrix}0.22 \ 0.26\end{bmatrix}\ =\ \begin{bmatrix}0.44 \ 0.52\end{bmatrix}$
- 따라서, 최종 출력에 대한 q에서의 gradient는 다음과 같습니다.
- $\frac{∂f}{∂f} \frac{∂f}{∂q}\ =\ \frac{∂f}{∂q}\ =\ 2q\ =\ \begin{bmatrix}0.44 \ 0.52\end{bmatrix}$
- 다음으로, 곱셈 노드의 $W$ 브랜치를 계산하겠습니다.
- $W$ 변화량에 따른 $f$ 의 변화량, 즉 $\frac{∂f}{∂W}$ 을 구해야합니다.
- chain rule 적용 위해 $W$ 변화량에 따른 $q$ 의 변화량, $\frac{∂q}{∂W}$ 을 알아야합니다.
- mul gate 는 swithching 역할을 하며, 각 행을 계산할때 다른 행은 관여하면 안되기때문에 $k$ 가 $i$ 일때는 1 을 곱해주고, 아닐때에는 0을 곱해줍니다.
- $\frac{∂q_k}{∂W_{i,j}}\ =\ 1_{k=i}x_j$
- 이것을 chain rule을 이용해서 $W$ 변화량에 따른 $f$ 의 변화량, $\frac{∂f}{∂W}$ 을 구할 수 있습니다.
- $\frac{∂f}{∂W_{i,j}}\ =\ \sum_k \frac{∂f}{∂q_k} \frac{∂q_k}{∂W_{i,j}}\ =\ \sum_k(2q_k)(1_{k=i}x_j)\ =\ 2q_ix_j$
- 그러므로 gradient는 $∇_Wf=2q⋅x^T$ 입니다.
즉 정리하면 다음과 같습니다.
\[\frac{∂f}{∂W}\ =\ \frac{∂f}{∂q}x^T\ =\ (2q)x^T\ =\ \begin{bmatrix}0.44 \\ 0.52\end{bmatrix} [0.2\ 0.4]\ =\ \begin{bmatrix}0.088\ 0.176 \\ 0.104\ 0.208\end{bmatrix}\]
- 다음으로, 곱셈 노드의 $x$ 브랜치를 계산하겠습니다.
- $x$ 변화량에 따른 $f$ 의 변화량, 즉 $\frac{∂f}{∂x}$ 을 구해야합니다.
- chain rule 적용 위해 $x$ 변화량에 따른 $q$ 의 변화량, $\frac{∂q}{∂x}$ 을 알아야합니다.
- $\frac{∂q_k}{∂x_i}\ =\ W_{k,i}$
- 이것을 chain rule을 이용해서 $x$ 변화량에 따른 $f$ 의 변화량, $\frac{∂f}{∂x}$ 을 구할 수 있습니다.
- $\frac{∂f}{∂x_i}\ =\ \sum_k \frac{∂f}{∂q_k} \frac{∂q_k}{∂x_i}\ =\ \sum_k(2q_k)(W_{k,i})\ =\ 2q_ix_j$
- 그러므로 gradient는 $∇_xf=W^T⋅q$ 입니다.
즉 정리하면 다음과 같습니다.
\[\frac{∂f}{∂X}\ =\ W^T\frac{∂f}{∂q}\ =\ W^T(2q) = \begin{bmatrix}0.1\ -0.3 \\ 0.5\ 0.8\end{bmatrix} \begin{bmatrix}0.44 \\ 0.52\end{bmatrix}\ =\ \begin{bmatrix}-0.112 \\ 0.636\end{bmatrix}\]
지금까지의 중요한 점은 다음과 같습니다.
- 벡터의 gradient는 항상 원본 벡터의 사이즈와 같습니다.
- 항상 gradient의 shape을 확인해야하며, gradient의 값을 계산했을 때 shape가 다른 결과가 나올 수 있기에 shape은 중요합니다.
- gradient의 각 요소는 함수의 최종 출력에 얼마나 특별한 영향을 미치는지 의미합니다.
Modularized implementation: forward / backward API
- computational graph에서의 과정을 실제 코드로 모듈화 된 구현이 가능합니다.
- 각 노드를 local하게 보았고 upstream gradient와 함께 chain rule을 이용해서 local gradient를 계산했습니다.
- 이것을 forward pass, 그리고 backward pass의 API로 생각할 수 있습니다.
- forward pass에서는 노드의 출력을 계산하는 함수를 구현하고, backward pass에서는 gradient를 계산합니다.
- 또한 위치적으로 정렬 된 순서로 수행하기를 원합니다.
- 노드를 처리하기 전에 이 전에 노드로 들어오는 모든 입력을 처리합니다.
- 그리고 backward로 보면, 역순서로 모든 게이트를 통과한 다음에 게이트 각각을 거꾸로 호출합니다.
- 또한 특정 게이트, 곱셈 게이트를 살펴보면 forward()와 backward()로 구성되어있습니다.
- forward pass
- x, y를 입력으로 받고 z를 리턴합니다.
- 중요한 것은 값을 저장(cache)해야합니다. backward pass에서 더 많이 사용하기 때문입니다.
- backward pass
- 입력으로 upstream gradient인 dz를 받고 출력으로 입력인 x와 y에 gradient를 전달합니다.
- 여기서 출력은 dx와 dy입니다.
- chain rule을 사용하여 upstream gradient 값을 이용해 다른 브랜치의 값과 곱합니다.
Summary so far…
- 신경망은 정말로 크고 복잡합니다.
- 그렇기에 모든 파라미터에 대해 gradient를 손으로 구하고 써내려가는 것은 비현실적입니다.
- 그래서 gradient를 계산하기 위해서 backpropagation을 사용하는 방법에 대해서 언급했고, 신경망의 핵심 기술입니다.
- 그리고 computational graph에서 chain rule을 재귀적으로 적용한 것입니다.
- 또한 각각의 노드에 대한 그래프 구조에 대해 어떻게 forward, backward API 구현으로 나타내는지에 대해서 이야기 했었습니다.
- forward pass에서는 나중에 gradient를 계산할 때 backward pass에서 chain rule에서 사용하기 위해 연산 결과를 계산하고 결과를 저장합니다.
- backward pass에서는 upstream gradient와 저장한 값들을 곱해 각 노드의 input에 대한 gradient를 구하고, 연결된 이전 노드로 통과시킵니다.
Fully-connected Neural Networks
Neural networks: the original linear classifier
- Neural Network를 알아보기 전에 linear classifier부터 짚고 넘어가겟습니다.
- linear classifier는 단순히 $W$ (weight) 와 input $x$ 를 곱해주는 $f=Wx$ 의 함수를 통해 클래스의 score를 계산했습니다.
- 하지만 다음과 같은 단점이 있었습니다.
- Geometric Viewpoint
- Linear classifiers로는 이런 형식의 데이터는 좋은 성능으로 분류하는 것이 불가능합니다.
- Visual Viewpoint
- 클래스 당 하나의 템플릿만 가질 수 있기때문에, 실제로 같은 class여도 정해진 템플릿과 비슷한 생김새가 아니라면 그 class로 인식될 수 없게 됩니다.
- 즉 정리해보면, Linear classifier의 문제점은 한 class 내에 다양한 특징들이 존재할 수 있지만, 모든 것들을 평균화시킨다는 점이 있습니다.
- 앞서 CIFAR-10 예시를 보면 horse class에 대한 $W$ 행을 시각화한 이미지를 확인하면, 말의 머리가 양쪽으로 있는 것을 확인할 수 있습니다. 이러한 점이 하나의 linear classifier 이 가지는 문제가 드러나는 모습임을 알 수 있습니다.
- 또한 car class 분류에 대한 $W$ 를 시각화 한 것을 보면 빨간색 차의 앞모습으로 보이는 물체만 있습니다. 빨간색이 아닌, 다른 색깔의 차 혹은 차의 앞모습이 아닌 옆모습의 이미지는 잘 분류할 수 없다는 문제가 있습니다.
만약, 하나의 class에 대해 하나의 $W$ (템플릿)이 아니라 여러 개의 템플릿이 존재하면 다양한 이미지에 대해서 분류를 해낼 수 있습니다. 예를들어, car class 에 대한 템플릿으로 빨간색 차, 노란색 차, 차의 옆모습 등등 의 다양한 템플릿을 생성하는 모습을 생각해볼 수 있습니다.
그러므로 Linear Classifier 모델의 성능이 나쁜 것을, 여러 개의 Layers를 사용한 neural networks 를 사용해서 해결하고자 합니다.
Neural networks: 2-layers also called fully connected network
- 지금까지의 linear score function은 $f(x, W) = Wx$
- 2-layer Neural Network부터 $f(x;W_1, W_2) = W_2max(0, W_1x)$ 형식으로 변합니다.
- $W_1x$: 한 번 matrix-vector multiplication(행렬-벡터의 곱셈)
- $max(0, W_1x)$: 0과 elementwise하게 max
- $W_1x$ 의 음수 값은 0으로 대체(truncate)
- H x 1 vector
- $W_2max(0, W_1x)$: 다시 matrix-vector multiplication.
- 2-layer Neural Network의 형태: 선형(비선형(선형 변환(x))
- 선형과 비선형의 합성함수를 계속 반복하여 적용한다.
- 2개의 weight, 1개의 nonlinear activation을 이용합니다.
- Bias를 집어넣으면, $f(x;W_1,b1,\ W_2,b_2) = W_2max(0, W_1x + b_1) + b_2$
- $(W1)_{i,j}$ 성분을 살펴보겠습니다.
- $h_1 = W_1x$
- $h_i= ∑k(W_1){i,k} x_k$
- $(W_1)_{i, j}$ 성분이 $x_j$ 를 $h_i$ 로 만드는 데에 주요한 역할을 합니다.(contribution)
- $h_i$ 는 $x_1 ~ x_D$ 이 각각 $(W_1)i$ 와 곱해져서 구성되는데, $h_i$ 를 구성하기 위해 $x_j$ 가 contribute한 만큼, $(W_1){i,j}$ 도 contribute합니다.
- 즉, $(W_1)_{i,j}$, 성분 = $h_i$ 에 대해서 $x_j$ 가 얼마만큼 contribute하는지 결정합니다.
- $x$ 의 각 component가 $h$ 의 각 component로 contribute 합니다.($W_1$이 꽉 차있는 matrix이기 때문)
- 결과적으로, Fully-Connected NN : 이전 레이어의 모든 벡터들이 다음 레이어의 모든 벡터에 영향을 미칩니다.
Template of Neural Networks
- $W$ 의 row 하나(class 하나에 대응됨)를 그림처럼 나타낸 것이 template 입니다.
- Linear classifier 일때는 클래스 하나당 하나의 template이 만들어지며, 이는 $W_1$ 하나만 있는 것으로 생각 할 수 있습니다.
- 입력과 직접적으로 연결되어 있으므로, 이 template을 공식화 할 수 있기 때문에 해석가능합니다.
- Neural Networks부터는 layer가 여러 개 입니다.
- $h$ 하나를 만들기 위해 템플릿($W_1$)을 한번 매칭한 후, 새로 만들어진 템플릿($W_2$)을 다시 한 번 조합합니다.
- 즉, 2-Layer Neural Network 에서는 카테고리당 1개의 템플릿만을 만드는 것이 아니고, 첫 번째 레이어에서 각각의 Weigth 마다 템플릿을 만들어 내고, 두번째 레이어에서는 이전에 만든 템플릿들을 재조합해서 class score를 만들어 냅니다.
- 이처럼 1개의 템플릿이 아닌 여러 개의 템플릿의 조합으로 class를 표현한다고 해서 이 방식을 ‘Distributed Representation’이라고 부르기도 합니다.
- 신경망 모델에서는 여러 개의 템플릿을 만들기에 중복된 정보를 가진 템플릿을 만들 수도 있지만, Network Pruning과 같은 기술들로 어느 정도는 해소할 수 있다고 합니다.
- 레이어가 두 개가 되면, horse에 대한 템플릿이 여러개일 때, 조합할 수 있게 됩니다.
- 하지만 각자 template에 대한 해석은 쉽지 않아집니다.
- 위 그림은 Layer가 6개인 6-layer Neural Networks를 그림으로 나타낸 것입니다.
- 세로(=너비)는 input의 차원(dimension)에 대응합니다.
- 보통 FC를 만들 때, H1 = H2 = H3 = H4 = H5를 같게 하는 경우가 많습니다. 혹은 1/2씩 곱해나가기도 합니다. (ex. H1=256, H2=128, H3=64, H4=32, H5=16)
- 위의 예시에서 $f = W_2max(0, W_1x)$ 이 이상으로 표현했습니다.
- $max(0, W_1x)$ 는 ReLu 라고 불리는 자주 쓰이는 activation function입니다.
- 따라서 여러 개의 $W (W_1, W_2, …, W_n)$ 이 학습되고, 이 각각의 $W$ 들은 각각의 중간 과정에서 하나의 class에 대한 다른 템플릿을 만들어내는 효과를 가지게 됩니다.
- 또한 linear classifier의 문제를 해결하기 위해, 여러 개의 레이어를 사용하여 classifier에 비선형성을 부여하여, 선형 문제에서는 해결할 수 없었던 문제들에 들을 해결할 수 있게 됩니다.
- $f = W(h(x))$ 의 형태에서 내부의 함수 $h(x)$ 는 비선형 함수여야만 합니다.
위와같이 layer를 깊게 쌓은 것을 Deep neural network 라고 지칭합니다.
- 정리하면 다음과 같습니다.
- Neural network는 지금까지 다루어온 Linear score function을 여러 개 쌓은 형태입니다.
- 입력 x를 받아 첫번째 W와 곱하고 출력한 일종의 score를 다시 받아 W를 곱하고 또 출력하고 이런식으로 반복하는 형태입니다.
- 하나의 Layer가 아니라 여러개의 Layer로 구성됩니다.
- 이때, 각각의 출력에 비선형함수를 사용해서 비선형성을 추가해줍니다.
- 만약 Neural network내에 activation function을 쓰지않으면 $f = W_1W_2x$ 형태가 됩니다.
- 즉, x에 대한 선형변환만 될 뿐입니다.
- 선형변환은 decision boundary가 linear하게 그어지므로, 충분히 복잡한 함수를 표현할 수 없습니다.
- 즉, ReLU와 같은 activation function이 있어야 $f(x; W)$ 가 $x$ 의 함수로서 더 다양한 함수를 표현할 수 있습니다.
Biologocal vs. artificual neurons
신경망이 무엇인지 함수로써 살펴보았는데, 보통 Neural Network를 설명할때는 신경망에 대한 생물학적 영감에 대해, 인간 두뇌의 신경망과 연관지어 설명합니다.
- dendrite(수상돌기)를 통해 특정 신호가 입력되게 됩니다.
- 들어온 신호들은 cell body 에서 종합되어 합쳐집니다.
- 이후, 합쳐진 신호는 axon 을 통해 다음 뉴런으로 전달되게 됩니다.
- 이때 받은 자극을 그대로 전달하지 않고, Input된 자극을 nonlinear하게 변환해서 전달해줍니다.
이러한 구조는 앞에서 다뤘던 computational graph의 각 노드에서 일어나는 과정과 굉장히 유사합니다.
- 이 현상을 수학적으로 모델링 한 것이 현재의 neural network 입니다.
- input된 자극을 $x_0$ 라고 할 때, 시냅스를 통해 신호가 조금 바뀐 형태로 $w_0 * x_0$ 가 출력되며, cell body에서는 모은 정보를 종합하고, activation function인 $f$ 를 적용하여 다른 뉴런으로 전달해줍니다.
- 여기서 주의할 점은 인공신경망 neural network은 인간의 실제 두뇌 신경망과는 완전히 같지는 않다는 점입니다. 실제 인간의 두뇌는 훨씬 더 복잡한 비선형 시스템입니다.
Activation functions
- 실제 사용될 수 있는 많은 다른 종류의 activation function이 존재합니다.
- ReLu 가 가장 일반적으로 사용됩니다.
Neural networks: Architectures
- Neural network에서 위와 같이 모든 노드의 출력이 모든 다음 노드의 입력으로 연결된 layer 형태를 Fully connected layer라고 합니다.
Example feed-forward computation of a neural network
# forward-pass of a 3-layer neural network:
f = lambda x: 1.0/(1.0 + np.exp(-x)) # activation function (use sigmoid)
x = np.random.randn(3, 1) # random input vector of three numbers (3x1)
h1 = f(np.dot(W1, x) + b1) # calculate first hidden later activations (4x1)
h2 = f(np.dot(W2, h1) + b2) # calculate second hidden later activations (4x1)
out = np.dot(W3, h2) + b3 # output neuron (1x1)
Summary
해당 게시물은 Standford 2017 CS231n 강의와 2022년 슬라이드를 바탕으로 작성되었습니다.
Lecture3 Recap
함수 $f$ 를 통해 어떻게 classifier를 정의하는지 다뤘습니다.
- weights W를 파라미터로 가집니다.
- 함수 f의 입력(input)은 데이터 $x$,
- 함수 f의 출력(output)은 분류하고자 하는 클래스들에 대한 score vector
또한 loss function을 정의했습니다.
- classifier 얼마나 badness한지 점수로 나타내기위해 Full loss 항(term)을 정의.
- Full loss = data loss + regularization
- regularization은 얼마나 우리의 모델이 단순한지, 더 나은 일반화를 위해 적합한 모델을 가지고 있는지를 표현.
또한 최적의 loss를 갖게 하는 파라미터 $W$ 를 찾고 싶어서, loss function의 W에 관한 gradient를 찾았습니다
- Gradient Descent를 통해 optimization을 하여 최적의 $W$ 를 찾습니다.
- gradient가 음수인 방향, 즉 경사가 하강하는 방향을 반복적으로 구해 아래로 나아가 가장 낮은 loss에 찾습니다.
또한 gradient를 구하는 다른 방법에 대해서도 이야기했습니다.
- 수학적으로 유한 차분 근사(finite differnce approximation)를 이용해 계산가능.
- 느리고 또한 근사치이지만 써내려가면서 하기에 가장 쉬운 방법
- analytic gradient를 어떻게 사용하지 그리고 계산하는 것까지 이야기했습니다.
- analytic gradient를 사용하는 것은 빠르고 정확합니다.
Problem: How to compute gradients?
analytic gradient를 사용하면 계산이 빠르고 정확하다 했는데, 그렇다면 gradient를 어떻게 구할 것인지 생각해봐야 합니다. 지금부터 왜 Computational graphs + Backpropagation 이 nice한지 부터 출발해보겠습니다.
- 우선 W에 대한 optimization이 까다로워지며, Gradient계산부터 복잡합니다.
- 2-layer neural networks Loss에 대해 W의 미분을 계산한다라고 했을때, W1, W2에 대한 gradient = loss를 W1, W2의 함수로 보고 각각에 대해 편미분을 합니다.
- 손으로 gradient를 직접 계산하려고 한다면, 매우 많은 수식과 종이가 필요합니다.
- 또한 Loss식(SVM 대신 cross-entropy loss를 사용)을 바꾸면, gradient를 새롭게 계산해야합니다.
- 매우 복잡한 모델에는 적합하지 않습니다.
Better Idea: Computational graphs + Backpropagation
따라서 아무리 복잡한 모델이 와도 컴퓨터가 계산할 수 있는 모듈을 만들고자 합니다. 이 때 모듈 계산을 도와주는 것이 computational graph 입니다.
임의의 복잡한 함수를 통해 어떻게 analytic gradient를 계산하는방법
- computational graph framework 사용합니다.
- computational graph를 이용하면 어떤 함수든지 표현할 수 있습니다.
그래프의 각 노드는 연산 단계를 나타냅니다. input이 $x$, $W$ 인 선형 classifier인 경우
- 처음 노드는 곱셈 노드입니다. 파라미터 $W$(weight)와 입력 데이터($x$)의 행렬 곱셈을 의미하고, 그 결과로 $s$(score) vector가 출력됩니다.
- 그 바로 다음 단계의 노드는 전단계에서 계산된 $s$ (score)을 이용해 loss 즉, $L_i$ 를 계산 하는 노드입니다. 이때 계산에 이용되는 손실함수는 hinge loss로 계산이 되어지는 것을 확인 할 수 있습니다.
- 또한 overfitting을 막기 위해 Regularization term을 더해주는 노드를 확인 할 수 있습니다.
- 그리고 최종 loss $L$ 은 regularization term과 data term의 합입니다.
computational graph를 사용해 함수를 표현하게 됨으로써, backpropagation을 사용할 수 있게 됩니다. backpropagation 은 gradient를 얻기위해 computational graph 내부의 모든 변수에 대해 chain rule을 재귀적으로 사용합니다.
computational graph는 매우 복잡한 함수를 이용하여 작업할때 아주 유용하게 사용됩니다. 예를들어 CNN은 가장 윗층(top)에 입력 이미지가 들어가고 아래(bottom)에는 loss가 있습니다. 입력 이미지는 loss function으로 가기까지 많은 layer를 거쳐 변형을 겪게 됩니다.
심지어 이렇게 복잡한 neural networks도 computational graph를 통해 손쉽게 계산이 가능합니다. 그렇다면 지금부터 backpropagation이 어떻게 동작하는지 간단한 예제를 살펴보겠습니다.
Backpropagation using computational graph
Forward Pass
$f(x,\ y,\ z)\ =\ (x\ +\ y)z$ 라는 함수가 있고 덧셈노드, 곱셈노드로 구성된 computational graph가 표현되어 있습니다. 그리고 $x=-2$, $y=5$, $z=-4$ 의 입력값을 넣어 각 연산 과정에서 계산된 값들을 적어두었습니다.
목표는 function $f$ 의 출력에 대한 어떤 변수의 gradient를 찾기 원합니다. 그렇기에 처음에는 항상 함수 $f$ 를 이용해서 computational graph로 나타내는 것입니다. 그리고 이 네트워크에 우리가 가지고 있는 값을 전달합니다.
- 우리가 찾기 원하는것은 $x$, $y$, $z$ 각각에 대한 $f$ 의 gradient입니다.
- 다음과 같이 정의한다면, $f(x,\ y,\ z)\ =\ (x\ +\ y)z\ =\ qz$ 라고 표현 가능합니다.
- $x\ +\ y$ 덧셈 노드를 $q$ 라고 정의
- $q\ *\ z$ 곱셈 노드를 $f$ 라고 정의
- 위 두 식에 각 변수에대해 편미분을 실행하면 다음과 같습니다.
- $x$ 변화량에 따른 $q$ 변화량, $\frac{∂q}{∂x}\ =\ 1$
- $y$ 변화량에 따른 $q$ 변화량, $\frac{∂q}{∂y}\ =\ 1$
- $q$ 변화량에 따른 $f$ 변화량, $\frac{∂f}{∂q}\ =\ z$
- $z$ 변화량에 따른 $f$ 변화량, $\frac{∂f}{∂z}\ =\ q$
Backward Pass
- backpropagation은 chain rule의 재귀적인 응용입니다.
- chiain rule에 의해 computational graph의 가장 끝, 뒤에서부터 gradient를 계산합니다.
- 우선 제일 뒷부분, 즉 출력 $f$ 에 대한 gradient를 계산하길 원합니다.
- 제일 뒷부분의 gradient는 $\frac{∂f}{∂f}\ =\ 1$ 입니다.
- 다음으로, 뒤로 이동해서 $z$ 에 대한 $f$ 의 gradient를 계산합니다.
- $z$ 에 대한 $f$ 의 gradient, 즉 $\frac{∂f}{∂z}$ 를 계산합니다.
- 해당 값은 이미 Forward Pass 과정에서 이미 $\frac{∂f}{∂z}\ =\ q$ 라고 계산해두었습니다.
- 따라서 $z$ 에 대한 $f$ 의 미분값은 $\frac{∂f}{∂z}\ =\ q\ =\ x\ +\ y\ =\ −2\ +\ 5\ =\ 3$ 가 됩니다.
- 다음으로, 뒤로 이동해서 $q$ 에 대한 $f$ 의 gradient를 계산합니다.
- $q$ 에 대한 $f$ 의 gradient, 즉 $\frac{∂f}{∂q}$ 를 계산합니다.
- 해당 값은 이미 Forward Pass 과정에서 이미 $\frac{∂f}{∂q}\ =\ z$ 라고 계산해두었습니다.
- 따라서 $z$ 에 대한 $f$ 의 미분값은 $\frac{∂f}{∂q}\ =\ z\ =\ -4$ 가 됩니다.
- 이제, 계산그래프 뒤로 이동해서 $y$ 에 대한 $f$ 의 gradient를 계산합니다.
- $y$ 에 대한 $f$ 의 gradient, 즉 $\frac{∂f}{∂y}$ 를 계산합니다.
- 하지만 여기의 $y$ 는 $f$ 와 바로 연결되어 있지 않습니다.
- 이때 이 값을 구할 때 chain rule 을 사용합니다.
- 즉, 해당 값을 $\frac{∂f}{∂y}\ =\ \frac{∂f}{∂q} \frac{∂q}{∂y}$ 로 나타낼 수 있으며, 이 것은 Forward Pass 에서 계산한 $\frac{∂f}{∂q}\ =\ z$, $\frac{∂q}{∂y}\ =\ 1$ 를 이용해 계산할 수 있습니다.
- 따라서 $y$ 에 대한 $f$ 의 미분값은 $\frac{∂f}{∂y}\ =\ \frac{∂f}{∂q} \frac{∂q}{∂y}\ =\ z\ ∗\ 1\ =\ −4$ 가 됩니다.
- 직관적으로 $y$ 가 $f$ 에 미치는 영향을 구하기 위한 것을 알 수 있습니다.
- 즉, $y$ 를 조금 바꿨을때 그것이 $q$ 에 대해 1만큼의 영향력을 미치는 것을 의미합니다.
- 또한 $f$ 에 대한 $q$ 의 영향력은 정확하게 -4 입니다.
- 결과적으로 이것들을 곱해보면 $f$ 에 대한 $y$ 의 영향력으로 $1\ *\ -4\ =\ -4$ 를 얻습니다.
- 이제, 계산그래프 뒤로 이동해서 $x$ 에 대한 $f$ 의 gradient를 계산합니다.
- $x$ 에 대한 $f$ 의 gradient, 즉 $\frac{∂f}{∂x}$ 를 계산합니다.
- 하지만 여기의 $x$ 는 $f$ 와 바로 연결되어 있지 않습니다.
- 이때 이 값을 구할 때 chain rule을 사용합니다.
- 즉, 해당 값을 $\frac{∂f}{∂x}\ =\ \frac{∂f}{∂q} \frac{∂q}{∂x}$ 로 나타낼 수 있으며, 이 것은 Forward Pass 에서 계산한 $\frac{∂f}{∂q}\ =\ z$, $\frac{∂q}{∂x}\ =\ 1$ 를 이용해 계산할 수 있습니다.
- 따라서 $x$ 에 대한 $f$ 의 미분값은 $\frac{∂f}{∂x}\ =\ \frac{∂f}{∂q} \frac{∂q}{∂x}\ =\ z\ ∗\ 1\ =\ −4$ 가 됩니다.
- 직관적으로 $x$ 가 $f$ 에 미치는 영향을 구하기 위한 것을 알 수 있습니다.
- 즉, $x$ 를 조금 바꿨을때 그것이 $q$ 에 대해 1만큼의 영향력을 미치는 것을 의미합니다.
- 또한 $f$ 에 대한 $q$ 의 영향력은 정확하게 -4 입니다.
- 결과적으로 이것들을 곱해보면 $f$ 에 대한 $x$ 의 영향력으로 $1\ *\ -4\ =\ -4$ 를 얻습니다.
Local gradient와 Upstream gradient
- 지금까지 Backpropagation을 통해 모든 지점에서의 gradient를 구해보았습니다.
- 각 단계에서의 과정을 살펴보면, 일정한 규칙 이 있다는 것을 알 수 있습니다.
- Computational graph의 모든 연산 단계에는 노드가 있습니다.
- $x$, $y$ 는 local input 입니다.
- 현재 지점에서의 노드의 출력에 대한 gradient를 local gradient 입니다.
- local input 인 $x$, $y$ 변화량에 따른 $z$ 의 변화량, 각각 $\frac{∂z}{∂x}$, $\frac{∂z}{∂y}$
- 다음 node에서 내려오는 gradient를 upstream gradient 입니다.
- 위 그림에서는 $\frac{∂L}{∂z}$
- Upstream gradient와 local gradient를 곱하면, chain rule에 따라 우리가 원하는 최종 출력에 대한 현재 지점에서의 gradient 를 구할 수 있습니다.
- $\frac{∂L}{∂x}\ =\ \frac{∂L}{∂z} \frac{∂z}{∂x}$, $\frac{∂L}{∂y}\ =\ \frac{∂L}{∂y} \frac{∂z}{∂y}$ 가 됩니다.
- 또한, 이 gradient는 다시 이전 노드의 Upstream gradient가 됩니다.
- 따라서, Backpropagation의 계산 은 recursive하게 구현이 가능 하기에, 우리가 원하는 모든 변수에 대한 gradient를 미적분계산 없이 효율적으로 구할 수 있습니다.
- 정리하면, 각 노드는 우리가 계산한 local gradient를 가지고 있으며, backpropagation을 통해 gradient을 얻습니다. 우리는 이것을 받아서 local gradient와 곱하기만 하면됩니다. 노드와 연결된, 노드 이외의 다른 어떤 값에 대하여 신경쓰지 않아도 됩니다.
Backpropagation using computational graph: Sigmoid
또 다른 예제로 $w$, $x$에 대한 함수 $f$ 는 $\frac{1}{1\ +\ e^{-(w0x0 + w1x1 + w2)}}$ 와 같습니다.
- 이전 예제와 마찬가지로, Computational graph를 그린 후, 차례대로 출력을 계산합니다.
- Sigmoid함수의 Forward Pass입니다. 위 슬라이드를 바탕으로 Backward Pass를 진행하겠습니다.
- 우선 최종 출력에서의 gradient를 구합니다.
- $\frac{∂f}{∂f}\ =\ 1$
- 노드들은 recursive하므로, 지금부터 upstream gradient $\frac{∂L}{∂f}$ 와 local gradient $\frac{∂f}{∂x}$ 로 생각하겠습니다.
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ 1$
- 그리고 local gradient는 위 슬라이드의 미분식에 따라 다음과 같습니다.
- $\frac{∂f}{∂x}\ =\ \frac{−1}{x^2}\ =\ \frac{−1}{1.37^2}$
- 따라서, 최종 출력에 대한 현재 지점에서의 gradient는 다음과 같습니다.
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (1)(\frac{−1}{1.37^2})\ =\ −0.53$
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ −0.53$
- 그리고 local gradient는 위 슬라이드의 미분식에 따라 다음과 같습니다.
- $\frac{∂f}{∂x}\ =\ 1$
- 따라서, 최종 출력에 대한 현재 지점에서의 gradient는 다음과 같습니다.
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (-0.53)(1)\ =\ −0.53$
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ −0.53$
- 그리고 local gradient는 위 슬라이드의 미분식에 따라 다음과 같습니다.
- $\frac{∂f}{∂x}\ =\ e^x\ =\ e^{-1}$
- 따라서, 최종 출력에 대한 현재 지점에서의 gradient는 다음과 같습니다.
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (-0.53)(e^{-1})\ =\ −0.20$
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ −0.20$
- 그리고 local gradient는 위 슬라이드의 미분식에 따라 다음과 같습니다.
- $\frac{∂f}{∂x}\ =\ a\ =\ -1$
- 따라서, 최종 출력에 대한 현재 지점에서의 gradient는 다음과 같습니다.
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (-0.20)(-1)\ =\ 0.20$
- 이제 덧셈 노드에 도달했습니다. 이번에는 위 아래로 나뉘어지는데, 각각에 대해 지금까지와 동일하게 수행하면 됩니다.
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ 0.20$
- 그리고 local gradient는 $+$ 연산자이므로, 다음과 같습니다.
- 윗쪽 노드
- $\frac{∂f}{∂x}\ =\ 1$
- 아래쪽 노드($w_2$)
- $\frac{∂f}{∂w_2}\ =\ 1$
- 윗쪽 노드
- 그러므로 최종 출력에 대한 각 지점에서의 gradient는 다음과 같습니다.
- 윗쪽 노드
- $\frac{∂L}{∂f} \frac{∂f}{∂x}\ =\ \frac{∂L}{∂x}\ =\ (0.20)(1)\ =\ 0.20$
- 아래쪽 노드($w_2$)
- $\frac{∂L}{∂f} \frac{∂f}{∂w_2}\ =\ \frac{∂L}{∂w_2}\ =\ (0.20)(1)\ =\ 0.20$
- 윗쪽 노드
- 이제 곱셈 노드에 도달했습니다. 이번에는 위 아래로 나뉘어지는데, 각각에 대해 지금까지와 동일하게 수행하면 됩니다.
- 앞에서 구한 gradient는 현재 지점에서의 upstream gradient가 되므로, 다음과 같습니다.
- $\frac{∂L}{∂f}\ =\ 0.20$
- 그리고 local gradient는 $*$ 연산자이므로, 다음과 같습니다.
- 윗쪽 노드($w_0$)
- $\frac{∂f}{∂w_0}\ =\ x_0\ =\ -1$
- 아래쪽 노드($x_0$)
- $\frac{∂f}{∂x_0}\ =\ w_0\ =\ 2$
- 윗쪽 노드($w_0$)
- 그러므로 최종 출력에 대한 각 지점에서의 gradient는 다음과 같습니다.
- 윗쪽 노드($w_0$)
- $\frac{∂L}{∂f} \frac{∂f}{∂w_0}\ =\ \frac{∂L}{∂w_0}\ =\ (0.20)(x_0)\ =\ (0.20)(-1)\ =\ -0.20$
- 아래쪽 노드($x_0$)
- $\frac{∂L}{∂f} \frac{∂f}{∂x_0}\ =\ \frac{∂L}{∂x_0}\ =\ (0.20)(w_0)\ =\ (0.20)(2)\ =\ 0.40$
- 윗쪽 노드($w_0$)
위와 마찬가지로, $w1$ 과 $x1$ 도 구해보겠습니다.
- local gradient는 $*$ 연산자이므로, 다음과 같습니다.
- 윗쪽 노드($w_1$)
- $\frac{∂f}{∂w_0}\ =\ x_1\ =\ -2$
- 아래쪽 노드($x_1$)
- $\frac{∂f}{∂x_0}\ =\ w_1\ =\ -3$
- 윗쪽 노드($w_1$)
- 그러므로 최종 출력에 대한 각 지점에서의 gradient는 다음과 같습니다.
- 윗쪽 노드($w_1$)
- $\frac{∂L}{∂f} \frac{∂f}{∂w_1}\ =\ \frac{∂L}{∂w_1}\ =\ (0.20)(x_1)\ =\ (0.20)(-2)\ =\ -0.40$
- 아래쪽 노드($x_1$)
- $\frac{∂L}{∂f} \frac{∂f}{∂x_1}\ =\ \frac{∂L}{∂x_1}\ =\ (0.20)(w_1)\ =\ (0.20)(-3)\ =\ -0.60$
- 윗쪽 노드($w_1$)
지금까지의 과정을 정리하면 다음과 같습니다.
- 먼저, 식을 computational graph로 표현합니다.
- forward pass 계산합니다. (초록색 숫자들)
- backward gradient 계산합니다. (빨간색 숫자들)
- local gradient와 upstream gradient의 곱을 계속해서 구하면 됩니다.
- 즉 chain rule을 사용했습니다.
- 이때, local gradient는 각 연산에 따른 미분을 통해 구합니다.
Computational Graph can be easily expressed
- 위 슬라이드의 파란색 박스 부분 연산들은 사실 Sigmoid 함수를 풀어서 나타낸 것입니다.
- 따라서, Computational Graph로 생각하는 것의 장점은 Sigmoid와 같은 복잡한 함수도 computational graph를 작성하고 simple한 연산들의 조합으로 (local gradient를 구하는 과정을 반복해서) gradient를 구할 수 있습니다.
- 즉 computational graph를 만들 때, computational 노드에 대해 우리가 원하는 세분화된 정의를 할 수 있습니다.
- 다시 예를 들어 sigmoid 함수를 볼 때 gradient를 계산할 수 있습니다.
- 분석적으로 이를 살펴본다면 sigmoid의 gradient는 $(1 - sigma(x)) * sigma(x)$ 와 같습니다.
- 하나의 big 노드, sigmoid로 바꿀 수 있습니다.
- 입력이 1(초록색) 출력은 0.73을 갖습니다.
- gradient를 얻기 위해 하나의 완전한 sigmoid 노드를 이용한다면, (1-sigmoid(x)) * sigmoid(x)를 이용할 수 있습니다.
- x는 0.73이고 이 값을 위의 식에 대입하면 gradient로 0.2를 얻게 됩니다.
- upstream gradient인 1과 곱하면 sigmoid gate로 바꾸기 이전의 작은 노드들로 계산된 값과 정확히 같은 값을 얻을 수 있습니다.
- 만약, 아주 복잡한 식에 backpropagation을 적용하기 위해 gradient를 구해야 한다면, Computational graph를 작성하고 하나하나의 local gradient를 구한 후, chain rule을 적용하면 빠르게 문제해결이 가능합니다.
Patterns in gradient flow
- Backpropagation에서 backward pass 과정을 하다보면 일관된 패턴 을 발견할 수 있습니다.
- add gate : upstream gradient가 연결된 브랜치에 똑같이 같은 값으로 분배됩니다.(gradient distributor)
- max gate : 큰 값의 local gradient는 1, 작은 값의 local gradient는 0이 되므로 여러 개 중에 가장 큰 값에만 gradient를 전달합니다. 즉, gradient가 한쪽으로만 흐릅니다.(gradient router)
- mul gate : local gradient가 다른 반대 편 변수의 값이므로, upstream gradient를 받아 다른 변수 값으로 scaling 합니다. 즉, gradient가 switch되며, swithing + scaling의 역할을 합니다.(gradient switcher)
- copy gate : gradient가 단순히 더해집니다.(gradient adder)
Backprop Implementation: “Flat” code
위 과정들을 실제 Flat한 code 형태로 진행과정을 살펴보겠습니다.
def f(w0, x0, w1, x1, w2):
# Forward pass: Compute output
s0 = w0 * x0
s1 = w1 * x1
s2 = s0 * s1
s3 = s2 * w2
L = sigmoid(s3)
#Backward pass: Compute grads
grad_L = 1.0
grad_s3 = grad_L * (1 - L) * L
grad_w2 = grad_s3
grad_s2 = grad_s3
grad_s0 = grad_s2
grad_s1 = grad_s2
grad_w1 = grad_s1 * x1
grad_x1 = grad_s1 * w1
grad_w0 = grad_s0 * x0
grad_x0 = grad_s0 * w0
Vector derivatives
vector to vector의 경우에 derivative는 Jacobian입니다.
- Input, output이 모두 scala일때는, 미분도 scala로 정의가 됩니다.
- 가장 많이 보는 형태로, output은 scala, input은 vector인 경우입니다.
- 이런 경우, gradient는 input과 차원수가 같습니다.
- gradient의 n번째 component는 n번째 input이 조금 변화했을 때, 전체 output이 얼마나 변하는 지에 대한 값입니다.
- 다변수함수의 경우, Input, output이 모두 다변수인 경우에, 미분(derivative)은 jacobian으로 표현됩니다.
- 즉, jacobian의 (n,m) component는 n번째 input이 변화할 때, m번째 output의 변화 정도를 의미합니다.
Gradients for vectorized code
- 위 슬라이드는 Upstream gradient, local gradient, Downstream gradient의 관계를 보여줍니다.
- 위 슬라이드는 $x$, $y$, $z$ 가 모두 vector인 상황이며, 차원(dimension)이 각각 $D_X$, $D_Y$, $D_Z$ 입니다.
- Gradient는 input값과 차원이 같기 때문에, upstream gradient는 똑같이 $D_Z$ 차원, downstream gradient는 각각 $D_X$, $D_Y$ 차원 입니다.
- Local gradient를 구하는 과정을 살펴볼때, 다변수 input, 다변수 output이므로, jacobian 행렬의 형태가 됩니다.
- 따라서 chain rule로 downstream gradient를 구하는 과정은 행렬과 벡터의 곱으로 표현됩니다.
- [Downstream] = [Local] * [Upstream] = $(D_x\ ×\ D_z)$ * $D_z$ = $D_x$
- 이때, 행렬이 매우 커질 가능성이 있기때문에, 메모리에 모두 올리는 것이 바람직하지않을 수 있습니다.
- 따라서, 연산의 패턴을 파악한 후, 메모리를 절약할 수 있는 방향으로 코드를 작성하는 방법을 살펴보겠습니다.
- 즉 x, y, z는 scalar 대신에 vector를 가지는것이 일반적이며, vector인 경우에는 local gradient가 Jacobian Matrix가 됩니다.
- Loss L은 여전히 scalar입니다.
- Gradients of variables wrt loss have same dims as the original variable
- gradient는 Jacobian 행렬이 되며, 각 요소의 미분을 포함하는 행렬이 됩니다.(예를들어 x의 각 원소에 대해 z에 대한 미분을 포함하는)
정리하자면 지금까지는 scalar값에 대해 backpropagation을 진행해 gradient를 구하는 방법에 대해 살펴봤지만, 실제로 입력값으로 넣어주는 파라미터 $W$ 는 matrix로 되어 있기 때문에 입력값이 vector로 들어가는 경우가 많습니다.
- 입력이 vector로 들어가게 되어도 전체적인 흐름은 같고 gradient의 형태, Jacobian matrix(자코비안 행렬, 다변수 벡터 함수의 도함수 행렬)로 표현됩니다.
- 이때, 행렬의 크기가 매우 커질 가능성이 있기때문에, 연산의 패턴을 파악한 후, 메모리를 절약할 수 있는 방법을 살펴보겠습니다.
What is the Jacobian matrix?
The Jacobian matrix is a matrix composed of the first-order partial derivatives of a multivariable function.
The formula for the Jacobian matrix is the following:
Example of Jacobian matrix
Having seen the meaning of the Jacobian matrix, we are going to see step by step how to compute the Jacobian matrix of a multivariable function.
- Find the Jacobian matrix at the point (1,2) of the following function:
- $f(x,\ y) = (x^4\ +\ 3y^2x,\ 5y^2\ -\ 2xy\ +\ 1)$
- First of all, we calculate all the first-order partial derivatives of the function:
- $\frac{∂f_1}{∂x}\ =\ 4x^3\ +\ 3y^2$
- $\frac{∂f_1}{∂y}\ =\ 6yx$
- $\frac{∂f_2}{∂x}\ =\ -2y$
- $\frac{∂f_2}{∂y}\ =\ 10y\ -\ 2x$
정리하면 다음과 같이 나타낼 수 있습니다.
Once we have found the expression of the Jacobian matrix, we evaluate it at the point (1,2):
Vectorized operations
$4096×1$ 크기를 가진 vector가 입력값(input)으로 들어간다고 하고 $4096×1$ 크기의 vector가 결과값(output)으로 나온다고 할때, 요소별로(elementwise) 최대값을 취하는 예제입니다.
- Jacobian matrix의 사이즈는 얼마인가요?
- $4096\ ∗\ 4096$ size가 됩니다.
- 만약 100개의 input을 동시에 입력으로 갖는 배치를 이용해 작업할 수 있습니다.
- 노드를 더욱 효율적으로 만들지만 100배 커지게 만듭니다.
- 즉 Jacobian은 $4096000\ *\ 4096000$ 입니다.
- 정리하면 데이터의 차원이 커질때, Jacobian matrix의 크기가 제곱으로 커지게 됩니다. 이는 실용적이지 않으며, 실제로는 이렇게 큰 Jacobian을 계산할 필요가 없습니다.
Backprop with Vectors
또한 Jacobian matrix의 형태(구조)를 살펴볼 수 있습니다.
- 위 슬라이드를 보면, $x ∈ ℝ^4, y ∈ ℝ^4$ 이며, $y = max(0, x)$ 로 elementwise하게 max를 취합니다.
- forward pass는 함숫값을 취하면 되므로 $x = (1, −2, 3, −1)$ 일 때, $y = (1, 0, 3, 0)$ 입니다.
- Backwardpass를 생각해볼때, Upstream gradient를 계산합니다.
- upstream gradient인 $dL/dy$ 는 4차원입니다. (Loss는 scala, y는 4차원이므로, scala를 4차원의 y로 미분하면 4차원벡터.)
- $dL/dx$ 를 구하기위해, local gradient인 Jacobian 행렬을 구합니다.
- Jacobian($dy/dx$)의 off-diagonal(비대각)요소들은 전부 0값을 갖게 됩니다.
- 결론적으로, downstream gradient는 “jacobian의 대각요소가 1인 행”에 대해서는 upstream gradient값을 가져오고, 0인 행에 대해서는 0값을 갖습니다.
- 현재의 Jacobian은 매우 sparse한 형태입니다. 비대각요소들이 전부 0이므로, 코드를 짤 때 메모리에 모두 업로드하는 것은 매우 비효율적입니다.
- [Downstream]의 i번째 성분 : input 데이터(x)가 양수(+)인지, 음수(-)인지에 따라서 upstream데이터를 그대로 유지할 지, 0으로 truncate할 지 정해집니다.
- 즉 Jacobian matrix의 형태를 보면 대각성분을 제외한 나머지 성분들이 0이 되는 diagonal matrix가 되므로, Jacobian matrix를 모두 작성하지 않고 diagonal 성분만을 사용해서 gradient를 구할 수 있습니다.
- input의 n번째 차원의 element는 output의 n번째 차원의 element에만 영향을 미칩니다.
- 따라서, 각 차원의 element끼리만 영향을 주므로, Jacobian matrix에서 대각성분만 모두 남게되는 것 입니다.
- 즉 Jacobian행렬은 대각행렬이 됩니다.
- 출력에 대한 x의 영향에 대해서, 그리고 이 값을 사용하는 것에 대해서만 알면 됩니다.
Backprop with Matrices(or Tensors)
$x$, $y$, $z$ 가 모두 matrix or tensor인 상황에도 같은 개념으로 구현할 수 있습니다.
- Matrix를 모두 벡터로 펼쳐서 생각해본다면, Local gradient는 여전히 jacobian matrix를 갖습니다.
- 따라서, downstream gradient는 행렬-벡터간 곱셈이 됩니다.
- 여전히 jacobian 행렬의 메모리 문제를 안고있습니다.
- [Local]*[Upstream] 방식이 간략하게 표현하기 위해서, Local gate가 어떠한 방식으로 정의되는지가 중요합니다.
Example: Matrix Multiplication
Matrix Multiplication에 대한 예시를 살펴보겠습니다.
- Forward pass: 주어진 값으로 행렬간 곱셈을 계산합니다.
- Backward pass: Upstream gradient가 들어왔을 때, $dL/dy$ 는 y와 같은 차원이므로 $N * M$.
- Downstream gradient는 x와 같은 차원인 N * D를 가져야 합니다.
- Mul gate는 swap multipier 역할을 하기때문에, $dL/dx$ 는 w값과 upstream이 곱해진 값을 갖게됩니다.
- $\frac{∂L}{∂x} = (sth\ about\ w) * \frac{∂L}{∂y} = \frac{∂L}{∂y} * w^T$
- mulitplication operation의 특성상 전치.
일반적으로 어떻게 되는지, $\frac{dL}{dx_{1,1}}$ 을 먼저 채워보겠습니다.
- $y_{1,1} = x_{1,1}w_{1,1} + x_{1,2}w_{2,1} + x_{1,3}w_{3,1} = \sum_K x_{1,K}W_{K,1}$
- $\frac{dy_{1,1}}{dx_{1,1}} = w_{1,1} = 3$
- $y_{1,2} = x_{1,1}w_{1,2} + x_{1,2}w_{2,2} + x_{1,3}w_{3,2} = \sum_K x_{1,K}W_{K,2}$
- $\frac{dy_{1,2}}{dx_{1,1}} = w_{1,2} = 2$
- $y_{1,3} = x_{1,1}w_{1,3} + x_{1,2}w_{2,3} + x_{1,3}w_{3,3} = \sum_K x_{1,K}W_{K,3}$
- $\frac{dy_{1,3}}{dx_{1,1}} = w_{1,3} = 1$
- $y_{1,4} = x_{1,1}w_{1,4} + x_{1,2}w_{2,4} + x_{1,3}w_{3,4} = \sum_K x_{1,K}W_{K,4}$
- $\frac{dy_{1,4}}{dx_{1,1}} = w_{1,4} = -1$
- $y_{2,1} = x_{2,1}w_{1,1} + x_{2,2}w_{2,1} + x_{2,3}w_{3,1} = \sum_K x_{2,K}W_{K,1}$
- $\frac{dy_{2,1}}{dx_{1,1}} = 0$
- $y_{2,2} = x_{2,1}w_{1,2} + x_{2,2}w_{2,2} + x_{2,3}w_{3,2} = \sum_K x_{2,K}W_{K,2}$
- $\frac{dy_{2,1}}{dx_{1,1}} = 0$
- $y_{2,3} = x_{2,1}w_{1,3} + x_{2,2}w_{2,3} + x_{2,3}w_{3,3} = \sum_K x_{2,K}W_{K,3}$
- $\frac{dy_{2,1}}{dx_{1,1}} = 0$
- $y_{2,4} = x_{2,1}w_{1,4} + x_{2,2}w_{2,4} + x_{2,3}w_{3,4} = \sum_K x_{2,K}W_{K,4}$
- $\frac{dy_{2,1}}{dx_{1,1}} = 0$
$x_{1,1}$ 에 대한 local gradient가 완성되었습니다.
- $\frac{∂L}{∂x_{1,1}} \ = \frac{∂y}{∂x_{1,1}} \frac{∂L}{∂y} \ = [row\ 1\ of\ matrix\ w] \frac{∂L}{∂y{1,:}} \ = (w_{1,:}) (\frac{∂L}{∂y{1,:}})$
- $\frac{dy_{1,K}}{dx_{2,3}} = 0$
- $\frac{dy_{2,1}}{dx_{2,3}} = w_{3,1} = 3$
- $\frac{dy_{2,2}}{dx_{2,3}} = w_{3,2} = 2$
- $\frac{dy_{2,3}}{dx_{2,3}} = w_{3,3} = 1$
- $\frac{dy_{2,4}}{dx_{2,3}} = w_{3,4} = -2$
- $\frac{∂L}{∂x_{2,3}} = (w_{3,:}) (\frac{∂L}{∂y{2,:}})$
위와 같은 방식 그대로 계산한다면, 모든 값을 채울 수 있습니다.
- 또한 일반화된 식을 도출 할 수 있습니다.
- $\frac{∂L}{∂x_{i,j}} \ = \frac{∂y}{∂x_{i,j}} \frac{∂L}{∂y} \ = (w_{j,:}) (\frac{∂L}{∂y_{i,:}})$
- $dL/dx = (dL/dy)w^T$
- w에 대해서도 마찬가지로 도출됩니다.
- $dL/dw = x^T (dL/dy)$
- 위 슬라이드와 같이 영향력을 볼 수 있습니다.
- $y$ 의 어떤 부분이 $x$ 의 한 요소에 의해 영향을 받습니까?
- $x_{n, d}$ 는 전체 행 $y_{n, .}$ 에 영향을 미칩니다.
- $\frac{∂L}{∂x_{n,d}} = \sum_m \frac{∂L}{∂y_{n,m}} \frac{∂y_{n,m}}{∂x_{n,d}}$
- $x_{n,d} 는 y_{n,m} 에 얼마나 영향을 미칩니까?
- $w_{d,m}$
- $\frac{∂L}{∂x_{n,d}} = \sum_m \frac{∂L}{∂y_{n,m}} \frac{∂y_{n,m}}{∂x_{n,d}} = \sum_m \frac{∂L}{∂y_{n,m}} w_{d,m}$
A vectorized example
computational graph보다 구체적인 벡터화 된 예제를 살펴보겠습니다.
- Matrix에서 Backpropagation을 수행합니다.
- 출력에 대한 upstream gradient는 이전과 동일하게 1입니다.
- $\frac{∂f}{∂f}\ =\ 1$
- local gradient는 L2 연산에서 제곱을 하므로 q2의 미분인 2q입니다.
- $W⋅x$ 연산을 $q$ 라고 한다면, $f(q)\ =\ q^2_1\ +\ q^2_2\ +\ …\ +\ q^2_n$ 으로 나타낼 수 있고, 각 $q_i$ 에 대해 편미분하면 $\frac{∂f}{∂q}\ =\ 2q_i$ 로 나타낼 수 있습니다.
- 즉, gradient 는 $∇_qf\ =\ 2q$ 입니다.
- $\frac{∂f}{∂q}\ =\ 2q\ =\ 2 \begin{bmatrix}0.22 \ 0.26\end{bmatrix}\ =\ \begin{bmatrix}0.44 \ 0.52\end{bmatrix}$
- 따라서, 최종 출력에 대한 q에서의 gradient는 다음과 같습니다.
- $\frac{∂f}{∂f} \frac{∂f}{∂q}\ =\ \frac{∂f}{∂q}\ =\ 2q\ =\ \begin{bmatrix}0.44 \ 0.52\end{bmatrix}$
- 다음으로, 곱셈 노드의 $W$ 브랜치를 계산하겠습니다.
- $W$ 변화량에 따른 $f$ 의 변화량, 즉 $\frac{∂f}{∂W}$ 을 구해야합니다.
- chain rule 적용 위해 $W$ 변화량에 따른 $q$ 의 변화량, $\frac{∂q}{∂W}$ 을 알아야합니다.
- mul gate 는 swithching 역할을 하며, 각 행을 계산할때 다른 행은 관여하면 안되기때문에 $k$ 가 $i$ 일때는 1 을 곱해주고, 아닐때에는 0을 곱해줍니다.
- $\frac{∂q_k}{∂W_{i,j}}\ =\ 1_{k=i}x_j$
- 이것을 chain rule을 이용해서 $W$ 변화량에 따른 $f$ 의 변화량, $\frac{∂f}{∂W}$ 을 구할 수 있습니다.
- $\frac{∂f}{∂W_{i,j}}\ =\ \sum_k \frac{∂f}{∂q_k} \frac{∂q_k}{∂W_{i,j}}\ =\ \sum_k(2q_k)(1_{k=i}x_j)\ =\ 2q_ix_j$
- 그러므로 gradient는 $∇_Wf=2q⋅x^T$ 입니다.
즉 정리하면 다음과 같습니다.
\[\frac{∂f}{∂W}\ =\ \frac{∂f}{∂q}x^T\ =\ (2q)x^T\ =\ \begin{bmatrix}0.44 \\ 0.52\end{bmatrix} [0.2\ 0.4]\ =\ \begin{bmatrix}0.088\ 0.176 \\ 0.104\ 0.208\end{bmatrix}\]
- 다음으로, 곱셈 노드의 $x$ 브랜치를 계산하겠습니다.
- $x$ 변화량에 따른 $f$ 의 변화량, 즉 $\frac{∂f}{∂x}$ 을 구해야합니다.
- chain rule 적용 위해 $x$ 변화량에 따른 $q$ 의 변화량, $\frac{∂q}{∂x}$ 을 알아야합니다.
- $\frac{∂q_k}{∂x_i}\ =\ W_{k,i}$
- 이것을 chain rule을 이용해서 $x$ 변화량에 따른 $f$ 의 변화량, $\frac{∂f}{∂x}$ 을 구할 수 있습니다.
- $\frac{∂f}{∂x_i}\ =\ \sum_k \frac{∂f}{∂q_k} \frac{∂q_k}{∂x_i}\ =\ \sum_k(2q_k)(W_{k,i})\ =\ 2q_ix_j$
- 그러므로 gradient는 $∇_xf=W^T⋅q$ 입니다.
즉 정리하면 다음과 같습니다.
\[\frac{∂f}{∂X}\ =\ W^T\frac{∂f}{∂q}\ =\ W^T(2q) = \begin{bmatrix}0.1\ -0.3 \\ 0.5\ 0.8\end{bmatrix} \begin{bmatrix}0.44 \\ 0.52\end{bmatrix}\ =\ \begin{bmatrix}-0.112 \\ 0.636\end{bmatrix}\]지금까지의 중요한 점은 다음과 같습니다.
- 벡터의 gradient는 항상 원본 벡터의 사이즈와 같습니다.
- 항상 gradient의 shape을 확인해야하며, gradient의 값을 계산했을 때 shape가 다른 결과가 나올 수 있기에 shape은 중요합니다.
- gradient의 각 요소는 함수의 최종 출력에 얼마나 특별한 영향을 미치는지 의미합니다.
Modularized implementation: forward / backward API
- computational graph에서의 과정을 실제 코드로 모듈화 된 구현이 가능합니다.
- 각 노드를 local하게 보았고 upstream gradient와 함께 chain rule을 이용해서 local gradient를 계산했습니다.
- 이것을 forward pass, 그리고 backward pass의 API로 생각할 수 있습니다.
- forward pass에서는 노드의 출력을 계산하는 함수를 구현하고, backward pass에서는 gradient를 계산합니다.
- 또한 위치적으로 정렬 된 순서로 수행하기를 원합니다.
- 노드를 처리하기 전에 이 전에 노드로 들어오는 모든 입력을 처리합니다.
- 그리고 backward로 보면, 역순서로 모든 게이트를 통과한 다음에 게이트 각각을 거꾸로 호출합니다.
- 또한 특정 게이트, 곱셈 게이트를 살펴보면 forward()와 backward()로 구성되어있습니다.
- forward pass
- x, y를 입력으로 받고 z를 리턴합니다.
- 중요한 것은 값을 저장(cache)해야합니다. backward pass에서 더 많이 사용하기 때문입니다.
- backward pass
- 입력으로 upstream gradient인 dz를 받고 출력으로 입력인 x와 y에 gradient를 전달합니다.
- 여기서 출력은 dx와 dy입니다.
- chain rule을 사용하여 upstream gradient 값을 이용해 다른 브랜치의 값과 곱합니다.
Summary so far…
- 신경망은 정말로 크고 복잡합니다.
- 그렇기에 모든 파라미터에 대해 gradient를 손으로 구하고 써내려가는 것은 비현실적입니다.
- 그래서 gradient를 계산하기 위해서 backpropagation을 사용하는 방법에 대해서 언급했고, 신경망의 핵심 기술입니다.
- 그리고 computational graph에서 chain rule을 재귀적으로 적용한 것입니다.
- 또한 각각의 노드에 대한 그래프 구조에 대해 어떻게 forward, backward API 구현으로 나타내는지에 대해서 이야기 했었습니다.
- forward pass에서는 나중에 gradient를 계산할 때 backward pass에서 chain rule에서 사용하기 위해 연산 결과를 계산하고 결과를 저장합니다.
- backward pass에서는 upstream gradient와 저장한 값들을 곱해 각 노드의 input에 대한 gradient를 구하고, 연결된 이전 노드로 통과시킵니다.
Fully-connected Neural Networks
Neural networks: the original linear classifier
- Neural Network를 알아보기 전에 linear classifier부터 짚고 넘어가겟습니다.
- linear classifier는 단순히 $W$ (weight) 와 input $x$ 를 곱해주는 $f=Wx$ 의 함수를 통해 클래스의 score를 계산했습니다.
- 하지만 다음과 같은 단점이 있었습니다.
- Geometric Viewpoint
- Linear classifiers로는 이런 형식의 데이터는 좋은 성능으로 분류하는 것이 불가능합니다.
- Visual Viewpoint
- 클래스 당 하나의 템플릿만 가질 수 있기때문에, 실제로 같은 class여도 정해진 템플릿과 비슷한 생김새가 아니라면 그 class로 인식될 수 없게 됩니다.
- Geometric Viewpoint
- 즉 정리해보면, Linear classifier의 문제점은 한 class 내에 다양한 특징들이 존재할 수 있지만, 모든 것들을 평균화시킨다는 점이 있습니다.
- 앞서 CIFAR-10 예시를 보면 horse class에 대한 $W$ 행을 시각화한 이미지를 확인하면, 말의 머리가 양쪽으로 있는 것을 확인할 수 있습니다. 이러한 점이 하나의 linear classifier 이 가지는 문제가 드러나는 모습임을 알 수 있습니다.
- 또한 car class 분류에 대한 $W$ 를 시각화 한 것을 보면 빨간색 차의 앞모습으로 보이는 물체만 있습니다. 빨간색이 아닌, 다른 색깔의 차 혹은 차의 앞모습이 아닌 옆모습의 이미지는 잘 분류할 수 없다는 문제가 있습니다.
만약, 하나의 class에 대해 하나의 $W$ (템플릿)이 아니라 여러 개의 템플릿이 존재하면 다양한 이미지에 대해서 분류를 해낼 수 있습니다. 예를들어, car class 에 대한 템플릿으로 빨간색 차, 노란색 차, 차의 옆모습 등등 의 다양한 템플릿을 생성하는 모습을 생각해볼 수 있습니다.
그러므로 Linear Classifier 모델의 성능이 나쁜 것을, 여러 개의 Layers를 사용한 neural networks 를 사용해서 해결하고자 합니다.
Neural networks: 2-layers also called fully connected network
- 지금까지의 linear score function은 $f(x, W) = Wx$
- 2-layer Neural Network부터 $f(x;W_1, W_2) = W_2max(0, W_1x)$ 형식으로 변합니다.
- $W_1x$: 한 번 matrix-vector multiplication(행렬-벡터의 곱셈)
- $max(0, W_1x)$: 0과 elementwise하게 max
- $W_1x$ 의 음수 값은 0으로 대체(truncate)
- H x 1 vector
- $W_2max(0, W_1x)$: 다시 matrix-vector multiplication.
- 2-layer Neural Network의 형태: 선형(비선형(선형 변환(x))
- 선형과 비선형의 합성함수를 계속 반복하여 적용한다.
- 2개의 weight, 1개의 nonlinear activation을 이용합니다.
- Bias를 집어넣으면, $f(x;W_1,b1,\ W_2,b_2) = W_2max(0, W_1x + b_1) + b_2$
- $(W1)_{i,j}$ 성분을 살펴보겠습니다.
- $h_1 = W_1x$
- $h_i= ∑k(W_1){i,k} x_k$
- $(W_1)_{i, j}$ 성분이 $x_j$ 를 $h_i$ 로 만드는 데에 주요한 역할을 합니다.(contribution)
- $h_i$ 는 $x_1 ~ x_D$ 이 각각 $(W_1)i$ 와 곱해져서 구성되는데, $h_i$ 를 구성하기 위해 $x_j$ 가 contribute한 만큼, $(W_1){i,j}$ 도 contribute합니다.
- 즉, $(W_1)_{i,j}$, 성분 = $h_i$ 에 대해서 $x_j$ 가 얼마만큼 contribute하는지 결정합니다.
- $x$ 의 각 component가 $h$ 의 각 component로 contribute 합니다.($W_1$이 꽉 차있는 matrix이기 때문)
- 결과적으로, Fully-Connected NN : 이전 레이어의 모든 벡터들이 다음 레이어의 모든 벡터에 영향을 미칩니다.
Template of Neural Networks
- $W$ 의 row 하나(class 하나에 대응됨)를 그림처럼 나타낸 것이 template 입니다.
- Linear classifier 일때는 클래스 하나당 하나의 template이 만들어지며, 이는 $W_1$ 하나만 있는 것으로 생각 할 수 있습니다.
- 입력과 직접적으로 연결되어 있으므로, 이 template을 공식화 할 수 있기 때문에 해석가능합니다.
- Neural Networks부터는 layer가 여러 개 입니다.
- $h$ 하나를 만들기 위해 템플릿($W_1$)을 한번 매칭한 후, 새로 만들어진 템플릿($W_2$)을 다시 한 번 조합합니다.
- 즉, 2-Layer Neural Network 에서는 카테고리당 1개의 템플릿만을 만드는 것이 아니고, 첫 번째 레이어에서 각각의 Weigth 마다 템플릿을 만들어 내고, 두번째 레이어에서는 이전에 만든 템플릿들을 재조합해서 class score를 만들어 냅니다.
- 이처럼 1개의 템플릿이 아닌 여러 개의 템플릿의 조합으로 class를 표현한다고 해서 이 방식을 ‘Distributed Representation’이라고 부르기도 합니다.
- 신경망 모델에서는 여러 개의 템플릿을 만들기에 중복된 정보를 가진 템플릿을 만들 수도 있지만, Network Pruning과 같은 기술들로 어느 정도는 해소할 수 있다고 합니다.
- 레이어가 두 개가 되면, horse에 대한 템플릿이 여러개일 때, 조합할 수 있게 됩니다.
- 하지만 각자 template에 대한 해석은 쉽지 않아집니다.
- 위 그림은 Layer가 6개인 6-layer Neural Networks를 그림으로 나타낸 것입니다.
- 세로(=너비)는 input의 차원(dimension)에 대응합니다.
- 보통 FC를 만들 때, H1 = H2 = H3 = H4 = H5를 같게 하는 경우가 많습니다. 혹은 1/2씩 곱해나가기도 합니다. (ex. H1=256, H2=128, H3=64, H4=32, H5=16)
- 위의 예시에서 $f = W_2max(0, W_1x)$ 이 이상으로 표현했습니다.
- $max(0, W_1x)$ 는 ReLu 라고 불리는 자주 쓰이는 activation function입니다.
- 따라서 여러 개의 $W (W_1, W_2, …, W_n)$ 이 학습되고, 이 각각의 $W$ 들은 각각의 중간 과정에서 하나의 class에 대한 다른 템플릿을 만들어내는 효과를 가지게 됩니다.
- 또한 linear classifier의 문제를 해결하기 위해, 여러 개의 레이어를 사용하여 classifier에 비선형성을 부여하여, 선형 문제에서는 해결할 수 없었던 문제들에 들을 해결할 수 있게 됩니다.
- $f = W(h(x))$ 의 형태에서 내부의 함수 $h(x)$ 는 비선형 함수여야만 합니다.
위와같이 layer를 깊게 쌓은 것을 Deep neural network 라고 지칭합니다.
- 정리하면 다음과 같습니다.
- Neural network는 지금까지 다루어온 Linear score function을 여러 개 쌓은 형태입니다.
- 입력 x를 받아 첫번째 W와 곱하고 출력한 일종의 score를 다시 받아 W를 곱하고 또 출력하고 이런식으로 반복하는 형태입니다.
- 하나의 Layer가 아니라 여러개의 Layer로 구성됩니다.
- 이때, 각각의 출력에 비선형함수를 사용해서 비선형성을 추가해줍니다.
- Neural network는 지금까지 다루어온 Linear score function을 여러 개 쌓은 형태입니다.
- 만약 Neural network내에 activation function을 쓰지않으면 $f = W_1W_2x$ 형태가 됩니다.
- 즉, x에 대한 선형변환만 될 뿐입니다.
- 선형변환은 decision boundary가 linear하게 그어지므로, 충분히 복잡한 함수를 표현할 수 없습니다.
- 즉, ReLU와 같은 activation function이 있어야 $f(x; W)$ 가 $x$ 의 함수로서 더 다양한 함수를 표현할 수 있습니다.
Biologocal vs. artificual neurons
신경망이 무엇인지 함수로써 살펴보았는데, 보통 Neural Network를 설명할때는 신경망에 대한 생물학적 영감에 대해, 인간 두뇌의 신경망과 연관지어 설명합니다.
- dendrite(수상돌기)를 통해 특정 신호가 입력되게 됩니다.
- 들어온 신호들은 cell body 에서 종합되어 합쳐집니다.
- 이후, 합쳐진 신호는 axon 을 통해 다음 뉴런으로 전달되게 됩니다.
- 이때 받은 자극을 그대로 전달하지 않고, Input된 자극을 nonlinear하게 변환해서 전달해줍니다.
이러한 구조는 앞에서 다뤘던 computational graph의 각 노드에서 일어나는 과정과 굉장히 유사합니다.
- 이 현상을 수학적으로 모델링 한 것이 현재의 neural network 입니다.
- input된 자극을 $x_0$ 라고 할 때, 시냅스를 통해 신호가 조금 바뀐 형태로 $w_0 * x_0$ 가 출력되며, cell body에서는 모은 정보를 종합하고, activation function인 $f$ 를 적용하여 다른 뉴런으로 전달해줍니다.
- 여기서 주의할 점은 인공신경망 neural network은 인간의 실제 두뇌 신경망과는 완전히 같지는 않다는 점입니다. 실제 인간의 두뇌는 훨씬 더 복잡한 비선형 시스템입니다.
Activation functions
- 실제 사용될 수 있는 많은 다른 종류의 activation function이 존재합니다.
- ReLu 가 가장 일반적으로 사용됩니다.
Neural networks: Architectures
- Neural network에서 위와 같이 모든 노드의 출력이 모든 다음 노드의 입력으로 연결된 layer 형태를 Fully connected layer라고 합니다.
Example feed-forward computation of a neural network
# forward-pass of a 3-layer neural network:
f = lambda x: 1.0/(1.0 + np.exp(-x)) # activation function (use sigmoid)
x = np.random.randn(3, 1) # random input vector of three numbers (3x1)
h1 = f(np.dot(W1, x) + b1) # calculate first hidden later activations (4x1)
h2 = f(np.dot(W2, h1) + b2) # calculate second hidden later activations (4x1)
out = np.dot(W3, h2) + b3 # output neuron (1x1)
Summary