ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [cs231n] 4강 역전파와 신경망 (3/4, 역전파 패턴, 벡터, 모듈화, backpropagation pattern, vector, modularization)
    AI 2021. 2. 5. 18:43

    무슨 일이 벌어지는 지 보면, 우리는 우리의 계산 그래프를 거꾸로 흐르는 이 경사를 취하고 있는데요. 여기에 눈치챌 수 있는 어떤 패턴이 있습니다. 그것으로 우리는 어떤 직관적인 해석을 할 수 있는데요. 우리는 더하기 게이트가 경사 배분기라는 걸 봤습니다. 우리가 더하기 노드를 통과하면 2개의 가지가 있고, 업스트림 경사를 받아 들여서 연결된 2개의 가지에 똑같은 값을 통과시켜서 배분하죠. 생각해 볼 게 더 있는데요. 맥스 (max) 게이트는 어떤가요?

    아래쪽에 맥스 게이트가 있는데요. 입력은 z와 w고 z는 2, w는 -1이죠. 우리는 그 둘 중 최대를 취하니까 2고 그걸 우리의 계산 그래프의 나머지 부분으로 전달 시킵니다. 그래서 이것에 대한 경사를 취하면, 업스트림 경사는, 예를 들어 2가 돌아오고 있다면, 이 지역 경사는 어떻게 되죠? 답은 z는 경사 2를 갖고, w는 0을 갖는데, 이들 중 하나가 거꾸로 전달된 경사의 값 전체를 갖죠. 그 변수로 흘러갈 거고, 다른 하나는 0의 경사를 갖죠. 그래서 우리는 이것을 경사 라우터 (router)로 생각할 수 있습니다. 그래서 더하기 노드는 양쪽 노드에 같은 경사를 전달하는 반면, 맥스 게이트는 경사를 받아 들여서 한쪽의 가지로 전달합니다. 이게 말이 되는 것은 순방향 전달을 보면, 최대값만이 계산 그래프의 나머지 부분으로 전달되죠. 그래서 그게 마지막에 우리의 함수 계산에 영향을 준 유일한 값이니까 우리의 경사를 뒤로 전달 할 때도, 그 계산 가지를 거쳐서 그것을 흘려 보내는 것이 말이 되는 겁니다.

    또 다른 것, 곱하기 게이트는 어떤가요? 앞에서 봤죠. 어떻게 해석 할 수 있죠?

    답은 지역 경사는 다른 변수의 값이죠. 우리는 이걸 경사 스위처 (gradient switcher)라고 할 수 있습니다. 스위처이자, 계수기 (scaler)죠. 업스트림 경사를 받아서 다른 가지의 값으로 곱하니까요.

    주목할 점은 하나의 노드가 여러 개의 노드로 연결될 때, 이 노드에서 경사들이 더해집니다. 이 가지들에서 다변수 체인 룰 (multi-variable chain rule)을 사용해서, 우리는 이 노드들에서부터 올라오는 업스트림 경사의 값을 받아 들입니다. 우리는 이것들을 더해서 총 업스트림 경사 (total upstream gradient)를 얻는데, 그게 이 노드로 흘러 들어오죠. 다변수 체인 룰로부터 보고 이것을 생각해 보면, 이 노드를 약간 변경하면, 그래프에서 순뱡향 진행을 하면 순방향 경로에서 연결된 두 노드 모두에 영향을 줍니다. 역전파를 할 때는, 이 올라오는 경사 두개 모두가 이 노드에 영향을 줍니다. 그래서 그게 우리가 이 노드로 올라오는 총 업스트림 경사를 더해서 얻는 이유입니다.

    이제 스칼라 (scalar) 경우는 다 봤고, 벡터 (vector)인 경우엔 어떻게 되는지 보죠. 우리의 변수가 x, y, z라면, 단지 숫자인게 아니라, 이에 대한 벡터들을 가집니다. 전체적인 흐름은, 모든 건 동일한데, 유일하게 다른 점은 우리의 경사가 야코비 행렬 (Jacobian matrix)이 된다는 겁니다. 이제 이것들은 모든 항목들의 미분을 포함하는 행렬이 됩니다. 예를 들면, 모든 x의 항목에 대한 z의 모든 항목에 대한 미분이죠.

    예를 들어, 우리의 입력이 벡터라면 , 4096차원 입력 벡터일 수 있겠죠. 나중에 합성곱 신경망 (convolutional neural network)을 볼 때, 흔히 보는 사이즈인데요. 우리의 노드는 원소마다의 (element-wise) 최대가 됩니다. 그래서 우리가 x의 f가 0과 항목마다 x를 비교하여 최대값인 거고, 우리의 출력은 역시 4096차원 벡터가 됩니다. 이 경우, 우리의 야코비 행렬의 크기는 얼마일까요?

    앞에서 얘기했듯이, 야코비 행렬은 각 열이 부분 미분이고, 즉, 입력의 각각의 차원에 대한 출력의 각각의 차원에 대한 부분 미분 행렬입니다.

    답은 4096의 제곱이죠. 이건 꽤 크죠. 4096 곱하기 4096이니까요.

    실제로 이것보다 더 클 수 있습니다. 왜냐면 우리는 미니배치 (minibatch)로 작업할 건데, 100개의 입력이 한번에 들어오니까요. 우리는 이모든 것을 우리의 노드에 동시에 넣는 것이 더 효율적이죠. 그래서 이게 100배가 될 거고, 실제로 우리의 야코비는 409,000 곱하기 409,000가 됩니다. 이건 정말 엄청난데, 사실상 완전히 작업하는 것이 어렵습니다.

    실제에서는 대부분의 경우 우리는 이 거대한 야코비를 계산할 필요가 없습니다. 왜 그럴까요? 이 야코비 행렬은 어떻게 생겼을까요? 여기서는 우리가 항목 별로 최대를 취하고 있고, 부분 미분의 각각이 뭔지 생각하면, 입력의 어떤 차원이 출력의 어떤 차원에 영향을 줄까요? 야코비 행렬에서 어떤 종류의 구조를 볼 수 있을까요? 대각선 (diagonal)이죠. 이게 원소마다이기 때문에, 입력의 첫번째 항목은, 예를 들어, 1차원은 출력의 해당하는 항에만 영향을 줍니다. 이것 때문에 우리의 야코비 행렬은 대각선 행렬이 됩니다. 실제로, 우리는 이 야코비 전체를 작성하거나 만들 필요가 없고 우리는 그냥 출력에 대한 x의 영향을 알 수 있습니다. 우리는 그냥 이 값들을 사용할 수 있습니다. 그리고 그걸 경사를 계산하면서 집어 넣을 수가 있습니다.

    이제 우리는 계산 그래프의 더 구체적인 벡터화된 예를 보겠습니다. 여기 x와 W의 함수 f가 있고 사실 W와 x의 곱에 L2와 같죠.

    이 경우 x는 n차원이고, W는 n 곱하기 n이죠.

    다시 우리의 첫 단계는 계산 그래프를 그리는 거죠. 우리는 W유와 x의 곱이 있고, 그다음 L2가 있습니다.

    이것을 위한 몇 가지 값들을 적어보죠. W는 2 x 2 행렬이고, x는 2차원 벡터입니다. 우리의 중간 노드에 레이블을 적어보면, 우리의 곱하기 뒤의 중간 노드는 q인데, q는 W 곱하기 x죠. 우리는 원소마다 이렇게 적을 수 있고, 첫번째 항을 보면 W1,1 곱하기 x1 더하기 W1,2 곱하기 x2 등등이죠. 그 다음 우리는 q에 대한 f를 표현할 수 있습니다. 두번째 노드를 보면, q에 대한 f가 있고, q의 L2 놈 (norm)이고 그건 q1 제곱 더하기 q2 제곱이죠.

    그걸 적었구요. 우리는 q를 얻고 우리의 최종 출력을 얻죠.

    이제 이걸로 역전파를 해보죠. 첫번째 단계로 우리의 출력에 대한 경사는 그냥 1이죠.

    한 노드 뒤로 가보죠. 우리는 q에 대한 경사를 얻고 싶습니다. q는 L2 앞의 중간 변수죠. q는 2차원 벡터인데요. 우리가 하고 싶은 건 q의 각 원소가 어떻게 f의 최종 값에 영향을 주는 지 알아 내는 것입니다. 여기 아래쪽에 f를 위해 작성한 식을 보면, 예를 들어, q1에 대한 f의 경사는 2*q1이 될 겁니다. 이건 그냥 미분 값을 취하죠. 우리는 qi의 각 원소에 대한 표현이 여기 있는데, 원한다면 벡터 형태로도 쓸 수 있죠. 그건 2곱하기 벡터 q가 되죠.

    그래서 우리가 얻는 것은 경사 0.44와 0.52인 벡터입니다. 그래서 여러분은 q를 받아서 그냥 두배한다는 걸 알 수 있죠. 각 원소를 두배하는 겁니다. 벡터의 경사는 항상 원래 벡터와 같은 크기가 될 겁니다. 그리고 경사의 각 원소는 이 특정 원소가 우리의 함수의 최종 출력에 어떻게 영향을 미치는 지를 의미합니다.

    한 단계 뒤로 가보죠. W에 대한 경사는 얼마죠? 여기서 우리는 다시 체인 룰을 적용하는 개념을 사용하고 싶은데요, 우리는 W에 대한 q의 지역 경사를 계산하고 싶습니다. 이걸 다시 W의 각 원소에 대한 q의 각 원소의 영향이 뭔지 보죠. 이건 우리가 앞에서 얘기한 야코비가 될 건데요. 이 곱하기를 보면, q는 W 곱하기 x죠. q의 첫번째 원소의 경사는 얼마죠? 그러니까 W1,1에 대한 q1말입니다. x1이죠. 이걸 더 일반적으로 써 보면, Wi,j에 대한 qk의 경사는 xj와 같죠.

    만약 우리가 Wi,j에 대한 f의 경사를 찾고 싶다면, 이 미분을 보죠. 우리는 앞서 얘기한 이 체인 룰을 사용해서 우리는 q의 각각의 원소에 대한 dqk분의 df와 Wi,j의 각각의 원소에 Wi,j분의 dqk를 곱할수 있습니다. 그래서 우리는 q의 각 원소에 대한 W의 각 원소에 대한 영향을 찾을 수 있고, 모든 q에 대해서 이걸 합치는 겁니다. 이걸 적는다면, 2 * qi * xj라고 할 수 있죠.

    이걸 여기에 적으면, 우리는 W에 대한 경사를 얻습니다. 다시 각 원소별로 계산할 수 있구요.

    우리가 유도한 이 식을 볼 수 있습니다. 그리고 벡터화된 형식으로 적을 수 있습니다.

    기억해야 할 중요한 점은 변수에 대한 경사를 항상 체크해야 한다는 겁니다. 변수와 같은 형태를 가져야 하는데, 이건 실제에서 무결성 검사에 매우 유용합니다. 경사가 무엇인지 일단 계산했으면, 변수와 같은 형태인지를 체크해야죠. 왜냐면, 경사의 각 원소는 그 원소가 여러분의 최종 출력에 얼마나 영향을 미치는 지를 정량화 하는 것이니까요.

    예를 하나 더 보죠. 마지막으로 찾아 볼 것은 qi에 대한 경사입니다. 우리가 부분 미분을 하면, dxi분의 dqk는 Wk,i죠. W에 대해서 했던 것과 마찬가지로, 우리는 체인 룰을 사용해서 그에 대한 전체식을 얻을 수 있습니다.

    이건 x에 대한 경사고, x와 같은 형태죠. 원한다면 벡터화된 형태로 적을 수 있습니다.

    우리가 이걸 생각하는 방식은 모듈화된 (modularized)구현 같은 것인데요. 우리의 계산 그래프에서 우리는 각 노드를 그 지역으로 보고 지역 경사를 계산하고 내려오는 업스트림 경사를 그것들과 연결하죠. 이것을 사실  forward와 backward API로 생각할 수 있습니다. forward에서 이 노드의 출력을 계산하는 함수를 구현하고, backward에서 우리는 경사를 계산하죠. 이걸 코드로 실제 구현할 때, 같은 방식으로 할 수 있습니다. 그래서 각 게이트에 대해서, 만약 우리가 forward 함수와 backward 함수를 구현한다면, backward는 체인 룰을 계산할거고, 그리고 우리가 전체 그래프를 가진다면, 우리는 전체 그래프에 걸쳐서 그래프의 모든 노드와 게이트를 하나 하나 지나가면서 forward를 만들 수 있죠. 게이트와 노드라는 단어를 바꿔가며 쓰고 있는데요. 우리는 모든 게이트를 하나씩 지나가면서 각각의 게이트의 forward 함수를 호출할 수 있죠. 우리는 이걸 위상 정렬 (topological order) 순서로 하고 싶고, 어떤 노드에 들어오는 모든 입력을 처리하고 그 노드를 지나갑니다. 뒤로 갈때는, 모든 게이트를 역순으로 정렬된 순서로 지나가죠. 그리고 이 게이트들에서 backward 함수를 호출합니다.

    특정 게이트에 대한 구현을 보면, 예를 들어 곱하기 게이트를 보면 우리는 forward를 구현하고 싶은데, x와 y를 입력으로 받아서, z값을 반환하죠. 뒤로 갈 때는, dz를 입력으로 받는데, 그건 업스트림 경사구요. 아래로 내려가는 x와 y에 대한 경사를 출력하고 싶은 거죠. 그래서 dx와 dy를 출력합니다.

    이 경우는 모든 것이 스칼라 (scalar)인 경우인데요. 순방향으로 보면, 중요한건 forward의 모든 값을 저장하고 있어야 합니다. 왜냐면 많은 경우 backward가 이걸 사용하기 때문이죠. 여기 forward에서, x와 y를 저장하고 싶고, backward에서, 체인 룰을 사용해서 , 업스트림 경사의 값을 취하고 다른 가지의 값을 곱합니다. 그래서 dx에 대해서 저장했던 self.y의 값을 dz로 곱하는 거죠. dy에 대해서도 같습니다.

    많은 딥러닝 프레임워크 (framework)와 라이브러리 (library)를 보면, 그것들은 정확하게 이런 모듈화를 따르고 있습니다. 예를 들어 카페 (Caffe)는 인기있는 딥러닝 프레임워크인데, 카페의 소스코드를 보면, 레이어즈 (layers)라는 디렉토리를 볼 수 있을 거고, 레이어즈라는 것은 사실 계산 노드들인데요. 일반적으로 레이어즈는 앞에서 얘기한 시그모이드와 같은 좀더 복잡한 계산 노드들입니다. 여러분은 다양한 모든 종류의 계산 노드들을 볼 수 있습니다. 시그모이드, 합성곱도 있고 아그맥스 (argmax)도 레이어죠. 이 모든 레이어들을 다 볼 수 있는데, 하나씩 들여다 보면, 동일하게 포워드 패스 (forward pass)와 백워드 패스 (backward pass)를 구현하고 있습니다. 우리가 만든 전체 네트워크를 순방향과 역방향으로 진행할 때 이 모든 것들이 호출됩니다. 우리의 망은 사실 망에서 사용하고자 하는 이 모든 레이어들을 쌓아 올리는 것이죠.

    구체적인 것을 보면, 이 경우는 시그모이드 레이어인데, 이런 것을 볼 수 있습니다. forward pass가 있고, 사실 정확히 시그모이드 표현을 계산하고 있죠. 그리고 backward pass도 있고, top_diff라는 입력을 받아 들이죠. 이 경우엔 업스트림 경사인데요. 그걸 우리가 계산한 지역 경사로 곱합니다.

    지금까지 얘기한걸 요약하면, 우리는 신경망으로 작업하기 시작했고, 이것들은 진짜 커지고 복잡해 질 수 있죠. 그래서 여러분의 모든 파라미터에 대해서 경사 공식을 손으로 작성하는 것은 비실용적이죠. 이 경사들을 구하기 위해서, 우리가 사용해야 하는 것은 역전파라고 했습니다. 이건 신경망의 핵심 테크닉 중 하나인데, 이건 반복적으로 체인룰을 적용하는 거고, 계산 그래프를 만들고, 뒤에서 부터 거꾸로 거쳐가면서 모든 중간 변수에 대해 경사를 계산하는 거죠. 변수들은 입력, 파라미터와 중간에 있는 모든 것이죠. 또한 이 구현과 그래프 구조, 각각의 노드가 어떤지에 대해 얘기했습니다. forward pass와 backward pass API를 구현하면서 봤죠. 순방향에선 연산의 결과를 계산하고자 했고, 어떤 중간 값들을 저장해서 나중에 경사 계산에서 사용하고자 했습니다. 역방향에서는 이 체인 룰을 적용해서 업스트림 경사를 취하고 그걸 지역 경사와 곱해서 노드의 입력에 관한 경사를 계산하죠. 그리고 그 다음 노드로 이걸 넘겨 줍니다.

    댓글

Designed by Tistory.