Understanding of Backpropagation

understanding of backpropagation

 

 

backpropagation 수식을 위한 weight의 표기 방식

 

 

이러한 표기법의 특이한 점은 j가 아웃풋 노드를 나타내고 k가 인풋 노드를 나타낸다는 점이다.

bias와 activation에 대해서도 비슷한 표기방식을 쓴다.

 

 

l번째 레이어의 j번째 노드의 activation은 l-1번째 레이어의 activation 값들과 관련이 있다.

이 식을 matrix 형식으로 나타낼 수도 있다. 각 레이어 마다 weight matrix을 정의한다. wl 매트릭스는 l번째 레이어의 노드들로 연결되는 weight들로 이루어진다. 또한 각 레이어의 bias들의 매트릭스도 bl로 나타낸다.

이 식을 보면 각 레이어의 액티베이션 값들은 그 전 레이어의 액티베이션 값으로부터 얻는다는 것을 의미한다.

 

이렇게 매트릭스 방식으로 표현하는 것이 코딩하기에도 좋고(매트릭스 관련 라이브러리 사용) 의미를 이해하기도 좋다.

이 수식을 사용하면서, 우리는 중간에 값을 구한는데, 이 값은 중요한 의미를 갖기 때문에 이름을 붙이는 것이 좋다.

 

이 zl값을 l번째 레이어에 대한 weighted input이라고 칭하자.

 

 

weighted input을 사용해서 수식을 다시 나타내면 다음과 같다.

또한, zl은 다음과 같은 컴포넌트 들로 이루어져 있다.

(레이어 l 의 j 노드에 대한 weighted input)

 

Backpropagation의 목표는 Cost function의 partial derivatives를(weight와 bias에 대한) 구하는 것이다.

 

Cost function으로는 quadritic cost function을 사용한다.

n: total number of training examples / L : number of layers /  : x가 인풋일 때의 activation 벡터

 

 

 

 

이 cost function을 위해 2가지 가정이 필요하다.

 

1) cost function은 모든 training example x에 대한 cost의 평균이다.

각각의 x에 대해서는 quadratic cost function을 사용한다.

이런 가정을 하는 이유는 backpropagation이 우리에게 각 training example의 partial derivative를 얻게 해주기 때문이다.

)

우리는 이 것들의 평균을 냄으로써 를 구할 수 있게 되는 것이다.

 

2) Cost는 neural network의 output에 대한 function으로 나타낼 수 있다.

예를 들어 quadratic cost function도 이 가정을 만족한다. training example x 하나의 quadratic cost도 다음과 같이 표기할 수 있기 때문이다.

그리고 이 식은 바로 output activation에 관한 식이다.

 

물론 이 cost function 또한 output y에 의존한다. 그런데 왜 cost는 y에 관한 식으로 간주하지 않을까?

 

왜냐하면 training example x와 output y는 고정된 값이기 때문이다. 이 값들은 weight과 bias에 의해 달라지는 값이 아니고, neural network가 배우는 값이 아니다.

그러므로 C를 output actvation a에 관한 function으로 간주하는 것이 옳다. y는 그저 parameter로만 사용된다.

 

Backpropagation is about understanding how changing the weights and biases in a network changes the cost function.

Backpropagation을 이해하는 것은 weight과 biase를 변화시키는 것이 cost function을 어떻게 변화시키는지를 이해하는 것이다.

 

우리는 최종적으로 partial derivatives 를 구하고자 한다. 하지만 먼저 우리는 중간 값으로써 l번째 레이어의 j번째 노드에서의 error 값을 구한다. : 

 

Backpropagation 은 이 error값을 구하는 과정을 가능하게 할 것이고, 그 후에 우리는 를 와 연관시키는 방법을 찾으면 되는 것이다.

 

 

 

우리의 neural network 에 작은 악마가 살고 있다고 가정하자.

 

이 악마는 input이 들어올 때마다 layer l neuron j 에서 약간의 값을 더한다. :  

 

그래서 아웃풋이 대신 이 되고, 이는 결국 나중 레이어들에도 영향을 끼친다.

최종적으로는 최종 cost에게 만큼의 변화를 주게 된다.

의 변화에 따른 C의 변화량(기울기)에 를 곱한 값)

 

 

그런데 이 악마가 실은 좋은 악마였다. 얘는 cost를 낮추는 를 찾아내기를 원한다.

