728x90
반응형
SMALL
728x90
반응형
SMALL

지난 시간 역전파의 덧셈과 곱셈 노드에 대하여 알아보는 시간을 가졌습니다.

2024.02.22 - [Programming/Deep Learning] - [Python/DeepLearning] #10.4. 역전파) 덧셈 노드와 곱셈 노드

 

[Python/DeepLearning] #10.4. 역전파) 덧셈 노드와 곱셈 노드

지난 글을 통해 계산 그래프의 역전파가 연쇄 법칙에 따라서 진행되는 모습을 이야기해 보았습니다. 2024.02.15 - [Programming/Deep Learning] - [Python/DeepLearning] #10.3. 역전파) 계산 그래프ᄅ

yuja-k.tistory.com

이번 시간에는 역전파의 완전 마지막! 활성화 함수 계층을 구현해 보도록 하겠습니다! 이 이상의 #10을 쪼개진 않겠습니다...

활성화 함수 계층 구현

 

드디어 지금 만든 계산 그래프를 신경망에 적용시켜 볼 수 있습니다!

여기에서는 신경망을 구성하는 층(계층) 각각을 클래스 하나씩으로 구현했는데요. 우선은 활성화 함수인 ReLU와 Sigmoid 계층을 구현해 보겠습니다!

 

ReLU 계층 만들기

먼저. 활성화 함수로 사용되는 ReLU의 수식은 다음과 같습니다.

위의 식을 미분하면 다음처럼 구할 수 있습니다.

순전파 때의 입력인 x가 0보다 크면 역전파는 상류의 값을 그래도 하류로 흘립니다.

반면에 순전파 때 x가 0 이하면 역전파 때는 하류로 신호를 보내지 않습니다.( 0을 보내기 때문입니다).

계산 그래프로는 다음 처럼 그릴 수 있겠네요!

 

그럼 본격적으로 ReLU 계층을 구현해 보도록 하겠습니다!

신경망 계층의 forward()와 backward() 함수는 넘파이 배열을 인수로 받는다고 가정합니다.

 

신경망 레이어 만들기
  • ReLU
  • Sigmoid
  • Affine layer( 기하학 레이어 - Fully Connected, Dense )
  • SoftMax + Loss layer
ReLU 구현
class ReLU:
  # mask : 순전파 시에 0이나 음수였던 인덱스를 저장하기 위함이다.
  # mask가 있어야 순전파 때 0이었던 부분을 역전파 때 0으로 만들어 줄 수 있다.
  def __init__(self):
    self.mask = None

  def forward(self, x):
    self.mask = (x <= 0)# 매개변수로 들어온 넘파이배열 x의 원소가  0이하인지 판단하기
    out = x.copy() # 원본 배열 복사
    out[self.mask] = 0 # 0보다 작은 원소들을 0으로 만들기

    return out

  # 순전파 때 음수였던 부분을 0으로 만든다.
  # 음수였었던 인덱스를 기억하고 있다가 (self.mask) 미분값 전달시에 해당 인덱스를 0으로 만든다.
  def backward(self, dout):
    dout[self.mask] = 0 # 상류에서 들어온 값에서 0보다 작은 값들에 대해 0으로 치환
    dx = dout # 완성된 ReLU 배열 리턴

    return dx

완성된 ReLU 계층을 np.array 배열을 넣어서 테스트해보겠습니다.

x = np.array([ [1.0, -0.5],
               [-2.0, 3.0] ])

print(x)

relu = ReLU()
relu.forward(x)

relu.mask

dx = np.array([ [-0.1, 4.0],
                [1.3, -1.1] ])

relu.backward(dx)

Sigmoid 계층 만들기

 

다시 한번 시그모이드 함수를 살펴보도록 하죠!

위 식을 계산 그래프로 그리면 다음과 같습니다.

 

X와 + 노드 말고 exp와 '/' 노드가 새롭게 등장했는데요, exp 노드는 𝑦=𝑒𝑥𝑝(𝑥) 계산을 수행하고, '/' 노드는 𝑦=1𝑥를 수행합니다. 그림과 같이 시그모이드의 계산은 국소적 계산의 순전파로 이루어집니다. 이제 역전파를 알아볼 텐데요, 각 노드에 대한 역전파를 단계별로 알아보도록 하겠습니다.

1단계

