5.1 계산그래프
오차역전파법을 제대로 이해하는 방법
1. 수식을 통해
2. 계산 그래프를 통해
계산그래프 - 계산 과정을 그래프로 나타낸 것. 복수의 노드와 에지(노드 사이의 직선)로 표현
5.1.1 계산그래프로 풀다
문제2) 슈퍼에서 사과를 2개, 귤을 3개 샀습니다. 사과는 1개에 100원, 귤은 1개 150원입니다. 소비세가 10%일 때 지불금액을 구하세요.
<계산 그래프를 이용한 문제풀이>
1. 계산그래프를 구성한다.
2. 그래프에서 계산을 왼쪽에서 오른쪽으로 진행한다.
순전파 - 계산을 왼쪽에서 오른쪽으로 진행하는 단계
5.1.2 국소적 계산
국소적 - 자신과 직접 관계된 작은 범위
각 노드에서의 계산은 국소적 계산
자신과 관련한 계산(입력된 두 숫자의 덧셈) 외에는 신경쓰지 않음
5.1.3 왜 계산 그래프로 푸는가?
<계산 그래프의 이점>
1. 국소적 계산 - 복잡해도 단순한 계산에 집중하여 문제를 단순화 할 수 있음
2. 중간 계산 결과를 모두 보관할 수 있음
3. 역전파를 통해 미분을 효율적으로 계산할 수 있음
'사과 개수에 대한 지불 금액의 미분' , '소비세에 대한 지불금액의 미분'
5.2 연쇄법칙
국소적 미분을 전달하는 원리 → 연쇄법칙에 따름
5.2.1 계산그래프의 역전파
신호 E에 노드의 국소적 미분을 곱한 후 다음 노드로 전달
(상류에서 전달된 값에 국소적 미분을 곱해 앞쪽 노드로 전달)
합성함수 - 여러함수로 구성된 함수
합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있음
연쇄법칙 = 함성함수의 미분에 대한 성질
역전파가 하는 일 = 연쇄법칙의 원리
5.3 역전파
<덧셈노드>
상류에서 전해진 미분 = 1
덧셈 노드 역전파는 1을 곱하기만 하기에 상류의 값을 그대로 다음 노드로 보냄.
<곱셈노드>
상류에서 전해진 미분- x였다면 역전파에서는 y / y였다면 역전파에서는 x
상류의 값에 순전파 때의 입력 신호들을 '서로 바꾼 값'을 곱해서 하류로 보냄
순방향 입력 신호의 값 필요
사과 가격에 대한 지불 금액의 미분, 사과 개수에 대한 지불 금액의 미분, 소비세에 대한 지불금액의 미분
5.4 단순한 계층 구현하기
forward() 순전파, backward() 역전파
<곱셈 계층 MulLayer>
class MulLayer:
def __init__(self):
self.x = float(None)
self.y = float(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
dy=dout*self.x
return dx,dy
순전파 때의 값을 '서로 바꿔' 곱한 후 하류로 흘림
dx=dout*self.y
dy=dout*self.x
backward() 가 받는 인수 - '순전파의 출력에 대한 미분'
<덧셈 계층 AddLayer>
class AddLayer:
def __init__(self):
None
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 활성화 함수 계층 구현하기
<ReLu 계층>
입력이 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
mask의 원소가 True인 곳(값이 0 이하)에서는 상류에서 전파된 dout을 0으로 설정
<Sigmoid 계층>
exp 노드, / 노드 등장
1단계
상류에서 흘러온 값(순전파의 출력 제곱 후 마이너스) 에 -y^2 을 곱해 하류로 전달
2단계
'+'노드는 상류의 값을 그대로 하류로 보냄
3단계
순전파 때의 출력을 곱해 하류로 전파
4단계
'X' 노드는 순전파 때의 값을 서로 바꿔 곱함
-1 곱해서 하류로 전파
중간과정을 묶어 'sigmoid' 노드 하나로 대체 가능
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = sigmoid(x)
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
5.6 Affine/Softmax 계층 구현하기
신경망의 순전파 때 수행하는 행렬의 곱 = 어파인 변환
Y= np.dot(X,W) + B
Y를 활성화 함수로 변환해 다음 층으로 전파함 - 신경망 순전파의 흐름
행렬의 곱 계산은 대응하는 차원의 원소 수를 일치시켜야 함
<Affine 계층의 역전파>
노드 사이에 행렬(다차원 배열)이 흐르고 있음.
→ 차원의 원소 수를 일치시켜야함
<배치용 Affine 계층>
데이터를 N개인 경우로 확장시킴.
묶은 데이터 = 배치
순전파 때는 편향이 XW에 대한 편향이 각 데이터에 더해짐
역전파 때는 각 데이터의 역전파 값이 편향의 원소에 모여야함
→ 편향의 역전파는 데이터에 대한 미분을 데이터마다 더해서 구함
<Softmax-with-Loss 계층>
softmax 계층은 입력 값을 정규화 (출력의 합이 1이 되도록 변형) 하여 출력함
신경망에서 수행하는 작업- 학습, 추론
추론할 때는 Softmax 계층을 일반적으로 사용 X, Softmax 앞의 Affine 층의 출력을 점수라고 함
손실함수인 교체 엔트로피 오차도 포함하여 'Softmax-with-Loss 계층'을 구현
Softmax 계층의 역전파 = (y1-t1, y2-t2, y3-t3) = Softmax 계층의 출력과 정답레이블의 오차(차분)
class SoftmaxWithLoss:
def __init__(self):
self.loss=None #손실
self.y=None #softmax의 출력
self.x=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 #batch size로 나눠서 데이터 1개당 오차를 앞 계층으로 전파
return dx
5.7 오차역전파법 구현하기
<신경망 학습의 순서>
1단계 - 미니배치
훈련 데이터 중 일부를 무작위로 가져옴. 선별한 데이터 = 미니배치, 미니배치의 손실함수 값을 줄이는 것이 목표
2단계 - 기울기 산출
미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구함
3단계 - 매개변수 갱신
가중치 매개변수를 기울기 방향으로 아주 조금 갱신
4단계 - 반복
1~3단계 반복
import sys, os
sys.path.append
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
<오차역전파법으로 구한 기울기 검증하기>
수치미분의 이점 - 구현하기 쉬움 / 느림
오차역전파법의 이점 - 매개변수 많아도 효율적으로 계산 가능 / 복잡해서 실수 있을 수 있음
import sys, os
sys.path.append
import numpy as np
from dataset.mnist import load_mnist
from ch05.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))
수치 미분으로 구한 기울기와 오차역전파법으로 구한 기울기의 오차 확인
<오차역전파법을 사용한 학습 구현하기>
import sys, os
sys.path.append
import numpy as np
from dataset.mnist import load_mnist
from ch05.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)
+) softmax, cross entropy error 함수 유도과정 따라해보기
'AI > 딥러닝' 카테고리의 다른 글
[밑바닥부터 시작하는 딥러닝] 7장 합성곱 신경망(CNN) (0) | 2023.04.15 |
---|---|
[밑바닥부터 시작하는 딥러닝] 6장 학습 관련 기술들 (1) | 2023.04.08 |
[밑바닥부터 시작하는 딥러닝] 4장 신경망 학습 (0) | 2023.03.25 |
[밑바닥부터 시작하는 딥러닝] 3장 신경망 (0) | 2023.03.18 |
[밑바닥부터 시작하는 딥러닝] 2장 퍼셉트론 (0) | 2023.03.18 |