본문 바로가기
프로그래밍 놀이터/머신러닝, AI, 딥러닝

[머신러닝] #5 오차역전파법 ( Back propagation )

by 돼지왕왕돼지 2018. 7. 7.

[머신러닝] #5 오차역전파법 ( Back propagation )


"Deep Learning from Scratch” 책을 보고 제가 필요한 내용만 정리한 내용입니다.

자세한 내용은 책을 구매해서 보세요~

affine, affine transformation, affine 계층, back propagation, backpropagation, backward, chain rule, computational graph, Dot, Edge, Forward, forward propagation, gradient check, matrix transpose, Node, ordereddict, relu 계층, sigmoid, sigmoid 계층, softmax 계층, softmax-with-loss 계층, transpose, 계산 과정 그래프, 계산 그래프, 계산 그래프의 역전파, 곱셈 노드의 역전파, 교차 엔트로피 오차, 국소적 계산, 국소적 미분, 기울기 확인, 내적, 노드, 단순한 계층 구현하기, 덧셈노드의 역전파, 배치용 affine 계층, 수치 미분, 수치미분, 수치미분 단점, 순전파, 순전파 미분, 신경망 score, 신경망 학습의 전체 그림, 신경망의 기능 단위, 어파인 변환, 에지, 역전파, 역전파 미분, 연쇄 법칙, 연쇄법칙의 원리, 오차역전파법, 오차역전파법 구현하기, 중간 계산 결과 보관, 합성 함수, 행렬 내적, 활성화 함수 계층 구현하기


-

수치미분은 단순하고 구현하기도 쉽지만 계산 시간이 오래 걸린다는게 단점이다.

오차역전파법(backpropagation)은 어렵지만 시간이 짧게 걸린다.



-

오차역전파법을 제대로 이해하는 방법은 두 가지가 있다. 하나는 수식을 통한 것이고, 다른 하나는 계산 그래프를 통한 것이다.

수식을 통한 것이 일반적인 방법으로, 특히 기계학습을 다루는 책 대부분은 수식을 중심으로 이야기를 전개한다.

하지만 수식을 중심으로만 생각하다 보면 본질을 놓치거나, 수많은 수식에 당황할 수 있다.





5.1. 계산 그래프


-

계산 그래프(computational graph)는 계산 과정을 그래프로 나타낸 것이다.

여기에서 말하는 그래프는 자료구조로 복수의 노드(node)와 에지(edge)로 표현한다.




5.1.1. 계산 그래프로 풀다.


-

책에 계산 그래프를 구조화하는 예제가 나와있다. (사과와 귤의 갯수 및 부가세 등으로.. )


계산 그래프를 이용한 문제풀이는 다음 흐름으로 진행된다.


1. 계산 그래프를 구성한다.

2. 그래프에서 계산을 왼쪽에서 오른쪽으로 진행한다.


여기서 왼쪽에서 오른쪽으로 진행하는 단계를 순전파(forward propagation)라고 한다.

순전파는 계산 그래프의 출발점부터 종착점으로의 전파이다.

반대 방향(오른쪽에서 왼쪽)의 전파로 가능하다. 이를 역전파(backward propagation)라고 한다.

역전파는 이후에 미분을 계산할 때 중요한 역할을 한다.




5.1.2. 국소적 계산


-

계산 그래프의 특징은 ‘국소적 계산’을 전파함으로써 최종 결과를 얻는다는 점에 있다.

국소적이란 ‘자신과 직접 관계된 작은 범위’라는 뜻이다.

국소적 계산은 결국 전체에서 어떤 일이 벌어지든 상관없이 자신과 관계된 정보만으로 결과를 출력할 수 있다는 것이다.


국소적 계산은 단순하지만, 그 결과를 전달함으로써 전체를 구성하는 복잡한 계산을 해낼 수 있다.




5.1.3. 왜 계산 그래프로 푸는가?


-

계산 그래프의 이점 중 하나는 ‘국소적 계산’ 이다.

전체가 아무리 복잡해도 각 노드에서는 단순한 계산에 집중하여 문제를 단순화 할 수 있다.


또 다른 이점으로는, 계산 그래프는 중간 계산 결과를 모두 보관할 수 있다.


마지막으로 실제 계산 그래프를 사용하는 가장 큰 이유는 역전파를 통해 ‘미분’을 효율적으로 계산할 수 있다는 것이다.



-

책에 나온 예제 중 하나는 다음과 같다.


사과 가격 (100)

                       -> x (200)

사과의 개수 (2)

                                             -> x (지불금액, 220)

                       소비세 (1.1)



