"오차역전파법"의 두 판 사이의 차이

잔글
 
(같은 사용자의 중간 판 5개는 보이지 않습니다)
8번째 줄: 8번째 줄:
 
= 사용예 =
 
= 사용예 =
 
각 층을 클래스로 만들고, 해당 레이어의 전방전파, 후방전파 메서드를 만들어 구현한다.
 
각 층을 클래스로 만들고, 해당 레이어의 전방전파, 후방전파 메서드를 만들어 구현한다.
 +
 +
처음 출발하는 수는 1이다.  <math>\frac{\partial L}{\partial L} =1</math>이므로.
  
 
== Relu ==
 
== Relu ==
42번째 줄: 44번째 줄:
 
# <math>y= -x</math>의 미분 -1을 곱한다.
 
# <math>y= -x</math>의 미분 -1을 곱한다.
  
이들의 결과는 <math>y^2\exp(-x)</math>인데, <math>y= \frac{1}{1+\exp(-x)}</math>이므로 정리하면 <math>y(1-y)</math>가 된다. 즉, 순전파의 출력 y로부터 역전파를 구하게 되었다.<syntaxhighlight lang="python">
+
이들의 결과는 <math>y^2\exp(-x)</math>인데, <math>y= \frac{1}{1+\exp(-x)}</math>이므로 정리하면 <math>y(1-y)</math>가 된다.(사실, 그냥 미분을 계산해도 뭐;;) 즉, 순전파의 출력 y로부터 역전파를 구하게 되었다.<syntaxhighlight lang="python">
 
class Sigmoid:
 
class Sigmoid:
 
     def __init__(self):
 
     def __init__(self):
58번째 줄: 60번째 줄:
  
 
== softmax ==
 
== softmax ==
 +
원리는 다음 affine 계층에서의 역전파를 참고하자.
 +
 +
일반적으로 Cross entropy error층과 함께 사용된다. 교차 엔트로피의 수식은 <math>L=-\sum_{k}t_k \ln y_k</math>이다. 역전파가 들어오면
 +
 +
=== 크로스엔트로피의 역전파 ===
 +
 +
* +의 역전파 -1을 그대로 전달하고,
 +
* 위에 곱해 *의 역전파 두 입력을 바꾸어 각 노드에 <math>-t_k</math>가  전달되고,
 +
* 위에 곱해 log의 역전파 <math>-\frac{t_k}{y_k}</math>를 전달한다.
 +
 +
=== 소프트맥스의 역전파 ===
 +
 +
* 입력값으로 <math>-\frac{t_k}{y_k}</math>가 들어오고,
 +
* 나누어주는 값으론 <math>\frac{t_1+t_2+...}{S}</math>인데, t_k는 정답레이블이므로 다 더하면 1이 된다. 즉, 분모의 시그마로 나누는 과정의 역전파는 <math>\frac{1}{S}</math>
 +
* 각각의 분자에는 <math>-\frac{t_k}{S y_k}</math>가 들어오고 exp연산에서 <math>y_k -t_k</math>가 결과로 나오게 된다.
  
 +
=== 구현 ===
 +
<syntaxhighlight lang="python">
 +
class SoftmasWithLoss:
 +
    def __init__(self):
 +
        self.loss = None  # 손실함수값을 저장하기 위한 변수.
 +
        self.y = None  # 출력값을 저장하기 위한 변수.
 +
        self.t = None  # 정답레이블
 +
       
 +
    def forward(self, x):
 +
        self.t = t
 +
        self.y = softmax(x)  # 이전에 구현한 소프트맥스 함수값을 넣는다.
 +
        self.loss = cross_entrop_error(self.y, self.t)  # 오차함수로 오차값을 얻는다. 내 생각엔 backward에서 다뤄도 될 것 같은데..
 +
        return self.loss  # 포워드니까 y값이 나와야 하는 거 아닌감??
 +
    def backward(self, dout):
 +
        batch_size = self.t.shape[0]  # 데이터 갯수
 +
        dx = (self.y - self.t) / batch_size  # 역전파값을 데이터 갯수로 나눈다.(데이터를 여러 개 넣었을 경우.)
 +
        return dx
 +
</syntaxhighlight>
 
== affine ==
 
== affine ==
 
딥러닝의 중간 층들에선 행렬 계산이 이루어지는데, 이런 행렬의 곱을 affine변환이라 부른다. 때문에 중간 뉴런들에 대한 층은 어파인층이라 부르고, 이들의 역전파를 통해 어파인층 학습도 가능하다.
 
