오차역전파법

Pywiki
둘러보기로 가기 검색하러 가기

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)  # +의 역전파는 그냥 더할 뿐이니, 전파된 값들을 다 더해 하나의 축으로 만든다.