본문 바로가기
DATA Science/DeepLearing from scratch

오차역전파법

by Rainbound-IT 2021. 5. 14.
반응형

수치미분을 통하여 신경망의 가중치 매개변수를 구했지만 수치미분은 시간이 오래걸린다.

 

계산그래프

계산 그래프를 통하여 계산과정을 알아보면 오차역전파법을 잘 이해할수 있다.

계산그래프

위처럼 어떠한 값을 수식에 넣고 계산해 나가는 것이 계산그래프 같은것.

문제의 흐름을 살펴 보면 1. 계산 그래프를 구성 2. 그래프에서 계산을 왼쪽에서 오른쪽으로 진행한다.

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

이것을 반대로 한다면 역전파(backward propagation) 라고 한다.

 

계산그래프를 사용하는 목적은 국소적계산과 역전파를 통해 '미분'을 효율적으로 계산할 수 있는 점에 있다.

국소적계산 - 각 노드에서 단순한 계산을 하고 중간에 저장을 할수 있다.

역전파의 계산

역전파를 사용하면 미분값을 전달하여 값을 구할수가 있다.

 

연쇄법칙

위 국소적 미분을 전달하는 원리는 연쇄법칙이다.

계산절차를 보면 신호 E에 노드의 국소적 미분을 곱한 후 다음 노드로 전달한다.

여기서 국소적 미분은 순전파때의 미분을 구한다는것이고 x에 대한 y의 미분을 구하는것이다.

 

합성함수 : 여러 함수로 구성된 함수 
합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다.

합성함수 미분의 예

 

위 식을 연쇄법칙을 통해 미분δz/δx를 구해보면

위에서 구한 미분을 이용하여 구할수가 있다.
위 과정을 그래프로 그려본것
위 그래프에 앞에서 구한 식을 넣어보았다.

 

 

역전파

이처럼 연쇄법칙은 이뤄 진다는 것을 알수 있었다.

이제 역전파의 구조를 설명해보자

 

  • 덧셈노드의 역전파

z=x+y를  대상으로 역전파를 살펴보자

미분은dz/dx =1, dz/dy =1 이 될것이다.

덧셈논드의 역전파 그래프
위를 예시로 그래프를 그려보면 역전파에서는 그대로 출력된다는걸 알수 있다.

 

  • 곱셈노드의 역전파

z=xy라고 가정하면

미분값은 dz/dx=y, dz/dy=x 이다

곱셈 노드 역전파를 구현보면 '서로 바꾼 값'을 곱해서 보낸다.

덧셈 노드 역전파와 달리 곱셈 노드 역전파의 경우 그대로 값을 보내지 않아서 순방향입력신호가 필요하여 곱셈노드 역전파 구현시 순전파의 입력 신호를 변수에 저장해둡니다.

 

단순한 계층 구현

위에서 해던것을 파이썬으로 구현한다. 곱셉노드를 mulLayer, 덧셈노드를 addLayer

 

곱셈계층

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MulLayer:
    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):
        dx = dout * self.y  # x와 y를 바꾼다.
        dy = dout * self.x
 
        return dx, dy
 
cs

여기서 dout은 미분값

위 사과 계산하는것을 코드로 구현한 것이다.

 

덧셈계층

덧셈계층은 곱셈계층과 구성은 같으나 그대로 보내주는것이므로 역전파쪽만 바꿔주면되므로 그냥 적겟다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
cs

 

이제 덧셈 계층과 곱셈 계층을 사용하여 사과2개, 귤2개를 사는 상황을 구현하자

 

활성화 함수 계층 구현하기 

이제 계산 그래프를 신경망에 적용해보자. 

신경망을 구성하는 층 각각을 클래스 하나로 구현한다.

ReLU와 Sigmoid 계층을 구현하겠다.

 

ReLU 계층

ReLu의 수식

 

