[cs231n] lecture 4 내용 정리

이현지·2022년 5월 24일
0

cs231n

목록 보기
2/2

Neural Networks

이전에는 linear score function을 이용하여 score를 얻을 수 있었습니다. 즉, f=Wx로 구할 수 있었는데, 이제는 layer를 하나 더 쌓아서 2-layer Neural Network를 만들어보는 것입니다. max(0,W)max(0,W)의 비선형 함수 즉, activation function을 이용해서 layer를 하나 더 쌓아주면 됩니다.

'Neural Network'는 매우 광범위한 용어인데, Neural Network를 ‘fully connected network' 또는 ‘multi-layer perceptrons'(MLP)이라고도 부릅니다. 다층 퍼셉트론(MLP)과 같이 오직 입력층에서 출력층 방향으로 연산이 전개되는 신경망을 피드 포워드 신경망(Feed-Forward Neural Network, FFNN)이라고 합니다.
방금 말했던 것처럼, linear score function에 layer를 하나 더 쌓아서 2-layer Neural Network를 만들어본 예제가 있습니다. 3072차원의 벡터 x에 W1이라는 matrix를 곱해 100차원의 h를 만들고, 여기에 max(0,W1x) 를 취한 뒤, W2라는 matrix를 곱해서 최종 10개 class에 대한 score를 내는 neural network입니다. 


우리가 이전에 linear classifier에서 사용되는 weight matrix의 각 row가 class를 분류하기 위해 input의 어떤 부분을 살펴봐야 하는지에 대한 정보를 담은 template이라고 얘기했습니다. 하지만, linear classifier에서의 한계점은 이러한 template을 class마다 단 한 개씩만 가질 수 있다는 점이었습니다. 따라서 class 내에서 다양한 형태를 가진다면 예측력이 크게 감소하게 됩니다.

예를 들어서, car class의 score를 계산할 때 하나의 자동차 template 만을 가지고 있기 때문에, 위의 경우는 빨간색 자동차가 보이는데, 노란색 차를 분류하고 싶을 때 예측력이 감소한다는 것입니다. 즉, 다른 여러 종류의 차를 찾기 원할 수도 있습니다.

이러한 단점을 neural networks 가 보완할 수 있는데, 위 예시에서 W1W_1은 100 X 3072 크기의 matrix이고, 각각의 row가 template가 됩니다. 즉, class 크기보다 더 많은 template를 갖게 되기 때문에 다양한 형태의 template를 학습할 수 있고, 같은 class를 나타내는 이미지의 형태가 다르더라도 학습할 수 있게 됩니다. W1xW_1x에 비선형 함수(이 경우, ReLU)를 적용해서 최종적으로 구한 결과가 hh 입니다. 그 뒤에 hhW2W_2 가중치를 부여하여 10x1의 shape를 가지게 되고, 이는 분류해야하는 class들에 대한 각 class score를 나타냅니다. 이를 통해 다양한 template의 점수들을 반영해서 class score를 더 잘 예측할 수 있게 됩니다.

  • ❓ Q. 그런데 만약 activation function 없이 선형적인 layer만 쌓아서 Neural Network를 만든다면 어떻게 될까?
    : 해당 모델은 linear function으로 계산되는 것과 동일하며, NN이 더 많은 layer로 구성되더라도 일반 Logistic Regression과 동일하게 됩니다. 결국 hidden layer의 의미가 없어지게 되는 것이죠! 위의 예시처럼 단순 행렬 곱이 되어, W3xW_3x의 linear function이 될 것입니다.
    데이터가 복잡해지고, feature들의 차원이 증가하면서 데이터의 분포가 선형적이지 않고 비선형적으로 나타나면서 단순한 선형의 boundary로는 표현이 불가능하기 때문에 비선형의 boundary가 필요하기 때문에 non-linear가 필요합니다.