위의 예에서 사과 가격이 오르면 최종 금액에 어떤 영향을 미치는지 알고 싶다고 하자.

이는 ‘사과 가격에 대한 지불 금액의 미분’ 을 구하는 문제이다.

미분 값은 사과 값이 ‘아주 조금’ 올랐을 때 지불 금액이 얼마나 증가하느냐를 표시한 것이다. ( 미분의 정의를 잘 생각하자 )


사과 가격에 대한 지불 금액의 미분같은 값은 역전파를 하면 구할 수 있다.


역전파는 '국소적 미분’ 을 전달하며, 오른쪽에서 왼쪽으로 ‘1 -> 1.1 -> 2.2’ 순으로 미분 값을 전달한다.

이 결과로부터 사과 가격에 대한 지불 금액의 미분 값은 2.2 라고 할 수 있다.

사과 1원이 오르면 최종 금액은 2.2원 오른다는 뜻이다.





5.2. 연쇄법칙


-

역전파는 ‘국소적인 비문’을 순방향과는 반대인 오른쪽에서 왼쪽으로 전달한다.

또한, 이 국소적 미분을 전달하는 원리는 연쇄법칙(chain rule)에 따른 것이다.

연쇄법칙은 계산 그래프 상의 역전파와 같다.



5.2.1. 계산 그래프의 역전파


-

역전파의 계산 절차는 전달되는 신호에 노드의 국소적 미분을 곱한 후 다음 노드로 전달하는 것이다.

여기서 말하는 국소적 미분은 순전파 때의 미분을 구한다는 것이다.




5.2.2. 연쇄법칙이란?


-

연쇄법칙을 설명하려면 합성 함수 이야기부터 시작해야 한다.

합성 함수란 여러 함수로 구성된 함수이다.

예를 들어 y = (x+a)^2 라는 식은 다음 두 개의 식으로 구성된다.

y = u^2

u = x+ a


연쇄법칙은 합성 함수의 미분에 대한 성질이며, 다음과 같이 정의된다.


“합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다.”


이것이 연쇄법칙의 원리이다.


예를 들어 위의 식에서 x 에 대한 y 의 미분은, u 에 대한 y 의 미분과, x 에 대한 u 의 미분의 곱으로 나타낼 수 있다는 것이다.

affine, affine transformation, affine 계층, back propagation, backpropagation, backward, chain rule, computational graph, Dot, Edge, Forward, forward propagation, gradient check, matrix transpose, Node, ordereddict, relu 계층, sigmoid, sigmoid 계층, softmax 계층, softmax-with-loss 계층, transpose, 계산 과정 그래프, 계산 그래프, 계산 그래프의 역전파, 곱셈 노드의 역전파, 교차 엔트로피 오차, 국소적 계산, 국소적 미분, 기울기 확인, 내적, 노드, 단순한 계층 구현하기, 덧셈노드의 역전파, 배치용 affine 계층, 수치 미분, 수치미분, 수치미분 단점, 순전파, 순전파 미분, 신경망 score, 신경망 학습의 전체 그림, 신경망의 기능 단위, 어파인 변환, 에지, 역전파, 역전파 미분, 연쇄 법칙, 연쇄법칙의 원리, 오차역전파법, 오차역전파법 구현하기, 중간 계산 결과 보관, 합성 함수, 행렬 내적, 활성화 함수 계층 구현하기

위의 식에서 dy/du = 2u 이고, du/dx = 1 로, 최종적으로 dy/dx = 2u = 2(x+a) 가 된다.




5.2.3. 연쇄법칙과 계산 그래프





5.3. 역전파


5.3.1. 덧셈 노드의 역전파


-

덧셈 노드의 역전파는 입력 값을 그대로 흘려보낸다.




5.3.2. 곱셈 노드의 역전파


-

곱셈 노드 역전파는 상류의 값에 순전파 때의 입력 신호들을 “서로 바꾼 값”을 곱해서 하류로 보낸다.

순전파때 x 가 곱해졌다면 역전파에서는 y 를, 반대로 순전파때 y 가 곱해졌다면 역전파때 x 를 곱해서 전달한다.

( 한 노드에 x, y 의 input 이 들어가서 z 가 나온다고 하면, x 쪽으로는 y 가 곱해진 값이 전달되고, y 쪽으로는 x 가 곱해진 값이 전달된다. )




5.3.3. 사과 쇼핑의 예





5.4. 단순한 계층 구현하기


-

다음 절에서는 신경망을 구현하는 “계층” 각각을 하나의 클래스로 구현한다.

여기에서 말하는 계층이란 신경망의 기능 단위이다.

