본 글은 "바닥부터 시작하는 딥러닝" 책을 바탕으로 작성되었습니다.
- Chapter4에서 신경망의 가중치 매개변수의 기울기 구할 때, 수치 미분을 사용하였다.
- 수치 미분은 구현이 쉽지만 계산 시간이 오래 거리는 단점이 있으므로,
이번 Chapter5에서는 효율적으로 계산할 수 있는 '오차역전파법'에 대해서 학습한다.
5.1 계산 그래프
- 계산 그래프와 같이, 계산을 왼쪽에서 오른쪽으로 전파하는 단계를 "순전파"라고 한다.
- 순전파의 반대 방향의 단계를 "역전파"라고 한다.
- 계산 그래프의 이점은 순전파와 역전파를 활용하여, 각 변수의 미분을 효율적으로 구할 수 있다.
5.2 연쇄법칙
- 연전파는 '국소적인 미분'을 순반향과는 반대로 전달한다.
(국소적: 자신과 직접 관계된 작은 범위)
- 국소적 미분을 전달하는 원리는 "연쇄법칙'이라고 한다.
- 합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다. 이는 "연쇄법칙의 원리"이다.
(합성함수: 여러 함수로 구성된 함수)
5.3 단순한 계층 구현하기
5.3.1 곱셈 계층
- forward( )는 순전파, backward( )는 역전파를 처리한다.
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
- 아래는 사과 100원짜리 2개를 구매한 가격을 구하는 예제이다. (구매가격에는 부가세 10% 포함)
- backward()가 받는 인수는 '순전파의 출력에 대한 미분'이다.
apple = 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)
print(f"forward: {price}")
#역전파
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(f"backwoard: {dapple}, {dapple_num}, {dtax}")
5.3.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
- 아래는 위에서 구현한 덧셈 계층의 코드를 이용하여, 사과 100원짜리 2개와 귤 150원짜리 3개의 값을 추출하는 코드이다
apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1
# 계층들
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()
# 순전파
apple_price = mul_apple_layer.forward(apple, apple_num)
orange_price = mul_orange_layer.forward(orange, orange_num)
all_price = add_apple_orange_layer.forward(apple_price, orange_price)
price = mul_tax_layer.forward(all_price, tax)
# 역전파
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice)
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price)
dorange, dorange_num = mul_orange_layer.backward(dorange_price)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(price)
print(dapple_num, dapple, dorange, dorange_num, dtax)
5.4 활성화 함수 계층 구현하기
5.4.1 ReLU 계층
- 순전파 때 입력 값 x가 0보다 크면, 역전파는 상류 값을 그대로 하류로 보낸다.
- 순전파 때 입력 값 x가 0이하면, 역전파 때는 하류에 신호를 보내지 않고 0을 반환한다.
- mask라는 인스턴스 변수는 True/False로 구성된 넘파이 배열이다.
순전파 입력인 x의 원소 값이 0 이하인 인덱스는 True, 그 외 0보다 큰 원소는 False로 유지한다.
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
- 아래는 mask변수에 대해 이해하기 쉽게 확인한 부분이다.
- 아래 결과와 같이, x가 0이하면 True값으로 설정되는 것을 알 수 있다.
- 이를 통해, mask 원소가 True인 곳에는 0으로 설정한다.
5.4.2 Sigmoid 계층
- 시그모이드 계층의 역전파는 순전파의 출력 y로만 계산할 수 있다. (책 참고)
- 시그모이드 계층을 파이썬으로 구현하면 아래와 같다.
- 아래 구현은 순전파의 출력을 인스턴스 변수 out에 보관했다가, 역전파 계산 때, 그 값을 사용한다.
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.5 Affine/Softmax 계층 구현하기
5.5.1 Affine 계층
- 순전파 편향 덧셈은 각각의 데이터에 더해진다. 이에 대한 예제는 아래와 같다.
- 역전파 때는 각 데이터의 역전파 값이 편향의 원소에 모여져야되는데, 이는 아래와 같다.
- 편향의 역전파는 데이터에 대한 미분을 각 데이터마다 더해서 구한다.
- Affine 구현은 아래와 같다.
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
5.5.2 Softmax-with-Loss 계층
- softmax-with-loss 계층은 소프트맥스 계정을 구현하고, 손실함수인 교차 엔트로피 오차도 포함하여 구현한다는 의미이다
- 역전파 때는 전파하는 값을 배치 수로 나눠, 데이터 1개당 오차를 앞 계층으로 전파한다.
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
5.6 오차역전파법 구현하기
- 지금까지 구현한 계층을 조합하여, 신경망을 구축한다.
5.6.1 신경망 학습의 전체 그림
- 전제 -> 미니배치 -> 기울기 산출 -> 매개변수 갱신 -> 반복
5.6.2 오차역전파법을 적용한 신경망 구현하기
- OrderedDict에 신경망의 계층을 보관하는데, 이는 순서 있는 딕셔너리이다.
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):
#순전파
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.6.3 오차역전파법으로 구한 기울기 검증하기
- 지금까지 기울기는 구하는 방법은 수치 미분을 사용하는 방법 또는 해석적으로 수식을 풀어 구하는 방법이 있었다.
- 해석적 방법은 오차역전파법을 이용하여, 매개변수가 많아도 효율적으로 계산할 수 있다.
- 수치 미분으로 느리지만, 오차역전파법이 제대로 구현되었는지 확인하기 위해서는 필요하다.
(수치 미분은 구현이 쉽지만, 오차역전파법은 복잡해서 종종 실수가 발생한다.)
- 수치 미분 결과와 오차역전파법의 결과를 비교해서, 기울기가 일치하는지 확인하는 작업을 "기울기 확인"이라고 한다.
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))
5.6.4 오차역전파법을 사용한 학습 구현하기
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)
'AI' 카테고리의 다른 글
Building Systems with the ChatGPT API (0) | 2024.08.03 |
---|---|
Chapter 6. 학습 관련 기술들 (0) | 2024.07.13 |
Chapter 4. 신경망 학습 (0) | 2024.06.23 |
Chapter 3. 신경망 (0) | 2024.06.18 |
Chapter2. 퍼셉트론 (1) | 2024.06.17 |