가 커다란 값(양수이든 음수이든)이라고 가정하면, 작은 악마는 와 반대의 부호를 갖는 를 찾아냄으로써 cost를 꽤 낮출 수 있다. 반대로, 가 0에 가깝다면, weighted input 에 무슨 짓을 해도 cost에 큰 변화를 줄 수 없다.

이 작은 악마의 입장에서 이는 곧, 이 뉴런이 optimal에 꽤나 가깝다는 걸 의미한다.

그러므로 휴리스틱한 이해를 해보자면, 는 이 뉴런에서 error의 양을 판단하는 기준이 될 수 있다.

 

이 이야기를 모티브로 우리는 error 를 다음과 같이 정의한다. :

(error에 대한 하나의 measure를 도출했다고 보는 게 자연스러움.)

이 또한 전처럼 vector로 표현할 수 있다. :  (layer l에서의 에러들의 벡터)

Backpropagation은 각각의 레이어에서의 를 계산하는 방법을 제공해 줄 것이다.

그 후에는 이 에러들을 우리의 실제 관심사인 를 계산하는 데에 활용할 수 있을 것이다.

사실, 작은 악마가 output activation 를 변화시켜서, 우리로 하여금 를 error에 대한 measure로 사용하도록 하는 게 자연스럽다.

에러에 대한 표기로 그냥 를 사용해도 되지만 수식들을 좀 더 단순하게 표현하기 위해 를 계속 사용하겠다.

 

 

 

 

An equation for the error in the output layer, δ

 

최종 output layer L 의 error을 구하는 수식 :

 

 

 부분은 cost가 얼마나 급격하게 바뀌는 지에 대한 양이다.

C가 output neuron들중 하나에 별로 영향 받지 않는다면, 의 값은 작을 것이다. (우리가 바라는 것이다.)

 부분은 에서 activation function 가 얼마나 급격하게 변화하느냐에 대한 값이다. (참고로, 는 activation function 적용 전의 weight 와 bias를 가지고 도출해낸 값.)

 

 이 수식의 모든 값들은 쉽게 구할 수 있다.

특히, 뉴럴넷의 특성(최종 아웃풋을 내는 성질)을 계산하면서 자연히 를 계산하게 되고, (forward propagation)

거기에 약간의 추가 계산을 하면 를 구하게 된다.

 

의 정확한 형태는 물론 cost function의 형태에 따라 달라지지만,

cost function을 알고 있다는 전제이기 때문에, 역시나 쉽게 구할 수 있을 것이다.

 

예를 들어 quadraric cost function 을 사용 중이었다면,

 

일테고, 가 될 것이다.

 

 

위의 수식은 component-wise expression 이기 때문에 matrix-based form 으로 쓴다면 다음과 같다.

(아웃풋 activation이 여러개라고 하면)

( : 요거는 component-wise 곱이다.)

 

 는 아웃풋 activation들의 변화에 따른 C의 변화율이라고 볼 수 있다. (앞에서 본 식과 사실상 같다.)

 

예를 들어, quadratic cost function의 경우, 이기 때문에

 이 된다.

 

이렇게 모든 것을 벡터 형식으로 나타내면 numpy를 이용해서 계산하기 편리하다.

 

 

An equation for the error δl in terms of the error in the next layer, δl+1

그 다음 레이어의 에러값을 이용해서 레이어의 에러값 도출하는 수식.

 

 : transpose of weight matrix in layer l + 1

 

weight matrix 를 transpose 시키는 것은 네트워크에서 반대방향으로 이동하는 것이라고 직관적으로 생각할 수 있다.

에 를 곱함으로써 layer l 에서의 에러값 에 대한 어떤 측정치를 얻게 된다는 것이다.

우리는 그 후에 이 값에 로 component-wise 곱을 해서 layer l 에서의 activation function을 반대방향으로 통과하도록 하고 결국 를 구하게 되는 것이다.

이와 같은 방식으로 우리는 모든 레이어의 에러값을 구할 수 있다. :    …

 

 

An equation for the rate of change of the cost with respect to any bias in the network
bias에 대한 cost의 변화율 (미분값)

에러값 는  정확히 미분값 와 일치한다.

 

 

An equation for the rate of change of the cost with respect to any weight in the network
네트워크 내의 특정 weight 에 대한 cost의 변화율

이 수식은 을 에 관해서 나타내는 방식이다.

 

