Skip to content

Latest commit

 

History

History
325 lines (215 loc) · 15.1 KB

220209.md

File metadata and controls

325 lines (215 loc) · 15.1 KB

Day 117

Deep Learning from Scratch

5. 오차역전파법(backpropagation)

활성화 함수 계층 구현

ReLU 계층

활성화 함수로 사용되는 ReLU의 수식은 다음과 같다.

e 5 7

이를 x에 대한 y의 미분을 하여 아래의 식으로 만든다.

e 5 8

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

fig 5-18

클래스로 구현하면 다음과 같다.

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 클래스는 mask라는 인스턴스 변수를 가진다. mask는 True/False로 구성된 넘파이 배열로, 순전파의 입력인 x의 원소 값이 0 이하인 인덱스는 True, 그 외는 False로 유지한다.

x = np.array([[1.0, -0.5], [-2.0, 3.0]])
print(x)
mask = (x <= 0)
print(mask)
print(x[mask])

[[ 1. -0.5]
[-2. 3. ]]
[[False True]
[ True False]]
[-0.5 -2. ]

순전파 때의 입력 값이 0이면 역전파 때의 값은 0이 되어야 해서 역전파 때는 순전파 때 만들어둔 mask를 써서 mask의 원소가 True인 곳에는 상류에서 전파된 dout을 0으로 설정한다.

Sigmoid 계층

시그모이드 함수의 수식과 계산 그래프는 다음과 같다.

e 5 9

fig 5-19

'exp' 노드는 y = exp(x) 계산을, '/' 노드는 y = 1/x 계산을 수행한다.

역전파 1단계

'/'노드(y = 1/x)를 미분하면 아래의 식이 된다.

e 5 10

여기서 x는 1 + exp(-x) 전체를 x 하나로 치환한 것으로 보면 된다. 이전에 t로 치환한 것처럼 생각하면 될 것이다.

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

fig 5-19(1)

역전파 2단계

'+' 노드는 상류의 값을 여과 없이 하류로 내보낸다.

fig 5-19(2)

역전파 3단계

'exp'노드(y=exp(x))의 미분은 다음과 같다.

e 5 11

상류의 값에 순전파 때의 출력(이 예시에서는 exp(-x))을 곱해 하류로 전파한다.

fig 5-19(3)

역전파 4단계

'x' 노드는 순전파 때의 값을 서로 바꿔 곱한다.

fig 5-20

이 최종 출력은 순전파의 입력 x와 출력 y만으로 계산할 수 있다. 그래서 위의 과정을 sigmoid 노드 하나로 대체할 수 있다.

fig 5-21

최종 출력은 다음과 같이 정리할 수도 있다.

e 5 12

즉, Sigmoid 계층의 역전파는 순전파의 출력인 y만으로 계산할 수 있다.

fig 5-22

구현하면 다음과 같다.

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

Affine/Softmax 계층 구현

Affine 계층

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

X, W, B가 각각 형상이 (2,), (2, 3), (3,)인 다차원 배열이며 뉴런의 가중치 합 Y는 np.dot(X, W) + B로 계산한다. 계산 후에 Y를 활성화 함수로 변환해 다음 층으로 전파하는 것이 신경망의 순전파의 흐름이다.

앞의 행렬의 곱과 편향의 합을 계산 그래프로 그리면 다음과 같다.

fig 5-24

노드 사이에 흐르는 것이 행렬임을 주의해야 한다.

역전파는 다음과 같은 수식이 도출된다.

e 5 13

WT는 W의 전치행렬을 뜻한다. 전치행렬은 W의 (i, j) 위치의 원소를 (j, i) 위치로 바꾼 것으로 표현하면 다음과 같다.

e 5 14

역전파를 계산 그래프로 표현하면 다음과 같다.

fig 5-25

배치용 Affine 계층

위의 Affine 계층은 입력 데이터로 X 하나만을 고려한 것이다.

배치용 Affine 계층을 계산 그래프로 그리면 다음과 같다.

fig 5-27

X의 형상이 (N, 2)가 된 것 외에 변화가 없다.

순전파 때의 편향 덧셈은 X, W에 대한 편향이 각 데이터에 더해진다.

X_dot_W = np.array([[0, 0, 0], [10, 10, 10]])
B = np.array([1, 2, 3])
print(X_dot_W)
print(X_dot_W + B)

[[ 0 0 0]
[10 10 10]]
[[ 1 2 3]
[11 12 13]]

순전파의 편향 덧셈은 각각의 데이터에 더해져서 역전파 때는 각 데이터의 역전파 값이 편향의 원소에 모여야 한다.

dY = np.array([[1, 2, 3], [4, 5, 6]])
print(dY)
dB = np.sum(dY, axis=0)
print(dB)

[[1 2 3]
[4 5 6]]
[5 7 9]

이 예에서는 데이터가 2개(N = 2)라고 가정한다. 편향의 역전파는 그 두 데이터에 대한 미분을 데이터마다 더해서 구한다. 그래서 np.sum()에서 0번째 축(데이터를 단위로 한 축)에 대해서(axis=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)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        
        return dx

Softmax-with-Loss 게층

소프트맥스 함수는 입력 값을 정규화하여 출력한다.

fig 5-28

위의 그림은 손글씨 숫자 인식에서의 예시이다.

Softmax 계층은 입력 값을 정규화(출력의 합이 1이 되도록 변형)하여 출력한다. 손글씨 숫자는 가짓수가 10개이므로 Softmax 계층의 입력은 10개가 된다.