딥러닝의 중간 층들에선 행렬 계산이 이루어지는데, 이런 행렬의 곱을 affine변환이라 부른다. 때문에 중간 뉴런들에 대한 층은 어파인층이라 부르고, 이들의 역전파를 통해 어파인층 학습도 가능하다.
 +
 +
오차함수에 대하여 각 입력에 대한 편미분은 <math>\frac{\partial E}{\partial X} = \left ( \frac{\partial E}{\partial x_1} , \frac{\partial E}{\partial x_2} , \ldots    \right )</math> 형태이다. 핵심은 <math>\left ( X+\frac{\partial E}{\partial X}  \right ) W = \left ( Y + \frac {\partial E}{\partial Y}  \right )</math>에서 <math> X W = Y</math>이므로 <math>\frac{\partial E}{\partial X} W = \frac{\partial E}{\partial Y}</math>이런 관계를 갖는다는 말이다. 즉, <math>\frac{\partial E}{\partial X} = \frac{\partial E}{\partial Y} W^T</math> 로 역전파와 가중치의 전치행렬을 통해 다음층으로 보낼 보낼 영향을 구할 수 있으며, 달리 쓰면 <math>\frac{\partial E}{\partial W} = \frac{\partial E}{\partial Y} X^T</math>을 통해 이전층에서 왔던 신호의 전치행렬을 통해 가중치의 영향력을 구할 수 있다.
 +
 +
=== 구현 ===
 +
<syntaxhighlight lang="python">
 +
class Affine:
 +
    def __init__(self, W, b):  # 처음에 만들 때 가중치와 편향값을 부여한다.
 +
        self.W = W
 +
        self.b = b
 +
        self.x = None  # 앞 층으로부터 받을 x. 이 값은 저장해뒀다가 W의 미분 때 쓰인다.
 +
        self.dW = None  # 가중치의 영향력(미분값)을 담을 것.
 +
       
 +
    def forward(self, x):
 +
        self.x = x
 +
        out = np.dot(x, self.W) + self.b
 +
        return out
 +
    def backward(self, dout):
 +
        dx = np.dot(dout, self.W.T)  # x로 전파할 값을 구한다.
 +
        self.dW = np.dot(self.x.T, dout)
 +
        self.db = np.sum(dout, axis=0)  # +의 역전파는 그냥 더할 뿐이니, 전파된 값들을 다 더해 하나의 축으로 만든다.
 +
</syntaxhighlight>
 +
[[분류:딥러닝 이론]]

2021년 12월 8일 (수) 11:59 기준 최신판

1 개요편집

backpropagation. 줄이면 역전파. 경사법을 배웠으면 대부분 떠올릴 생각이다. 수치미분은 속도도 느리지만, 정확도에서도 아주 약간 아쉽다. 때문에 각 함수들에 대해 미분값을 미리 해석적으로 계산해두고 단순히 해당 값을 넣기만 하면 많은 연산을 아낄 수 있다.

어떤 계산에서의 순전파가 특정 연산을 수행한다면 역전파는 그 연산에서의 미분을 구할 수 있다는 아이디어에서 출발한다. 재미난 트릭인데, 역전파를 통해 해당 노드를 지날 때마다 미분값이 어떻게 변하는지도 알 수 있다는 점에서 개별 층별 훈련이 빠르게 가능해진다.

국소 미분을 단순히 축적해가며 아랫쪽으로 역전파 해간다. 이는 미분의 연쇄법칙에 의해 가능한 트릭.

2 사용예편집

각 층을 클래스로 만들고, 해당 레이어의 전방전파, 후방전파 메서드를 만들어 구현한다.

처음 출발하는 수는 1이다.  이므로.

2.1 Relu편집

class Relu:
    def __init__(self):
        self.mask = None  # 원소값이 0 이하면 True를 반환해 저장하기 위한 것.
        
    def forward(self, x):       # 배열을 받아 진행한다.
        self.mask = (x <=0 )    # x의 배열 중 0 이하를 True 체크해 저장.
        out = x.copy()          # 출력값을 저장할 변수.
        out[self.mask] = 0      # True인 인덱스에 0을 넣는다.
        return out
        
    def backward(self, dout):  # 미소변위라는 의미에서 d를 붙인 이름.
        # 순전파에서 0이었다면, 역전파에서도 미분값은 0이 되어야 한다. 전달된 게 없으니.
        dout[self.mask] = 0  # 역전파 입력값을 받아 순전파에서 0 이하였던 인덱스에 0을 넣는다.
        return dout