'/' 노드를 미분하면 다음과 같습니다.

위처럼 상류에서 흘러 들어온 값에 𝑦2(순전파의 출력을 제곱한 후 음수를 붙인 값)을 곱해서 하류로 전달시키게 됩니다. 계산 그래프에서는 다음과 같습니다.

2단계

'+' 노드는 상류의 값을 여과 없이 하류로 보냅니다. 계산 그래프의 결과는 다음과 같아집니다.

3단계

'exp'노드는 𝑦=𝑒𝑥𝑝(𝑥) 연산을 수행하며, 그 미분은 다음과 같습니다.

계산 그래프에서는 상류의 값에 순전파 때의 출력(이 예에서는 𝑒𝑥𝑝(𝑥))을 곱해 하류로 전파합니다.

4단계

제일 마지막 X 노드는 순전파 때의 값을 서로 바꿔 곱합니다. 이 예에서는 -1을 곱하면 될 것 같습니다.

총 4 단계를 거쳐 Sigmoid 계층의 역전파를 계산 그래프로 완성해 보았습니다. 역전파의 최종출력인 𝐿/𝑦*𝑦2*𝑒𝑥𝑝(𝑥)) 값이 하류 노드로 전파됩니다. 그런데 𝐿/𝑦*𝑦2*𝑒𝑥𝑝(𝑥)를 순전파의 입력 𝑥와 출력 𝑦만으로 계산할 수 있다는 것을 알 수 있는데요, 따라서 계산 중간 과정을 모두 묶어서 다음처럼 단순한 그림으로 표현이 가능합니다.

또한 𝐿/𝑦*𝑦2*𝑒𝑥𝑝(𝑥)는 다음 처럼 정리할 수 있습니다.

이처럼 Sigmoid 계층의 역전파는 순전파의 출력 y 만으로도 계산할 수 있습니다.

그렇다면, Sigmoid 계층을 파이썬으로 직접 만들어 보겠습니다.

Sigmoid 구현하기
class Sigmoid:
  def __init__(self):
    self.out = None

  # 순전파
  def forward(self, x):
    out = 1 / ( 1 + np.exp(-x) )
    self.out = out

    return out

  # 역전파
  def backward(self, dout):
    dx = dout * self.out * (1.0 - self.out)
    return dx

 

이 구현은 단순히 순전파의 출력을 인스턴스 변수 out에 보관했다가, 역전파 계산 때 그 값을 사용합니다.

지금까지 역전파의 단계별 구현을 해보았습니다. 

 

다음 시간에는 오차역전파의 Affine/Softmax 계층 신경망의 순전파에 대하여 구현해 보도록 하겠습니다.

728x90
반응형
LIST
728x90
반응형
SMALL

지난 시간, 수식을 통한 오차역전파법에 대하여 이해해 보았습니다.

2024.02.08 - [Programming/Deep Learning] - [Python/DeepLearning] #10.2. 역전파) 수식을 통한 오차역전파법 이해

 

이번 시간에서는 계산 그래프를 통한 역전파에 대하여 알아보도록 하겠습니다!

오차역전파법을 위한 계산 그래프

 

일전에 수식으로 풀어본 오차역전파법은 수학을 오랫동안 놓았거나 수식으로만 생각하면 본질을 놓칠 우려가 있습니다. 이번에 우리가 해볼 내용은 계산 그래프를 이용해 오차역전파법을 이해하는 것인데요, 수식으로 오차역전파법을 이해하는 것보다는 약간은 부정확할 수 있으나 최종적으로는 수식으로 알아본 오차역전파법을 이해할 수 있고, 실제 코드 구현까지 해보도록 하겠습니다. 계산 그래프로 설명한다는 아이디어는 안드레 카패스의 블로그 또 그와 페이페이 리 교수가 진행한 스탠퍼드 대학교 딥러닝 수업 CS321n을 참고했습니다.

 

계산 그래프

 

계산 그래프(computational graph)는 계산 과정을 그래프로 그려낸 것입니다. 그래프는 우리가 잘 아는 그래프 자료 구조 형태로 되어 있으며, 처음에 쉽게 접근하기 위해 계산 그래프를 통한 간단한 문제를 풀어보도록 하겠습니다. 먼저 익숙해지자!라는 이야기입니다. 예를 들어 다음과 같은 예시가 있다고 하죠, "A라는 사람이 1개 100원인 사과를 2개 샀습니다. 이때 지불 금액을 구하세요, 단 소비세 10%가 부과됩니다."라는 예시를 계산그래프로 표현하면 다음과 같아집니다.

 