위의 예시에서도 사용했던 ReLu를 사용하는 것이 대부분의 문제에서 default한 좋은 선택이라고 합니다. ReLu는 양수일 경우, 입력을 그대로 내보내며, 음수인 경우에는 0으로 내보냅니다. ReLu의 경우 일부 뉴런만 활성화하기 때문에 네트워크를 sparse하고 efficient하게 만들어줍니다. 그러나, 입력이 음수인 경우 gradient가 무조건 0이 된다는 단점이 있습니다.

이 문제를 극복하기 위해서 Leaky ReLU가 고안되었다고 합니다. 더 자세한 내용은 이후 강의에서 자세히 다룬다고 하니, 그 강의를 참고하면 될 것 같습니다.
Neural network의 구조를 살펴보면, input layer에서 hidden layer를 거쳐 output layer로 연산이 진행됩니다. 보통 hidden layer와 output layer는 fully-connected layer로 구성됩니다. 그리고 이걸 2-layer Neural Net 혹은 1-hidden-layer Neural Net 이렇게 부를 수 있다고 합니다.

Neural network를 코드 예시를 통해서 살펴보면, f 라는 sigmoid activation function을 정의한 다음, x는 [3x1] input vector를 만들어줍니다. Hidden layer는 h1, h2를 통해 2개의 hidden layer가 생성됐다는 것을 알 수 있습니다. Hidden layer 1은 [4x3] 의 크기를 지닌 W1을 이용해서 연산한 뒤, b1을 더해주고 여기에 activation function 을 통과시켜 hidden layer1을 구하게 됩니다. Hidden layer2도 마찬가지로 계산해주고 output layer는 하나의 값을 가지므로, [1x4] 의 크기를 가진 W3를 곱해서 output layer를 계산합니다.

2-레이어 네트워크의 전체 구현은 20줄 정도로 실제로 매우 단순합니다.


우리는 뉴런이 무엇인지를 함수로써 살펴보았습니다. 지금부터 간단하게 생물학적으로 뉴런에 대해서 이야기해보겠습니다.
이것은 뉴런의 다이어그램입니다. 많은 뉴런이 서로 연결되어 있고, 각 뉴런을 따라 전달되는 신호가 존재합니다. 그리고 각 뉴런은 수상돌기를 가지고 있고, 뉴런에 들어온 신호를 받습니다. 그리고 세포체(cell body)는 기본적으로 들어오는 신호를 종합합니다. 세포체(cell body)는 이것들을 받아 합친 후에 모든 신호는 하류 뉴런과 연결된 다른 cell로 이동합니다. 이것은 축삭(axon)을 통해 운반됩니다.

오른쪽 아래의 신경망의 그림을 보게 되면, 수상돌기를 통해서 다른 뉴런을 통해 자극을 받아오게 되고, 이것을 cell body에서 합칩니다. 실제 신경망에서는 wixi+bw_ix_i + b 형태로 계산을 하게 됩니다. 그리고 활성 함수을 거친 뒤 출력값을 얻고 아래로 연결된 layer에게 전달하고 있습니다.

다른 뉴런에게 자극을 보낼지, 아닐지를 결정하는 게 neural network에서의 activation function과 같으며, 실제 뉴런과 가장 비슷한 기능을 수행하는 것은 바로 ReLU라고 합니다. 그런데 위 그림에서는 sigmoid 함수가 예시로 적혀 있네요

그런데, 실제 뉴런 하고는 차이가 많이 존재하기 때문에 이러한 비유를 사용하는 것은 주의해야 한다고 하네요.. 생물학적인 뉴련은 복잡한 연결 패턴을 갖고 있지만, neural network 에서의 뉴런은 계산 효율을 높이기 위한 일반 레이어로 체계화되어 있습니다.


Backpropagation