2.2 sigmoid편집

순전파에서 결과는  

  1. x에 -1을 곱하고(   )
  2. exp 처리를 하고 (   )
  3. 여기에 1을 더한 후 (   )
  4. 분자, 분모를 뒤집는다.(   )

역전파에선 다시 거꾸로

  1.  의 미분   으로 상류에서 흘러온 값을 제곱한 후 마이너스를 씌우고,
  2.  의 미분 1을 곱하고,(여기에서의 y는 1번에서의 최종 y와 다르다.)
  3.  의 미분  을 상류에서 흘러온 값에 처리하여  를 곱하고,(여기에서의 y는 1번에서의 최종 y와 다르다.)
  4.  의 미분 -1을 곱한다.

이들의 결과는  인데,  이므로 정리하면  가 된다.(사실, 그냥 미분을 계산해도 뭐;;) 즉, 순전파의 출력 y로부터 역전파를 구하게 되었다.

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):  # 미소변위라는 의미에서 d를 붙인 이름.
        dx = dout * (1 - self.out) * self.out
        return dx

2.3 softmax편집

원리는 다음 affine 계층에서의 역전파를 참고하자.

일반적으로 Cross entropy error층과 함께 사용된다. 교차 엔트로피의 수식은  이다. 역전파가 들어오면

2.3.1 크로스엔트로피의 역전파편집

  • +의 역전파 -1을 그대로 전달하고,
  • 위에 곱해 *의 역전파 두 입력을 바꾸어 각 노드에  가 전달되고,
  • 위에 곱해 log의 역전파  를 전달한다.

2.3.2 소프트맥스의 역전파편집

  • 입력값으로  가 들어오고,
  • 나누어주는 값으론  인데, t_k는 정답레이블이므로 다 더하면 1이 된다. 즉, 분모의 시그마로 나누는 과정의 역전파는  
  • 각각의 분자에는  가 들어오고 exp연산에서  가 결과로 나오게 된다.

2.3.3 구현편집

class SoftmasWithLoss:
    def __init__(self):
        self.loss = None  # 손실함수값을 저장하기 위한 변수.
        self.y = None  # 출력값을 저장하기 위한 변수.
        self.t = None  # 정답레이블
        
    def forward(self, x):
        self.t = t
        self.y = softmax(x)  # 이전에 구현한 소프트맥스 함수값을 넣는다.
        self.loss = cross_entrop_error(self.y, self.t)  # 오차함수로 오차값을 얻는다. 내 생각엔 backward에서 다뤄도 될 것 같은데..
        return self.loss  # 포워드니까 y값이 나와야 하는 거 아닌감??
    def backward(self, dout):
        batch_size = self.t.shape[0]  # 데이터 갯수
        dx = (self.y - self.t) / batch_size  # 역전파값을 데이터 갯수로 나눈다.(데이터를 여러 개 넣었을 경우.)
        return dx

2.4 affine편집

딥러닝의 중간 층들에선 행렬 계산이 이루어지는데, 이런 행렬의 곱을 affine변환이라 부른다. 때문에 중간 뉴런들에 대한 층은 어파인층이라 부르고, 이들의 역전파를 통해 어파인층 학습도 가능하다.

오차함수에 대하여 각 입력에 대한 편미분은   형태이다. 핵심은  에서  이므로  이런 관계를 갖는다는 말이다. 즉,   로 역전파와 가중치의 전치행렬을 통해 다음층으로 보낼 보낼 영향을 구할 수 있으며, 달리 쓰면  을 통해 이전층에서 왔던 신호의 전치행렬을 통해 가중치의 영향력을 구할 수 있다.

2.4.1 구현편집

class Affine:
    def __init__(self, W, b):  # 처음에 만들 때 가중치와 편향값을 부여한다.
        self.W = W
        self.b = b
        self.x = None  # 앞 층으로부터 받을 x. 이 값은 저장해뒀다가 W의 미분 때 쓰인다.
        self.dW = None  # 가중치의 영향력(미분값)을 담을 것.
        
    def forward(self, x):
        self.x = x
        out = np.dot(x, self.W) + self.b
        return out
    def backward(self, dout):
        dx = np.dot(dout, self.W.T)  # x로 전파할 값을 구한다.
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)  # +의 역전파는 그냥 더할 뿐이니, 전파된 값들을 다 더해 하나의 축으로 만든다.