예를 들어 시그모이드 함수를 위한 sigmoid, 행렬 내적을 위한 affine 등의 기능을 계층 단위로 구현한다.




5.4.1. 곱셈 계층


-

모든 계층은 forward() 와 backward() 라는 공통의 메서드를 갖도록 구현한다.

class MulLayer: # 곱셈 layer

    def __init__(self):

        self.x = None

        self.y = None


    def forward(self, x, y):

        self.x = x

        self.y = y

        out = x * y

        return out


    def backward(self, dout): # x 와 y 를 바꾸어 계산

        dx = dout * self.y 

        dy = dout * self.x

        return dx, dy



-

ex)

apple_price = 100

apple_num = 2

tax = 1.1


#계층들

mul_apple_layer = MulLayer()

mul_tax_layer = MulLayer()


#순전파

apple_price = mul_apple_layer.forward(apple, apple_num)

price = mul_tax_layer.forward(apple_price, tax)


dprice = 1

dapple_price, dtax = mul_tax_layer.backward(dprice)

dapple, dapple_num = mul_apple_layer.backward(dapple_price)




5.4.2. 덧셈 계층


-

class AddLayer:

    def __init__(self):

        pass # 아무일도 하지 말라는 명령


    def forward(self, x, y):

        out = x + y

        return out


    def backward(self, dout):

        dx = dout * 1

        dy = dout * 1

        return dx, dy






5.5. 활성화 함수 계층 구현하기


5.5.1. ReLU 계층


-

ReLU 수식은 다음과 같다.

y = x if x> 0

   = 0 if x <= 0


ReLU 의 미분은 1 if x > 0, 0 if x <= 0

이는 순전파 때의 입력인 x 가 0보다 크면 역전파는 상류의 값을 그대로 하류로 흘린다.

반대로 순전파 때 x 가 0 이하면 역전파 때는 하류로 신호를 보내지 않는다.



-

class ReLU:

    def __init__(self):

        self.mask = None


    def forward(self, x):

        self.mask = (x <= 0)

        out = x.copy()

        out[self.mask] = 0

return out


    def backward(self, dout):

        dout[self.mask] = 0

        dx = dout

        return dx



-

ReLU 계층은 전기 회로의 스위치에 비유할 수 있다.

순전파 때 전류가 흐르고 있으면 스위치를 ON 하고, 흐르지 않으면 OFF 한다.

역전파 때는 스위치가 ON 이라면 전류가 그대로 흐르고 OFF 면 더 이상 흐르지 않는다.




5.5.2. sigmoid 계층


-

시그모이드 함수는 다음과 같다.

affine, affine transformation, affine 계층, back propagation, backpropagation, backward, chain rule, computational graph, Dot, Edge, Forward, forward propagation, gradient check, matrix transpose, Node, ordereddict, relu 계층, sigmoid, sigmoid 계층, softmax 계층, softmax-with-loss 계층, transpose, 계산 과정 그래프, 계산 그래프, 계산 그래프의 역전파, 곱셈 노드의 역전파, 교차 엔트로피 오차, 국소적 계산, 국소적 미분, 기울기 확인, 내적, 노드, 단순한 계층 구현하기, 덧셈노드의 역전파, 배치용 affine 계층, 수치 미분, 수치미분, 수치미분 단점, 순전파, 순전파 미분, 신경망 score, 신경망 학습의 전체 그림, 신경망의 기능 단위, 어파인 변환, 에지, 역전파, 역전파 미분, 연쇄 법칙, 연쇄법칙의 원리, 오차역전파법, 오차역전파법 구현하기, 중간 계산 결과 보관, 합성 함수, 행렬 내적, 활성화 함수 계층 구현하기



-

y = 1/ x 을 미분하면 - 1 / x^2 = - y^2


이에 따르면 역전파 때는 상류에서 흘러온 값에 -y^2 (순전파의 출력을 제곱한 후 마이너스를 붙인 값)을 곱해서 하류로 전달한다.



-

exp 의 미분은 exp 이다.



-

결과적으로 시그모이드 함수의 역전파 최종 출력은..

(dL/dy) y^2 exp(-x)


sigmoid 의 역전파는 입력인 x 와 출력인 y 값만으로 값을 구할 수 있다.

간소화 버전은 역전파 과정의 중간 계산들을 생략할 수 있어 효율적이다.


이를 또 정리할 수 있는데 정리된 버전은

(dL/dy) y (1-y)


즉 출력 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):

        dx = dout * (1.0 - self.out) * self.out

        return dx





5.6. Affine/Softmax 계층 구현하기


5.6.1. Affine 계층


-

신경망의 순전파에서는 가중치 신호의 총합을 계산하기 때문에 행렬의 내적(넘파이의 np.dot())을 사용했다.