Neural network에 loss function을 구해보면, data loss에 regularization을 더한 값이라는걸 알 수 있습니다. 근데, 이렇게 복잡한 수학적 공식으로 어떻게 gradient를 계산할 수 있을까요? 우리는 각 WW의 영향력을 알기 위해서는 편미분을 진행해야 하는데, 이는 연산이 매우 어렵습니다..
그래서 computational graphbackpropagation을 통해서 gradient를 구하는 방식은 복잡한 연산을 보다 간단하게 수행할 수 있습니다. computational graph는 어떤 함수를 표현할 때 graph을 이용해서 표현하는 방법입니다. 위 슬라이드를 보게 되면, 노드(node)가 존재하고 화살표가 존재하는데 노드는 연산을 의미하고, 화살표는 데이터의 흐름을 나타냅니다.

예를 들어서, 위 슬라이드에서 xxWW를 첫 번째 곱셈 노드에 집어넣으면 WxWx 연산해주고, 여기서 나온 값이 s(scores)입니다. 그리고 여기에 hinge loss를 계산한 뒤, 여기에 R(W)R(W)를 더해서 최종 값인 LL을 구하게 됩니다.

이렇게 computational graph를 이용해서 함수를 표현하게 되면, 모든 변수에 대해서 재귀적으로 gradient를 계산하는 backpropagation을 사용할 수 있게 됩니다. Backpropagation은 gradient를 얻기 위해 computational graph 내부의 모든 변수에 대해 chain rule을 재귀적으로 사용합니다.
매우 간단한 예시를 살펴보면,
사용되는 함수는 f(x,y,z)=(x+y)zf(x,y,z)=(x+y)z라는 함수입니다. 즉, 입력으로 xxyyzz를 받아 (x+y)z(x+y)z를 출력하는 함수입니다. 이때, x =2x = -2y =5y = 5z =4z = -4로 대입해 사용합니다. 우리가 이번 예시에서 해볼 것은, 변수들에 대해서 함수의 gradient를 구해보는 것입니다.

여기에서 모든 중간 변수에 이름을 줘봅시다. x+yx+y 덧셈 노드를 qq라고 부를 것입니다. 여기에서 ffqzqz입니다. 그리고 저는 xxyy에 각각에 대한 qq의 gradient를 표시해 놓았습니다. 그것은 단순 덧셈이기 때문에 1입니다. 그리고 qqzz에 대한 ff의 gradient는 곱셈규칙에 의해 각각 zzqq입니다. 그리고 우리가 찾기 원하는것은 x,y,zx,y,z 각각에 대한 ff의 gradient입니다. 각각은 함수의 input 값인 x, y, zx, y, z가 변했을 때, 함수 ff에 얼마나 영향을 미치는지를 나타냅니다.
backpropagation은 computational graph의 가장 끝에서부터 gradient를 계산합니다. 가장 마지막 노드는 함수 ff 그 자체를 나타내기 때문에, f/f∂f / ∂f가 되고, 값은 1로 구해집니다.

이번에는 뒤로 이동해서 zz에 대한 gradient를 계산해보면, zz에 대한 ff의 미분이 qq이고, qq의 값은 3이기 때문에 여기에서 zz에 대한 ff의 미분 값은 3을 갖습니다.
그리고 다음으로 qq에 대한 ff의 편미분 값을 구해보면 편미분 값은 zz와 같기 때문에 -4라는 것을 알 수 있습니다.

그래프의 더 뒤로 이동해서, yy에 대한 ff의 미분값을 구해보면, 그런데 여기에서 yyff와 바로 연결되어 있지 않습니다. ffzz와 연결되어 있는데, 이것을 구하는 방법은 chain rule을 이용하면 됩니다. f/y∂f / ∂y은 upstream gradient인 f/q∂f/∂q과 local gradient인 q/y∂q / ∂y의 곱으로 나타낼 수 있습니다.

f/q∂f/∂q은 -4이고, 이를 q/y∂q / ∂y과 합성(곱)합니다. q/y∂q / ∂y은 덧셈 공식이기 때문에 1입니다. 이것들을 곱해보면 우리는 f/y∂f / ∂y으로 -4를 얻습니다.