처음에 사과의 100원이 'x 2' 노드로 흘러 200원이 된 다음 소비세 계산을 위해 'x 1.1' 노드를 거쳐 최종적으로는 220원이 됩니다. 위 그래프에 따르면 최종 답은 220원이 된다는 사실을 알 수 있네요 위의 그림에서는 계산 노드를 각각 'x 2', 'x 1.1'로 표현했지만 '2'와 '1.1'을 각각 사과의 개수와 소비세에 대한 변수가 되기 때문에 따로 빼서 다음과 같이 표기할 수 있습니다.

 

그럼 다음 문제를 풀어 보도록 하겠습니다.

"A가 사과를 2개, 귤을 3개 샀습니다. 사과는 1개에 100원, 귤은 1개 150원입니다. 소비세가 10% 부과될 때 A가 지불해야 할 금액은?" 위 문제도 계산그래프로 풀어볼 수 있습니다. 이때의 계산 그래프는 다음과 같겠네요!

 

위 문제에서는 새로운 노드인 덧셈 노드가 추가되었습니다. 덧셈 노드가 추가되어 사과의 가격과 귤의 가격을 합치는 모습이 보이고 있습니다. 왼쪽에서 오른쪽으로 순차적으로 계산을 끝내고 제일 마지막에 1.1을 곱하면 우리가 원하는 값인 715원이 나오고 끝나게 됩니다. 계산 그래프를 이용한 문제풀이는 다음과 같이 해석할 수 있습니다.

  1. 계산 그래프를 구성
  2. 그래프에서 계산을 왼쪽에서 오른쪽으로 진행

이처럼 '계산을 왼쪽에서 오른쪽으로 진행'하는 단계를 순전파(forward propagation)라고 합니다. 순전파는 계산 그래프의 출발점부터 종착점으로의 전파단계를 그려줍니다. 역전파(backword propagation)는 무엇일까요? 바로 '오른쪽에서 왼쪽으로 전파되는 단계를 의미합니다!


국소적 계산

 

계산 그래프의 특징은 '국소적 계산'을 전파함으로써 최종 결과를 얻는다는 점에 있습니다. 여기서 '국소적'이란, "자신과 직접 관계된 작은 범위"를 의미하는데, 뭔가 떠오르지 않으시나요? 수학으로 따지면 바로 편미분을 의미한다는 것입니다. 즉, 국소적 계산은 전체에서 어떤 일이 벌어지든 상관없이 자신과 관계된 정보만을 토대로 결과를 낼 수 있다는 이야기입니다. 구체적인 예를 들어 보겠습니다. 여러분이 마트에서 사과 2개를 포함한 여러 가지의 물품들을 구매하는 상황을 구해보겠습니다. 그렇다면 사과에 대한 국소적 계산을 진행한다고 이해할 수 있는데요, 그래프로 확인해 보겠습니다.

 

위 그림에서 여러 식품을 구매하여( 복잡한 계산을 하여) 4,000원이라는 금액이 나왔고, 여기에 사과 가격인 200원을 더해 총 4,200원이 나왔습니다. 이는 '사과에 대한 국소적 계산'이기 때문에, 4,000원이 어떻게 나왔는지는 전혀 신경 쓸게 없다는 이야기가 됩니다. 그냥 단순히 복잡한 계산의 결과물인 4,000원과 사과의 가격인 200원을 더해 4,200을 알아내면 된다는 것이죠. 중요한 점은 계산 그래프는 이처럼 국소적 계산에 집중한다는 것입니다. 전체 계산 자체가 아무리 복잡해도 각 단계에서 하는 일은 해당 노드의 '국소적 계산'일뿐입니다. 국소적 계산은 단순하지만 그 결과를 전달함으로써 전체를 구성하는 복잡한 계산을 해낼 수 있습니다. 마치 자동차 조립을 하는 것과 비슷한데요, 각각의 부품을 복잡하게 만들어 내고, 최종적으로 합쳐 차를 완성하는 단계라고 볼 수 있습니다.

 

계산 그래프를 사용하는 이유

 