신경망의 순전파 때 수행하는 행렬의 내적은 기하학에서는 어파인 변환(affine transformation)이라고 한다.

여기서는 어파인 변환을 수행하는 처리를 “Affine 계층” 이라는 이름으로 구현한다.



-

순서에 주의해야 한다.

X dot W 를 하기 때문에 역전파 할 때는 W 의 위치는 오른쪽, X 의 위치는 왼쪽이다.

dL/dX = dL/dY dot W^T ( dL/dX 는 X 와 같은 형상 ) 

dL/dW = X^T dot dL/dY ( dL/dW 는 W 와 같은 형상 )


^T 는 전치행렬을 뜻하는데, 전치행렬은 행렬의 (i,j) 위치의 원소를 (j, i)위치로 바꾼 것을 말한다.

W = (w11, w21, w31)

        (w12, w22, w32)


W^T = (w11, w12)

            (w21, w22)

            (w31, w32)




5.6.2. 배치용 Affine 계층


-

순전파의 편향 덧셈은 각각의 데이터에 더해진다.

그래서 역전파 때는 각 데이터의 역전파 값이 편향의 원소에 모여야 한다.

dB = np.sum(dY, axis=0) #0번째 축



-

class Affine:

    def __init__(self, W, b):

        self.W = W

        self.b = b

        self.x = None

        self.dW = None

        self.db = 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) # transposed matrix

        self.dW = np.dot(self.X.T, dout)

        self.db = np.sum(dout, axis=0)




5.6.3. Softmax-with-loss 계층


-

소프트맥스 함수는 입력 값을 정규화하여 출력한다. ( 출력 합이 1이 되도록 변형 )



-

신경망에서 수행하는 작업은 학습과 추론 두 가지이다.

추론할 때는 일반적으로 Softmax 계층을 사용하지 않는다.

추론할 때는 마지막 Affine 계층의 출력을 인식 결과로 이용한다.

신경망에서 정규화하지 않는 출력 결과를 점수(score)라고 한다.

즉, 신경망 추론에서 답을 하나만 내는 경우에는 가장 높은 점수만 알면 되니, softmax 계층은 필요 없다는 것이다.

반면,신경망을 학습할 때는 Softmax 계층이 필요하다.



-

소프트맥스 계층은 손실 함수인 교차 엔트로피 오차도 포함하여 softmax-with-loss 게층이라는 이름으로 구현한다.


소프트맥스 계층의 역전파 결과는 (y1-t1, y2-t2, y3-t3) 이다. (말끔하다)

y 는 소프트맥스 계층의 출력이고, t 는 정답 레이블이다.

신경망의 역전파에서는 이 차분이 앞 계층에 전해지는 것이다.

이는 신경망 학습의 중요한 성질이다.



-

소프트맥스 함수의 손실 함수로 교차 엔트로피 오차를 사용하니 역전파가 y-t 로 말끔히 떨어진다.

사실 이런 말끔함은 우연이 아니라 '교차 엔트로피 오차'라는 함수가 그렇게 설계되었기 때문이다.

또 회귀의 출력층에서 사용하는 '항등 함수'의 손실 함수로 '평균 제곱 오차'를 이용하는 이유도 이와 같다.

즉, 항등 함수의 손실함수로 평균 제곱 오차를 사용하면 역전파의 결과가 y-t 로 말끔히 떨어진다.



-

class SoftmaxWithLoss:

    def __init__(self):

        self.loss = None # 손실

        self.y = None # softmax 출력

        self.t = None # 정답 레이블(원-핫 벡터)


    def forward(self, x,y):

        self.t = t

        self.y = softmax(x)

        self.loss = cross_entropy_error(self.y, self.t)

        return self.loss


    def backward(self, dout=1):

        batch_size = self.t.shape[0]

        dx = (self.y - self.t) / batch_size

        return dx






5.7. 오차역전파법 구현하기


5.7.1. 신경망 학습의 전체 그림


-

전제

    신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 ‘학습’이라 한다. 신경망 학습은 다음과 같이 4단계로 수행한다.


1단계 - 미니배치

    훈련 데이터 중 일부를 무작위로 가져온다. 이렇게 선별한 데이터를 미니배치라 하며, 그 미니배치의 손실 함수 값을 줄이는 것이 목표이다.


2단계 - 기울기 산출

    미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다.


3단계 - 매개변수 갱신

    가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다.


4단계 - 1~3단계를 반복한다.




5.7.2. 오차역전파법을 적용한 신경망 구현하기


-