만약 똑같이 xx의 gradient를 알고 싶으면, yy의 gradient를 구하는 같은 방법으로 구할 수 있습니다. chain rule을 적용해서, f/q∂f / ∂q의 gradient 값인 -4와 q/x∂q / ∂x 값인 1을 곱해주면 됩니다.
하지만 각 노드는 오직 주변에 대해서만 알고 있고, 우리가 연산 시에 알 수 있는 건, 각 노드와 각 노드의 local 입력입니다. 입력은 노드와 연결되어있고 입력 값은 노드로 흘러 들어가 출력을 얻게 됩니다. 여기에서 local 입력은 x,yx,y이고 출력은 zz입니다. 그리고 이 노드에서 local gradient 즉, z/x∂z / ∂x, z/y∂z / ∂y도 구할 수 있습니다. 각 노드는 우리가 이전에 보았던 것처럼 덧셈 혹은 곱셈일 것이기 때문에 쉽게 gradient를 구할 수 있고, 이를 위해 복잡한 미적분을 할 필요가 없습니다.

x,yx, y 값이 LL 값에 미치는 영향을 알기 위해 Backpropagation을 진행해보면, Loss에 대한 zz의 미분 값이 upstream gradient로 흘러들어오고, 이것을 local gradient 와 곱해주면 될 것입니다. 그리고 나서 우리가 gradient를 구하면 이 노드와 연결된 직전 노드로 전달할 수 있습니다.

이것으로부터 얻을 수 있는 중요한 것은 각 노드는 local gradient를 가지고 있고, 이를 backpropagation을 통해 얻습니다. Gradient 값들은 상위 노드 방향으로 계속 전달되고 우리는 이것을 받아 local gradient와 곱하기만 하면 됩니다. 그래서 노드와 연결된 노드 이외의 다른 어떤 값에 대하여 신경 쓰지 않아도 됩니다.


다음 예제에 대해서 살펴보면,
이번 예시도 동일하게 그래프의 제일 뒤에서부터 역전파를 시작합니다. 그리고 여기서의 gradient는 당연하게도 1입니다.

다음 노드는 xx를 input으로 했을 때, output으로 1/x1/x가 나오게 됩니다. 따라서, local gradient는 output을 input으로 편미분 하는 것입니다. 1/x1/x를 xx로 편미분 하면 1/𝑥2−1/𝑥^2(x-x제곱의 역수)이 됩니다. xx 값이 이 지점에서는 1.37이므로, xx에 1.37을 대입해주면 local gradient를 구할 수 있습니다. 거기에다가, 이전에 넘어온 upstream gradient를 곱해줘야 하기 때문에, (1/1.372)(1.00)=0.53(-1/1.37^2)(1.00) = -0.53이 됩니다.

여기에서 upstream gradient는 -0.53입니다. 그리고 현재 노드는 +1입니다. 아래의 유도식을 다시 살펴보겠습니다. (x+상수)에 대한 local gradient는 1입니다. 이제 chain rule을 이용하면, 이 변수에 대한 gradient는 upstream gradient는 -0.53이고, local gradient는 1 이기 때문에 이 둘을 곱해준 크기인 -0.53이 됩니다.

다음 노드는 exponential을 가지고 있습니다. 그리고 upstream gradient는 -0.53입니다. 따라서, 𝑒𝑥𝑒^𝑥를 xx로 편미분 해주면 되고, 편미분 한 결과는 똑같이 𝑒𝑥𝑒^𝑥가 됩니다. exponential 노드에서, chain rule에 의해 gradient가 0.53𝑒𝑥-0.53 * 𝑒^𝑥임을 알 수 있습니다. 그래서 최종 gradient는 -0.2가 됩니다.