계산 그래프의 이점은 무엇일까요? 바로 국소적 계산입니다. 전체가 아무리 복잡해도 각 노드에서는 단순한 계산에 집중하여 문제를 단순화시킬 수 있기 때문이지요, 또한 계산 그래프는 중간 계산 결과를 모두 보관할 수 있습니다. 에지에 저장되어 있는 숫자들이 그것을 의미하고 있지요, 하지만 이것 때문에 계산 그래프를 사용하진 않습니다! 계산 그래프를 사용하는 가장 큰 이유는 역전파를 통해 '미분'을 효율적으로 계산할 수 있기 때문입니다.

계산 그래프의 역전파 첫 번째 문제에 대한 계산 그래프는 사과 2개를 사서 소비세를 포함한 최종 금액을 구하는 것이었습니다. 여기서 새로운 문제를 제시해 보겠습니다. "사과 가격이 오르면 최종 금액에 어떠한 영향을 미칠 것인가?"가 문제입니다. 즉 이는 사과 가격에 대한 지불 금액의 미분을 구하는 문제에 해당됩니다. 사과 값을 x로, 지불 금액을 L이라 했을 때

로 표현이 가능하다는 것이죠, 즉 이 미분값은 사과 값이 '아주 조금' 올랐을 때 지불 금액이 얼마나 증가하느냐를 표시한 것입니다. 즉, '사과 가격에 대한 지불 금액의 미분' 같은 값은 계산 그래프에서 역전파를 하면 구할 수 있게 됩니다. 다음 그림에서는 계산 그래프 상의 역전파에 의해 미분을 구할 수가 있습니다. 아직 역전파가 어떻게 이뤄지는지에 대해서는 이야기하지 않았습니다!

위 그림에서 굵은 화살표로 역전파를 표현해 보았습니다. 이 전파는 각각 노드에 대한 국소적 미분을 전달합니다. 즉, 들어오고 있는 사과의 개수나 소비세에 대한 국소적으로 미분을 진행하였기 때문에, 소비세와 사과의 개수 같은 변수에 대한 미분만 진행했다는 이야기입니다. 그리고 그 미분값은 화살표 방향으로 적어내고 있습니다. 이 예에서 역전 파는 오른쪽에서 왼쪽으로 '1 -> 1.1 -> 2.2' 순으로 미분값을 전달하고 있습니다. 이 결과로부터 알 수 있는 사실은 '사과 가격에 대한 지불금액이 미분'값은 2.2라는 것을 알 수 있게 됩니다. 즉, 사과 가격이 1원 오르면 최종 가격은 2.2원 오른다는 것이죠. 여기에서는 사과 가격에 대한 미분만 구했지만, '소비세에 대한 지불 금액의 미분'이나 '사과 개수에 대한 지불 금액의 미분'도 같은 순서로 구해낼 수가 있습니다. 그리고 그때는 중간까지 구한 미분 결과를 공유할 수 있어서 다수의 미분을 효율적으로 계산할 수 있습니다. 이처럼 계산 그래프의 이점은 순전파와 역전파를 활용해서 각 변수의 미분을 효율적으로 구할 수 있다는 것입니다.

 

연쇄법칙과 계산 그래프

 

연쇄법칙 계산을 계산 그래프로 나타낼 수 있습니다. 2 제곱 계산을 '**2' 노드로 나타내면 다음과 같습니다.

오른쪽에서 왼쪽으로 신호가 전파되는 모습을 볼 수 있습니다. 역전파에서의 계산 절차는 노드로 들어온 입력 신호에 그 노드의 국소적 미분인 편미분을 곱한 후 다음 노드로 전달합니다. 예를 들어 **2 노드에서의 역전파를 보면 입력은 𝑧∂𝑧이며, 이에 대한 국소적 미분인 𝑧𝑡를 곱해 다음 노드로 넘깁니다. 맨 왼쪽의 역전파를 보면 x에 대한 z의 미분이 연쇄법칙에 따라서

가 된다는 사실을 알아낼 수 있고, 이를 계산하면

가 된다는 사실을 알아낼 수 있습니다.

지금까지 아주아주 긴 오차역전파법을 위한 계산 그래프를 위한 이해를 수식으로 알아보았습니다! 다음 세션을 통해 최종적으로 코드 구현을 해보겠습니다.

728x90
반응형
LIST

+ Recent posts