이 수식은 다음과 같이 다시 표시할 수도 있다.

 : activation of the neuron input to the weight w

: error of the neuron output from the weight w

 

 

activation 의 값이 작다는 것은 (0에 가깝다는 것은)   또한 매우 작다는 것을 의미하고, 이것은 이 weight가 learns slowly 한다는 것을 의미한다. 즉, gradient descent 하는 동안 많이 변하지 않는 다는 것이다.

 

이 말은 곧, low-activation neuron들로 부터 나온(activation 값이 매우 작은 뉴런들로 부터 나온) weight 들은 learns slowly한다는 의미이다.

 

같은 맥락에서 또 다른 형태의 인사이트들도 얻을 수 있다.

먼저, output layer를 살펴보자.

 

 수식에서의 를 살펴보자.

함수는 (sigmoid 함수) 가 0이나 1에 가까워 질 때 매우 flat 해진다. (즉, 기울기가 0이 된다. )

 

이 말은 즉,

output 뉴런이 매우 높거나 매우 낮은 activation 값을 가지면

weight가 learns slowly 한다는 것을 의미한다. (error가 0에 가깝기 때문에)

 

이 경우 output neuron이 saturated 되었다고 한다. (output neuron의 bias에 대해서도 비슷한 언급을 한다.)

 

 

이번엔  에서의  부분을 살펴보자.

neuron이 매우 saturated 되어 있으면  또한 매우 작아진다는 것을 알 수 있다.

 

 

정리하자면, weight는 다음의 경우 learn slowly 한다.

1) input neuron이 low-activated

2) output neuron 이 saturated (either very high or very low activated)

 

 

이와 같은 발견이 대단히 놀랍지는 않지만, 뉴럴 네트워크 가 learning할 때 내부에서 어떤 일이 일어나는지에 대한 감을 잡기에 도움이 된다. 게다가, 이 추론을 역으로 이용할 수 있다.

 

네가지 기초 equation 은 standard sigmoid function 이외의 다른 activation function 들에도 적용 가능하다.

 

그러므로 우리는 이 equation들을 특정한 learning property를 같는 activation function을 디자인하는 데에 활용할 수 있다.

 

예를 들어 우리가 sigmoid function이 아닌 다른 activation function 를 선택했다 하자.

만약 값이 항상 양의 수가 되고 절대 0에 가까워 지지 않는다면, 보통의 sigmoid activation을 사용한 뉴런들이 saturate 하면서 발생하는 slow-down이 발생하지 않을 것이다. (learning 하는 동안)

(후에 이런 modification을 다시 살펴볼 것이다.)

 

이 네가지 equation을 기억하고 그런 modification를 시도하는 이유를 잘 고찰해보자.

 

 

 

BP1은 다음과 같이도 나타낼 수 있다. : 

BP2는 다음과 같이도 나타낼 수 있다. : 

 

이 두 수식을 합치면 다음과 같이 나타낼 수 있다.

 

 

Backpropagation 수식 증명

 

4가지 수식을 살펴봤다 이 식들에 대한 증명은 다음과 같다.

증명은 모두 다항식 미분의 chain rule로 부터 나온 것이다.

먼저 이 정의를 떠올려 보자.(우리가 정의한 것임.)

 

최종 layer L의 j번째 노드에서의 에러는 cost function을 해당 노드에서의 z값(: weighted input, weight * 이전 노드 activation 값 + bias)에 대한 미분 값이다.

 

chain rule을 적용하면, 이 식은 다음과 같이 전개할 수 있다.

 

 

k : output layer의 모든 뉴런들

왜냐하면, :

  • chain rule : If y = f(u) and u = g(x) :

C는 모든 들에 대한 식들의 합이기 때문에 미분에도 시그마가 포함된다.

 

여기서, k번째 neuron의 activation 는 input weight 에서 j = k 일때만 영향을 받는다.

 

즉, 일때는 는 소멸한다. (0이 되어서 전체 항이 사라짐.)

 

따라서 이 수식은 다음과 같이 간소화될 수 있다.

또한,  이기 때문에 다음과 같이 바꿀 수 있다.

 

-> 첫번째 수식 BP1 완성!

 

 

두번째 수식을 도출해보자.

두번째 수식은 특정 layer에서의 error 를 그 다음 layer에서의 error 값  을 이용해서 구할 수 있게 하는 수식이다.

 