위 식의 미분 값

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Relu:
    def __init__(self-> None:
        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
cs

Relu 클래스를 코드화 하면 이와 같다.

여기서 mask라는 것이 나오는데 True/false로 구성된 넘파이 배열이다.

 

Sigmoid 계층

sigmoid 함수
sigmoid 계층의 계산 그래프(순전파)

 

sigmoid 함수의 역전파 계산그래프 추가!

 

위 그래프를 간소화

 

위에서 나온 결과값을 정리

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Sigmoid:
    def __init__(self-> None:
        self.mask = 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
cs

코드화

 

Affine/Softmax 계층구현하기

 

어파인 변환(Affine transformation) : 신경망의 순전파 때 수행하는 행렬의 곱
어파인 변환을 수행하는 처리를 'affine 계층'이라 함
Affine 예시
행렬 차원수를 일치시켜야한다.
Affine 계층의 계산 그래프
행렬을 사용한 역전파의 식

WT는 전치 행렬을 뜻한다.

전치행렬(행,렬 바꾸는것)-wiki

 

 

Affine 계층의 역전파

위 그림에서 주의 해야할것은 형상. 즉 행렬의 차원 원소수를 조심해야한다.

행렬의 곱셈 - wiki

 배치용 Affine 계층

지금까지는 입력데이터로 X를 하나(스칼라값)을 입력햇다.

이번에는 데이터를 N개 넣어 X를 (N,2)로 입력해보자.

배치용 Affine 계층의 계산 그래프
코드 구현

 

Softmax-with-Loss 계층

 출력층에서 사용하는 소프트맥스 함수에 대해 살펴 보면 입력값을 정규화하여 출력한다.

입력 이미지가 Affine - ReLU - Softmax 로 정규화된다. (2가 정답이라서 0.991 확률이 나온걸 볼수있다.)

 

여기서 Softmax 계층 입력값을 정규화(출력의 합이 1이 되도록)하여 출력하고,

입력은 숫자가 10가지 이므로 10개이다.

신경망에서 수행하는 작업은 학습과 추론.
추론할때는 일반적으로 Softmax 계층은 사용하지 않는다. (위처럼 Affine계층의 출력을 인식결과로 사용한다.)
신경망에서 정규화하지 않은 출력 결과를 점수(score)라고 한다.
즉, 신경망 추론에서 답을 하나만 내는경우에는 가장 높은 점수만 알면 되니 Softmax 계층은 필요 없다. 
But 신경망 학습에서는 Softmax 계층은 필요하다.
교차 엔트로피오차를 포함한 'Softmax-with-Loss 계층'
Softmax-with-Loss 계층의 계산그래프

 여기서 결과가 (y1-t1, y2-t2, y3-t3)이라는 점에 주목해야한다.

(y1,y2,y3)는 Softmax 계층의 출력, (t1,t2,t3)는 정답레이블이므로

위 결과가 Softmax계층의 출력과 정답레이블의 차분인것이다. 

이 차이인 오차가 앞 계층에 전해지는 것이다.

 

신경망 학습의 목적은

신경망 출력(Softmax의 출력)이 정답 레이블과 가까워지도록 가중치 매개변수의 값을 조정하는 것이다. 

그래서 오차를 효율적으로 앞 계층에 전달해야한다.

 

 오차역전파법 구현하기

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

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

2단계 - 기울기 산출
미니 배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구하는것. 

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

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict
 
 
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
        
    # x : 입력 데이터, t : 정답 레이블
    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
        
    # x : 입력 데이터, t : 정답 레이블
    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):
        # forward
        self.loss(x, t)
 
        # backward
        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'], grads['b1'= self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'= self.layers['Affine2'].dW, self.layers['Affine2'].db
 
        return grads
cs

 

layers: 순서가 있는 딕셔너리 변수로 신경망의 계층 보관

lastLayer: 신경망의 마지막 계층으로, SoftmaxWithLoss 계층

 

 

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

수치 미분은 구현하기 쉽지만 버그가 별로 없으나 오차역전법은 구현하기 복잡하여 오류가 많으므로

서로 비교하여 일치하는 작업인 기울기 확인(gradient check)을 한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
 
# 데이터 읽기
(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]) )
    print(key + ":" + str(diff))
cs

 

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

다른점은 기울기를 오차역전파법이라는것 빼고는 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import sys, os
sys.path.append(os.pardir)
 
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
 
# 데이터 읽기
(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)
 
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
 
train_loss_list = []
train_acc_list = []
test_acc_list = []
 
iter_per_epoch = max(train_size / batch_size, 1)
 
for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    # 기울기 계산
    #grad = network.numerical_gradient(x_batch, t_batch) # 수치 미분 방식
    grad = network.gradient(x_batch, t_batch) # 오차역전파법 방식(훨씬 빠르다)
    
    # 갱신
    for key in ('W1''b1''W2''b2'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print(train_acc, test_acc)
cs

 

반응형

'DATA Science > DeepLearing from scratch' 카테고리의 다른 글

CNN --- 합성곱 신경망  (0) 2021.05.20
딥러닝 학습 관련 기술들  (0) 2021.05.17
신경망 학습 - 학습 알고리즘 구현  (0) 2021.05.13
신경망 학습  (2) 2021.05.12
신경망  (0) 2021.05.12

댓글