"오차역전파법"의 두 판 사이의 차이
잔글 |
|||
(같은 사용자의 중간 판 2개는 보이지 않습니다) | |||
8번째 줄: | 8번째 줄: | ||
= 사용예 = | = 사용예 = | ||
각 층을 클래스로 만들고, 해당 레이어의 전방전파, 후방전파 메서드를 만들어 구현한다. | 각 층을 클래스로 만들고, 해당 레이어의 전방전파, 후방전파 메서드를 만들어 구현한다. | ||
+ | |||
+ | 처음 출발하는 수는 1이다. <math>\frac{\partial L}{\partial L} =1</math>이므로. | ||
== Relu == | == Relu == | ||
59번째 줄: | 61번째 줄: | ||
== softmax == | == softmax == | ||
원리는 다음 affine 계층에서의 역전파를 참고하자. | 원리는 다음 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>가 결과로 나오게 된다. | ||
=== 구현 === | === 구현 === | ||
71번째 줄: | 87번째 줄: | ||
self.t = t | self.t = t | ||
self.y = softmax(x) # 이전에 구현한 소프트맥스 함수값을 넣는다. | self.y = softmax(x) # 이전에 구현한 소프트맥스 함수값을 넣는다. | ||
− | self.loss = cross_entrop_error(self.y, self.t) # 오차함수로 오차값을 얻는다. | + | self.loss = cross_entrop_error(self.y, self.t) # 오차함수로 오차값을 얻는다. 내 생각엔 backward에서 다뤄도 될 것 같은데.. |
− | return self.loss | + | return self.loss # 포워드니까 y값이 나와야 하는 거 아닌감?? |
def backward(self, dout): | def backward(self, dout): | ||
batch_size = self.t.shape[0] # 데이터 갯수 | batch_size = self.t.shape[0] # 데이터 갯수 | ||
− | dx = (self.y - self.t) / batch_size # 역전파값을 데이터 갯수로 나눈다. | + | dx = (self.y - self.t) / batch_size # 역전파값을 데이터 갯수로 나눈다.(데이터를 여러 개 넣었을 경우.) |
return dx | return dx | ||
</syntaxhighlight> | </syntaxhighlight> | ||
101번째 줄: | 117번째 줄: | ||
self.db = np.sum(dout, axis=0) # +의 역전파는 그냥 더할 뿐이니, 전파된 값들을 다 더해 하나의 축으로 만든다. | self.db = np.sum(dout, axis=0) # +의 역전파는 그냥 더할 뿐이니, 전파된 값들을 다 더해 하나의 축으로 만든다. | ||
</syntaxhighlight> | </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[편집 | 원본 편집]
순전파에서 결과는
- x에 -1을 곱하고( )
- exp 처리를 하고 ( )
- 여기에 1을 더한 후 ( )
- 분자, 분모를 뒤집는다.( )
역전파에선 다시 거꾸로
- 의 미분 으로 상류에서 흘러온 값을 제곱한 후 마이너스를 씌우고,
- 의 미분 1을 곱하고,(여기에서의 y는 1번에서의 최종 y와 다르다.)
- 의 미분 을 상류에서 흘러온 값에 처리하여 를 곱하고,(여기에서의 y는 1번에서의 최종 y와 다르다.)
- 의 미분 -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) # +의 역전파는 그냥 더할 뿐이니, 전파된 값들을 다 더해 하나의 축으로 만든다.