먼저 두가지 error의 식을 나열해보자

 

 ,    

 

역시나 chain rule을 이용해서 다음과 같이 전개한다.

(앞서 본   를 대입)

 

 

여기서, 원래의 z의 수식을 다시 가져오면,

이렇게 정리한 수식을 에 관해 미분하면,

이 수식이 도출된다.

 

참고로, 시그마가 포함된 수식의 미분은 미분 결과의 시그마와 같다.

 

하지만, 이걸 특정 에 대해서 미분하면 j 이외의 다른 z항들은 다 상수항이 되어서 날라가기 때문에

 

  이 수식이 도출되는 것이다.

 

이 식을 다시 이 식에 대입하면, 최종적으로 다음 식이 도출된다.

 

이게 바로 BP2를 구한 것이다!

 

 

The Backpropagation Algorithm

 

Algorithm의 형태로 backpropagation을 나타내보자.

1) Input x : input 레이어에 해당하는 activation a1을 세팅한다.

2) Feedforward : 각 레이어마다 를 계산한다. (  )

3) Output error :  최종 layer L 에서의 error를(벡터) 계산한다.  

4) Backpropagate the error : 최종 layer부터 하나씩 backward로 2번째 layer까지

 )

각각의 레이어에서의 error 값(여러개이므로 벡터값) 을 구한다.

5) Output : cost function에 대한 모든 weight와 bias들의 gradient를 구한다. (이 때 각 뉴런에서의 error값이 이용됨.)

           

 

 

이 방법은 하나의 인풋에 대한 gradient descent를 구하는 방법이고, 실제 상황에선 stochastic gradient descent를 한다

 

그러려면 일단 mini-batch를 생성하는 외부 루프가 필요하다. 그리고 여러 training example 에 대한 gradient를 구한다.

(여러번의 training epoch를 루핑하도록 하는)

 

  1. 하나의 set의 training example 인풋을 받는다. (mini batch)

  2. 각각의 training example 에 대해 : 각각에 해당하는 activation 을 구한다.

1) feedforward : 이 과정으로 각 레이어에서의 z와 a들을 구할 수 있다.

2) output error : 마지막 레이어의 error를 구한다.

3) backpropagate error : 각각의 레이어들의 error를 구한다.

  1. gradiet descent : 각각의 레이어에서 gradient의 평균값(set 내의 training example들의 gradient의 평균)으로 descent를 진행

 

 

The Code for Backpropagation

 

지금까지 추상적으로 backpropagation을 이해해보았다. 이제 코드로 작성해보자.

잠깐, 그전에 먼저 이해를 확실히 해두어야 할 것.

 

이 때, delta v(v의 변화량)을 저 C미분값 벡터로 정한다고 가정해보자.

(cost function의 미분값(w에 대한 순간 기울기)만큼을 ‘w’에서 변화하게 하는 것이다.)

 

 :  C는 항상 감소하게 된다.

 

intuition :

1) cost의 기울기가 negative이면 w는 증가하고, positive이면 w는 감소한다.

2) 기울기가 크면, 그 만큼 그 방향으로 좀 더 가도 cost가 감소하는 데에 기여한다는 의미.

3) 그니까 항상 등고선에서 내려가는 방향으로 방향을 잡아간다는 intuition에 부합한다.

 

다시 코드로 돌아가보자. 핵심 부분은 이것이다. Gradient descent.

 

m : mini_batch의 아이템 갯수.

nabla_b, nabla_w : 여기에 mini_batch에서 받아온 모든 input들에 대한 gradient를 다 더한다.

바로 gradient descent의 뒷 부분인 이부분 :   ,   

 

class Network(object):
...
    def update_mini_batch(self, mini_batch, eta):
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.weights = [w-(eta/len(mini_batch))*nw 
                       for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(eta/len(mini_batch))*nb 
                       for b, nb in zip(self.biases, nabla_b)]

 

위의 코드로 self.weights 와 self.biases는 한 번 업데이트 된다.

요기서 나온 바로 이 함수, self.backprop(x, y)

이게 우리가 머리 싸맨 backpropagation의 수식들을 이용해서 모든 w, b의 gradient를 찾아낸 함수이다.