여기서 upstream gradient는 -0.2 입니다. 그리고 local gradient를 계산해보면..
local gradient는 xx에 대한 ff의 미분입니다. 그러므로 이 노드의 local gradient는 -1이 됩니다. 그렇기 때문에 gradient는 -1 * -0.2이므로 0.2가 됩니다.

이전의 간단한 예제에서 덧셈 노드를 가지고 있을 때 덧셈 노드에서, 각 입력에 대한 local gradient는 1이었습니다. 여기에서 local gradient는 upstream gradient 0.2와 한번 곱해져서 gradient는 1 * 0.2 = 0.2입니다. 그리고 우리는 아래(bottom) 브랜치에 대해서도 동등하게 연산이 됩니다.

몇가지 gradient를 더 채워보면, w₀과 x₀이 있는 곳까지 가면 여기에서는 곱셈 노드를 가지고 있습니다. local gradient를 구해보면 곱셈 법칙에 의해 w0w_0의 local gradient는 x0x_0의 값인 -1이 될 것이고, 이전에 넘어온 gradient가 그림에서 0.2가 되므로, 이를 곱해서 -0.2로 구할 수 있게 됩니다. x0x_0도 마찬가지로 local gradient는 w0w_0의 값인 2.00이 될 것이고, 이전에 넘어온 gradient가 그림에서 0.2가 되므로, 이를 곱해서 0.4로 구할 수 있게 됩니다.

여기서 중요한 점은! computational graph를 만들 때, computational 노드에 대해 우리가 원하는 세분화된 정의를 할 수 있는 것입니다. 이전 예시에서 덧셈과 곱셈, 절대적으로 작은 단위로 쪼갰습니다. 실제로 원한다면 이 노드들을 더 복잡한 그룹으로 묶을 수 있습니다. 즉, 노드 하나하나 계산하는 것이 아니라, 여러 개의 노드를 하나의 큰 노드로 바꿀 수 있습니다. 시그모이드 함수에 대해서 이야기해보자면, 이는 1/(1+ex)1 / (1 + e^{-x})와 같습니다.

Sigmoid 함수를 이용해서 식을 간단하게 만들면 (1sigma(x))sigma(x)(1- sigma(x)) * sigma(x)와 같습니다. 그리고 우리는 이것을 하나의 big 노드, sigmoid로 바꿀 수 있습니다. local gradient를 구할 수 있는 한 더 복잡한 노드 그룹을 만들 수 있다는 것입니다. 그리고 이 모든 것은 trade-off의 관계에 있습니다. 간결하고 단순한 그래프를 얻기 위해 당신이 많은 수학을 알고 싶어하는 것과 각 노드에서 단순한 gradient를 얻고 싶어하는 것 사이에서! 그 결과 당신은 원하는 만큼 복잡한 computational graph를 쓸 수 있습니다.

이 노드들을 sigmoid로 그룹화하면, 입력이 1 (초록색으로 쓰여 있음) 출력은 0.73을 갖습니다. 수식에 대입해서 계산해보면 local gradient 값은 0.2가 됩니다. 그리고 upstream gradient는 1이기 때문에 둘을 곱해서 0.2의 gradient 값을 얻을 수 있습니다.

다른 방식으로, σ(x)σ(x)는 0.73이고 이 값을 위의 식에 대입하면 local gradient로 0.2를 얻게 됩니다. 이것을 upstream gradient인 1과 곱합니다. 그러면 sigmoid gate로 바꾸기 이전의 작은 노드들로 계산된 값과 정확히 같은 값을 얻을 수 있습니다. 정리하자면, 복잡한 식의 gradient를 구할 때 computational graph로 연산을 쪼개서 backpropagation과 chain rule을 통해 단순하게 gradient를 계산할 수 있었습니다.

위에서 다룬 내용들인데, computational graph에서 나타나는 패턴이 있습니다. 덧셈 게이트는 gradient를 나눠줍니다. 두 개의 브랜치와 연결되는 덧셈 게이트에서는 upstream gradient를 연결된 브랜치에 정확히 같은 값으로 나눠줍니다.

