5번째 줄: |
5번째 줄: |
| | | |
| 국소 미분을 단순히 축적해가며 아랫쪽으로 역전파 해간다. 이는 미분의 연쇄법칙에 의해 가능한 트릭. | | 국소 미분을 단순히 축적해가며 아랫쪽으로 역전파 해간다. 이는 미분의 연쇄법칙에 의해 가능한 트릭. |
| + | |
| + | = 사용예 = |
| + | 각 층을 클래스로 만들고, 해당 레이어의 전방전파, 후방전파 메서드를 만들어 구현한다. |
| + | |
| + | 처음 출발하는 수는 1이다. <math>\frac{\partial L}{\partial L} =1</math>이므로. |
| + | |
| + | == Relu == |
| + | <syntaxhighlight lang="python"> |
| + | 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 |
| + | </syntaxhighlight> |
| + | |
| + | == sigmoid == |
| + | 순전파에서 결과는 <math>y= \frac{1}{1+\exp(-x)}</math> |
| + | |
| + | # x에 -1을 곱하고( <math>y= -x</math> ) |
| + | # exp 처리를 하고 ( <math>y= \exp(x)</math> ) |
| + | # 여기에 1을 더한 후 ( <math>y= x+1</math> ) |
| + | # 분자, 분모를 뒤집는다.( <math>y= \tfrac{1}{x}</math> ) |
| + | |
| + | 역전파에선 다시 거꾸로 |
| + | |
| + | # <math>y= \tfrac{1}{x}</math>의 미분 <math>-\tfrac{1}{x^2} = -y^2</math> 으로 상류에서 흘러온 값을 제곱한 후 마이너스를 씌우고, |
| + | # <math>y= x+1</math>의 미분 1을 곱하고,(여기에서의 y는 1번에서의 최종 y와 다르다.) |
| + | # <math>y= \exp(x)</math>의 미분 <math>\exp(x)</math>을 상류에서 흘러온 값에 처리하여 <math>\exp(x)</math>를 곱하고,(여기에서의 y는 1번에서의 최종 y와 다르다.) |
| + | # <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"> |
| + | 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 |
| + | </syntaxhighlight> |
| + | |
| + | == 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변환이라 부른다. 때문에 중간 뉴런들에 대한 층은 어파인층이라 부르고, 이들의 역전파를 통해 어파인층 학습도 가능하다. |
| + | |
| + | 오차함수에 대하여 각 입력에 대한 편미분은 <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> |
| + | [[분류:딥러닝 이론]] |