class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):

        #가중치 초기화

        self.params = { }

        self.params[‘W1’] = weight_init_std * np.random.randn(input_size, hidden_size)

        self.params[‘b1’] = np.zeros(hidden_size)

        self.params[‘W2’] = weight_init_std * np.random.randn(hidden_size, output_size)

        self.params[‘b2’] = np.zeros(output_size)


        #계층 생성

        self.layers = OrderedDict() # 순서가 보장되어야 한다.

        self.layers[‘Affine1’] = Affine(self.params[‘W1’], self.params[‘b1’])

        self.layers[‘Relu1’] = Relu()

        self.layers[;Affine2’] = Affine(self.params[‘W2’], self.params[‘b2’])

        self.lastLayer = SoftmaxWithLoss()


    def predict(self, x):

        for layer in self.layers.values():

            x = layer.forward(x)

        return x


    def loss(self, x, t):

        y = self.predict(x)

        return self.lastLayer.forward(y, t)


    def accuracy(self, x, t):

        y = self.predict(x)

        y = np.argmax(y, axis=1)

        if t.ndim != 1 :

            t = np.argmax(t, axis=1)


        accuracy = np.sum(y == t) / float(x.shape[0])

        return accuracy


    def numerical_gradient(self, x, t):

        loss_W = lambda W: self.loss(x, t)


        grads = { }

        grads[‘W1’] = numerical_gradient(loss_W, self.params[‘W1’])

        grads[‘b1’] = numerical_gradient(loss_W, self.params[‘b1’])

        grads[‘W2’] = numerical_gradient(loss_W, self.params[‘W2’])

        grads[‘b2’] = numerical_gradient(loss_W, self.params[‘b2’])

        return grads


    def gradient(self, x, t):

        #순전파

        self.loss(x, t)


        #역전파

        dout = 1

        dout = self.lastLayer.backward(dout)


        layers = list(self.layers.values())

        layers.reverse()

        for layer in layers:

            dout = layer.backward(dout)


        #결과 저장

        grads = { }

        grads[‘W1’] = self.layers[‘Affine1’].dW

        grads[‘b1’] = self.layers[‘Affine1’].db

        grads[‘W2’] = self.layers[‘Affine2’].dW

        grads[‘b2’] = self.layers[‘Affine2’].db

        return grads




5.7.3. 오차역전파법으로 구한 기울기 검증하기


-

수치 미분은 느리다. 그리고 오차역전파법을 제대로 구현해두면 수치 미분은 더 이상 필요 없다.

수치 미분은 오차역전파법을 정확히 구현했는지 확인하기 위해 필요하기도 하다.

수치 미분의 이점은 구현하기 쉬워 버그가 숨어 있기 어려운 반면, 오차역전파법은 구현하기 복잡해서 종종 실수를 하곤 한다.

그래서 수치 미분의 결과와 오차역전파법의 결과를 비교하여 오차역전파법을 제대로 구했는지 검증하곤 한다.

두 방식으로 구한 기울기가 일치함(엄밀히 말하면 거의 같음)을 확인하는 작업을 기울기 확인(gradient check)라고 한다.

기울기 확인은 다음과 같이 구현한다.

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)


x_batch = x_train[:3]

t_batch = t_train[:3]


grad_numerical = network.numerical_gradient(x_batch, t_batch)

grad_backprop = network.gradient(x_batch, t_batch)


for key in grad_numerical.keys():

    diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )



-

수치 미분과 오차역전파법의 결과 오차가 0이 되는 일은 드물다.

이는 컴퓨터가 할 수 있는 계산의 정밀도가 유한하기 때문이다. (가령 32비트 부동소수점)

이 정밀도의 한계 때문에 오차는 대부분 0이 되지는 않지만, 올바르게 구현했다면 0에 아주 가까운 값이 된다.

만약 그 값이 크면 오차역전파법을 잘못 구현했다고 의심해봐야 한다.




5.7.4. 오차역전파법을 사용한 학습 구현하기





5.8. 정리


-

계산 그래프를 이용하면 계산 과정을 시각적으로 파악할 수 있다.

계산 그래프의 노드는 국소적 계산으로 구성된다. 국소적 계산을 조합해 전체 계산을 구성한다.

계산 그래프의 순전파는 통상의 계산을 수행한다. 한편, 계산 그래프의 역전파로는 각 노드의 미분을 구할 수 있다.

신경망의 구성 요소를 계층으로 구현하여 기울기를 효율적으로 계산할 수 있다. (오차역전파법)

수치 미분과 오차역전파법의 결과를 비교하면 오차역전파법의 구현에 잘못이 없는지 확인할 수 있다.(기울기 확인)




댓글0