Mul gate는 local gradient를 계산할 시에 두 개의 input 값이 swap되어지기 때문에 5라는 upstream gradient를 곱해서 각각 15, 10의 값이 생성된다는 것을 알 수 있습니다..

Copy gate는 두 개의 upstream gradient를 더해주는 역할을 합니다.

Max gate를 보면, input으로 4,5가 들어가고 둘 중 큰 값인 9가 출력되는 것을 알 수 있습니다. Max gate는 add gate와 유사하게 gradient를 나눠주는 효과가 있지만, max input을 가진 곳에만 gradient를 나눠준다는 특징이 있습니다.

코드를 살펴보면, output을 연산하는 forward pass 부분과, gradient를 연산하는 backward pass 두 부분으로 나눌 수 있는데.. 순전파(forward)를 함수로 구현하면, 들어오는 input들에 대해서 모두 forward를 해준 뒤 loss를 return 하게 됩니다. 역전파(backward)의 경우에도 모든 게이트에 대해 backward를 해준 뒤 input들이 들어온 방향으로 global gradient를 return 하게 됩니다.

위 코드의 더 자세한 예시로 곱셈 게이트를 직접 구현해봅시다. 우선 forward 함수를 보면, zx, y 에 대한 미분값이 각각 y, x이기 때문에 이 scalar 값들을 다 저장해줘야 합니다. (ctx.save_for_backward(x,y))
그래야 backward 함수에서 gradient 값을 계산할 때 사용할 수 있기 때문입니다. 그리고 그 뒤에 input 에 대한 output 값을 연산하고 return 해주고, backward 함수에서는 grad_z 라는 upstream gradient 값을 입력 받고, grad_z와 local gradient인 x, y 값을 곱해주면 된다.

Scalar to scalar는 우리가 이전에 했던 예시들을 의미하고, vector to scalar는 xx는 벡터 yy값은 scalar 인데, xx의 각 element를 yy에 편미분한 것으로, 각각의 element가 각 요소에 어떤 영향을 미치는지를 의미하고 gradient가 파생된다고 합니다. Vector to vector는 jacobian matrix가 파생되는데 x,yx, y가 모두 vector이고 각 element끼리의 영향력을 판단하기 위해서 NxM 만큼의 편미분이 필요하고 이 때문에 matrix가 생성됩니다.

x,y,zx, y, z는 모두 벡터이고, loss 는 scalar인 예시를 살펴보겠습니다. Scalar 값인 LL에 vector zz의 각 요소를 편미분한 값인 upstream gradient는 vector의 shape를 띄고 있을 것입니다. 즉 vector zz의 각 element들이 LL에 얼마만큼의 영향력을 미쳤는지에 대한 값이 벡터 형식으로 전달된다는 것입니다.
이제 각 x,yx, y 벡터에 대해서 LL의 gradient를 구하고 싶은 경우, chain rule을 이용해 각각의 gradient를 구해야 할 것입니다. 그런데 x,yx, y 모두 각 element에 대한 zz의 local gradient를 구해야 하는데, x,y,zx,y,z가 모두 벡터이기 때문에 xx 그리고 yy의 모든 element들이 zz의 모든 element 들과 편미분 되어야 하고, 그렇게 되면 각각 DxD_x x DzD_z shape의 행렬과, DyD_y x DzD_z shape 의 행렬이 생성되게 됩니다. 이런 행렬을 jacobian matrix라고 부른다고 합니다. 즉, Jacobian 행렬의 각 행은 입력에 대한 출력의 편미분이 될 것입니다.