class Network(object):
...
   def backprop(self, x, y):
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]

        # feedforward
        activation = x
        activations = [x] # list to store all the activations, layer by layer
        zs = [] # list to store all the z vectors, layer by layer
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)

        # backward pass
        delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())

        for l in xrange(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
        return (nabla_b, nabla_w)
...

    def cost_derivative(self, output_activations, y):

        return (output_activations-y) 

def sigmoid(z):
    """The sigmoid function."""
    return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
    """Derivative of the sigmoid function."""
    return sigmoid(z)*(1-sigmoid(z))

 

 

왜 Backpropagation 방식으로 derivative를 구하는 것이 빠른가?

 

이런 개념을 도입해서 계산해낼 수도 있다.

하지만 이경우, cost function 계산을 엄청나게 많이 해야 하고, 프로그램이 엄청나게 느려지게 된다.

 

backpropagation의 멋진 점은,

한번 forward로 전체 네트워크를 계산한 후에는

동시에 모든 partial derivative들을 계산할 수 있게 해준다는 점이다.

 

이 계산량은 대략 전체 네트워크를 forward로 두번 계산하는 양과 비슷한다.

backpropagation은 매우 복잡해보이지만 사실상 계산량을 엄청나게 단축시켜 주는 것이다.

 

 

 

 

 

 

 

 

 

두가지 미스테리 :

 

– 아웃풋으로부터 backpropagated된 errorf를 구하는 것에 대한 deeper intuition

– 처음에 backpropagation을 어떻게 발견하게 되었을까?

(애초에 어떤 추론을 거쳐서 이걸 발견하게 되었을까?)

 

 

우리의 intuition을 개선하기 위해 다시 생각해보자.

 

우리가 weight 네트워크의 한 지점 에 작은 변화량 를 추가했다고 하자.

 

 

weight에서의 변화는 그  output에 해당하는 neuron의 activation에 영향을 주게 된다.

 

 

 

그 다음엔, 다음 레이어에 있는 모든 activation에 영향을 주게된다.

 

 

 

 

이런 식으로 결국 마지막 레이어까지 영향을 주게 되고, cost function에도 영향을 주게 된다.

 

 

 

cost function에서의 변화량 은, 애초 weight에서의 변화량 과 다음 식과 같은 연관성을 갖는다.

 

 

를 계산하는 방법은, 에서의 작은 변화가 어떻게 C에서의 변화에 영향을 주는지를 쫓아가(track) 보는 것이다.

 

이 과정의 모든 값들을 수식으로 잘 나타낼 수만 있다면, 를 구할 수 있을 것이다.

 

 

조금 더 진행해보자. 는 l layer의 j번째 뉴런의 activation에도 작은 변화 를 만들어낼 것이다.

 

이 변화에 대한 수식은 다음과 같다.

는 그 다음 레이어의 모든 activation에 영향을 줄 것이다.

이 중 하나의 뉴런 만 생각해보자.

 

 

 

 

이런 방식으로 레이어를 하나하나 건너가면 다음과 같이 전개 된다.

 

 

각 레이어 마다 뉴런이 여러개가 있기 때문에, 이 편미분 값들을 모두 더하면 C에서의 total change가 될 것이다.

 

 

이 때,   이 식을 대입하면 다음과 같이 전개 된다.

 

 

 

 

이 식은 매우 복잡해보이지만, 직관적인 이해가 가능하다.

우리는 네트워크 내의 weight에 관해서 C의 변화율을 계산하고 있는 것이다.

이 식에서 알 수 있는 것은,

두 뉴련 사이의 모든 edge들은 rate factor와 연관이 있는데,

rate factor 는 한 뉴런의 activation의 다른 뉴런의 activation에 대한 partial derivative이다.

 

첫번째 뉴런(우리가 시작했던)으로의 첫번째 weight를 나타내는 edge는 라는 rate factor를 갖는다.

 

한 path의 rate factor는 그 길을 가는 동안의 rate factor 들의 곱이다.

 

 는 결국 모든 path 들의 rate factor의 합이 되는 것이다.

 

 

 

이건 휴리스틱한 논의이다.

결국 backpropagation 알고리즘은 모든 path들의 rate factor를 더하는 방법을 제공하는 것이다.

혹은, weight에서의 작은 변화가 일으키는 모든 혼란에 대해서 keeping track 하는 것이다.

 

 

 

 

 

 

 

Advertisements

Occasionally, some of your visitors may see an advertisement here
You can hide these ads completely by upgrading to one of our paid plans.

UPGRADE NOW DISMISS MESSAGE

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s