"오차역전파법"의 두 판 사이의 차이
(→구현) |
|||
71번째 줄: | 71번째 줄: | ||
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> |
2021년 9월 13일 (월) 13:19 판
1 개요
backpropagation. 줄이면 역전파. 경사법을 배웠으면 대부분 떠올릴 생각이다. 수치미분은 속도도 느리지만, 정확도에서도 아주 약간 아쉽다. 때문에 각 함수들에 대해 미분값을 미리 해석적으로 계산해두고 단순히 해당 값을 넣기만 하면 많은 연산을 아낄 수 있다.
어떤 계산에서의 순전파가 특정 연산을 수행한다면 역전파는 그 연산에서의 미분을 구할 수 있다는 아이디어에서 출발한다. 재미난 트릭인데, 역전파를 통해 해당 노드를 지날 때마다 미분값이 어떻게 변하는지도 알 수 있다는 점에서 개별 층별 훈련이 빠르게 가능해진다.
국소 미분을 단순히 축적해가며 아랫쪽으로 역전파 해간다. 이는 미분의 연쇄법칙에 의해 가능한 트릭.
2 사용예
각 층을 클래스로 만들고, 해당 레이어의 전방전파, 후방전파 메서드를 만들어 구현한다.
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 계층에서의 역전파를 참고하자.
2.3.1 구현
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) # +의 역전파는 그냥 더할 뿐이니, 전파된 값들을 다 더해 하나의 축으로 만든다.