그러면 local gradient는 행렬이고, upstream gradient는 벡터일 것이다. 둘을 곱하면 gradient를 구할 수 있다. 예를 들어, LLxx로 편미분한 값을 계산하면, local gradient는 DxD_x x DzD_z 이고 upstream gradient는 DzD_z x 11 크기의 벡터일 것이다. 그래서 둘을 곱하면 다시 DxD_x x 11 크기의 벡터가 되기 때문에 결론적으로 downstream gradient는 벡터이다.

결론적으로 변수(x,yx,y)와 gradient는 같은 차원의 벡터가 된다.

4차원 벡터의 입력을 받아 활성화 함수 max(0,x)max(0,x) (ReLU)를 거쳐 4차원 벡터를 출력하는 예시를 살펴보면, 여기서 LL에 대한 input xx의 gradient 값을 알고 싶다면, upstream gradient는 구해져 있기 때문에 local gradient를 계산해야 합니다. 그리고 jacobian matrix는 input이 4차원, output도 4차원 벡터이기 때문에 4x4 matrix가 생성됩니다. 이 예시에서,
jacobian matrix는 Input 값이 하나의 output 값에만 영향을 주기 때문에 대각행렬 형태를 띄고 있습니다..

그래서 jacobian matrix에 upstream gradient 벡터를 곱해주면, Loss에 대한 x의 편미분 값을 구할 수 있습니다.

그런데, Jacobian matrix를 명시적으로 정의하는 것은 좋지 않습니다. 그 이유는 지금은 input, upstream gradient 벡터의 차원이 작아서 괜찮지만, 매우 큰 경우.. 예를 들어 4096 차원만 되더라도 매우 큰 크기의 행렬이 되고, 이렇게 sparse한 matrix의 경우 메모리 공간 낭비도 너무 심해지기 때문입니다.

그래서 행렬을 명시적으로 할당해주는 대신, 이러한 수식을 구현하여 벡터 입력값을 계산해주는 것이 훨씬 효율적입니다.

이번에는 input, output이 모두 행렬인 예제입니다. Upstream gradient는 𝐷𝑧𝐷_𝑧x𝑀𝑧𝑀_𝑧 크기의 행렬로 주어졌고, zzxx로 미분한 값인 local gradients 구해줘야 합니다. 그런데, input 값인 xx의 각 element의 개수가 Jacobian matrix의 행이 되고, output인 zz의 element의 개수가 Jacobian matrix의 열이 되기 때문에, Jacobian matrix는 (Dx(D_x×Mx)M_x)×(Dz(D_z×Mz)M_z) 크기의 행렬이 됩니다. 여기에 upstream gradient를 곱하면 됩니다.

zzyy를 편미분한 local gradient의 값을 구하는 방법도 이와 동일합니다. 이 케이스에서도 LL에 대한 xx의 gradient와 xx 행렬은 shape이 같습니다.


이처럼 행렬의 크기가 너무 커서 메모리를 너무 많이 차지하는 문제점이 발생하게 됩니다.

저 1이 영향을 주는 yy element는? nn 이 1인 경우 즉, 𝑥1,𝑑𝑥_{1,𝑑} 인 경우, yy 행렬과 L/y∂L / ∂y 행렬의 첫번째 행에 모두 영향을 미친다. nn이 2이면 두 행렬의 두번째 행에 모두 영향을 주므로 𝑥𝑛,𝑑𝑥_{𝑛,𝑑} 는 모든 yy의 행에 영향을 준다.

y=wxy = wx 이므로, 𝑦𝑛,𝑚𝑦_{𝑛,𝑚}𝑥𝑛,𝑑𝑥_{𝑛,𝑑}𝑤𝑑,𝑚𝑤_{𝑑,𝑚} 만큼의 영향을 미친다. 이거는 이후에 더 자세히 설명하겠습니다.

Chain rule에 의해서  L/x∂L/∂x = L/y∂L/∂y * y/x∂y/∂x 입니다. 그런데 xn,dx_{n,d} 는 모든 yny_n 행에 영향을 주기 때문에 편미분 값은 ww 입니다. 그런데 행렬 곱셈을 위해서 ww에 transpose를 해줌으로써 NxD라는 xx 값과 같은 shape의 gradient 행렬을 구할 수 있습니다.