신경망에서 수행하는 작업은 학습과 추론 두 가지이다. Softmax 앞의 Affine 계층의 출력을 점수라 하고, 신경망 추론에서 답을 하나만 내는 경우에는 가장 높은 점수만 알면되서 Softmax 계층이 필요 없다. 하지만 신경망을 학습할 때는 Softmax 계층이 필요하다.

소프트맥스 계층을 구현할 때 손실 함수인 교차 엔트로피 오차도 포함하면 다음과 같은 계산 그래프가 나온다.

fig 5-29

계산 그래프의 Softmax 계층을 먼저 보면 수식과 계산 그래프는 다음과 같다.

e a 1

fig a-2

계산 그래프는 지수의 합(수식의 분모 항)을 S로 표기했고, 최종 출력은 (y1, y2, y3)이다.

Cross Entropy Error 계층의 수식과 계산 그래프는 다음과 같다.

e a 2

fig a-3

Cross Entropy Error 계층의 역전파는 다음과 같이 표현된다.

fig a-4

역전파의 초깃값은 1이고, 'log' 노드의 역전파는 다음 식을 따른다는 것만 알면 어려운 것은 없다.

y = log x  
dy/dx = 1/x

결과는 (-t1/y1, -t2/y2, -t3/y3)이며, 이 값이 Softmax 계층으로의 역전파 입력이 된다.

역전파 1단계
Softmax 계층의 역전파는 우선 Cross Entropy Error 계층의 역전파 값이 흘러들어 오는 것으로 시작한다.

fig a-4(1)

역전파 2단계
'x' 노드에서 순전파의 입력들을 서로 바꿔 곱하는데 여기서 다음 계산이 이뤄진다.

e a 3

fig a-4(2)

그냥 y1을 다시 a와 S에 관한 식으로 돌려놓는 과정이다.

역전파 3단계
순전파 때 여러 갈래로 나뉘어 흘렀다면 역전파 때는 그 반대로 흘러온 여러 값을 더한다. 그래서 3개의 갈라진 역전파의 값(-t1S, -t2S, -t3S)이 더해진다.
이 더해진 값이 '/' 노드의 역전파를 거쳐 1/S * (t1 + t2 + t3)이 된다.
여기서 (t1, t2, t3)은 원-핫 벡터로 표현된 정답 레이블이다.
원-핫 벡터란 (t1, t2, t3) 중 단 하나만 1이고 나머지는 전부 0임을 뜻한다.
따라서 t1 + t2 + t3 = 1이 된다.

fig a-4(3)

역전파 4단계
'+'노드는 입력을 여과 없이 보낸다.

fig a-4(4)

역전파 5단계
'x'노드는 입력을 서로 바꾼 곱셈인데 여기서는 y1 = exp(a1)/S를 이용해 식을 변형한다.

fig a-4(5)

역전파 6단계 'exp' 노드에서는 다음 관계식이 성립된다.

e a 4

그리고 두 갈래의 입력의 합에 exp(a1)을 곱한 수치가 여기에서 구하는 역전파이다.
식으로 쓰면 (1/S - t1/exp(a1)) * exp(a1)이 되고, 이를 변형하면 y1 - t1이 된다. 나머지 a2와 a3의 역전파도 각각 y2 - t2와 y3 - t3가 된다.

이를 간결하게 표현하면 다음과 같다.

fig 5-30

역전파의 결과를 보면 (y1, y2, y3)는 Softmax 계층의 출력이고, (t1, t2, t3)는 정답 레이블이므로 결과인 (y1 - t1, y2 - t2, y3 - t3)는 Softmax 계층의 출력과 정답 레이블의 차분인 것이다.

신경망 학습의 목적은 신경망의 출력(Softmax의 출력)이 정답 레이블과 가까워지도록 가중치 매개변수의 값을 조정하는 것이다. 따라서 신경망의 출력과 정답 레이블의 오차를 효율적으로 앞 계층에 전달해야 한다.

앞의 (y1 - t1, y2 - t2, y3 - t3)라는 결과는 Softmax 계층의 출력과 정답 레이블의 차이로, 신경망의 현재 출력과 정답 레이블의 오차를 있는 그대로 드러내는 것이다.

예를 들어 정답 레이블이 (0, 1, 0)일 때 Softmax 계층이 (0.3, 0.2, 0.5)를 출력했을 때 정답의 인덱스는 1이지만 출력에서는 확률이 겨우 0.2라서, 신경망이 제대로 인식하지 못하고 있다는 것을 알 수 있다. 이 경우 Softmax 계층의 역전파는 (0.3, -0.8, 0.5)라는 큰 오차를 전파해서 이 오차로부터 앞 계층들은 큰 깨달음을 얻는다.
만약 Softmax 계층이 (0.01, 0.99, 0)을 출력했으면 역전파가 보내는 오차는 비교적 작은(0.01, -0.01, 0)이다. 이러면 앞 계층으로 전달된 오차가 작으므로 학습하는 정도도 작아진다.

구현하면 다음과 같다.

class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None # 손실
        self.y = None    # softmax의 출력
        self.t = None    # 정답 레이블(원-핫 벡터)
        
    def forward(self, x, t):
        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

역전파 때는 전파하는 값을 배치의 수로 나눠서 데이터 1개당 오차를 앞 계층으로 전파하는 것을 생각해야 한다.