비슷하게, chain rule에 의해서 L/w∂L/∂w =  L/y∂L/∂y * y/w∂y/∂w 입니다. 이 경우, local gradient 값은 xn,dx_{n,d} 가 될 것입니다. yy에 대한 ww의 영향은 xx 만큼이기 때문입니다. 이 경우도 마찬가지로 행렬 곱셈을 유용하게 해주기 위해서 xx를 transpose 시켜줍니다.

벡터화된 경우에 어떤 식으로 계산되는지 살펴보겠습니다. xx와 WW는 녹색으로 표시된 것처럼 다음과 같이 나타나고, 이들을 곱한 값을 qq, 그리고 L2 distance를 사용해서 얻은 결과값은 f(q)f(q)로 표기할 수 있습니다. 이전에 했던 것과 마찬가지로 맨 마지막 노드부터 시작합니다. 가장 처음 노드는 항상 1로 시작합니다. 이제 L2 이전의 중간 변수인 qq에 대한 gradient를 찾아야 합니다.

여기 공식을 보면, 각 qq의 element로 편미분한 함수 ff2qi2q_i가 된다고 합니다. 그래서 이 공식을 통해서 local gradient를 구해줍니다. 수식에 대입해주면, Local gradient는 [0.44 0.52] 의 벡터가 되고, upstream gradient는 1이기 때문에 local gradient가 그대로 gradient가 됩니다. 또한 여기서 주목할 수 있는 건, 벡터의 gradient는 원본 벡터와 shape이 동일하다는 점입니다.

WW의 gradient를 구하기 위해서 다시 chain rule을 사용해야 합니다. WW에 대한 qq의 local gradient를 계산하기 원한다면 element별 연산을 다시 해야하고, 이 연산을 통해 Jacobian matrix가 생성될 것입니다. 그런데, 곱셈 gate의 노드일 때의 패턴을 적용해보면, local gradient인 Jacobian 행렬을 굳이 계산해보지 않아도 알 수 있습니다.

식이 되게 복잡해보이는데 하나씩 뜯어보면 일단,

1k=i1_{k=i}는 indicator variable인데요. kkii가 동일할 때는 1이고 그렇지 않으면 0을 나타냅니다.

이게 왜 이런 식으로 나타나는지는 WxW⋅x를 생각해보시면 이해할 수 있습니다.
q1q_1에 해당하는 값은 W11W_{11} × x1x_1 + W12W_{12} × x2x_2로 구하게 됩니다. 즉, kk와 ii가 같을 때만 WW의 성분이 qq에게 영향을 줄 수 있다는 것입니다. 따라서 이렇게 indicator variable이 생겨난 것입니다.

수식을 정리해보면, W/f∂W/∂f는 vectorized term으로 쓰자면 2𝑞𝑥𝑇2𝑞⋅𝑥^𝑇로 표현될 수 있습니다. x를 transpose 시킨 이유는 x를 transpose 시켜야 결과값이 행렬이 나오기 때문입니다.

이렇게 간단한 공식을 통해서 Jacobian을 직접 계산하지 않고 gradient 값을 구할 수 있습니다. 그리고 항상 체크해봐야 할 것은 변수의 shape와 gradient의 shape가 항상 같아야 한다는 것입니다. 

다음으로는 f/x∂f/∂x에 대해서 구해보도록 하겠습니다. 형광펜 쳐둔 곳이 Local gradient를 구하는 공식인데요,

이렇게 공식이 유도된다는 것을 알 수 있습니다.

수식을 정리해보면, f/x∂f/∂x = 2WTq2W^Tq 가 되고, 수식에 대입해서 간단하게 gradient 를 구할 수 있습니다.

profile
열심히 살아보자구

0개의 댓글