오차역전파법
오차역전법을 풀어쓰면 '오차를 역(반대 방향)으로 전파하는 방법(backward propagation of errors')이다.
계산 그래프
계산 그래프(computational graph)는 계산 과정을 그래프로 나타낸 것이다.
그래프는 노드(node)와 엣지(edge)로 표현된다.
문제 1: 현빈 군은 슈퍼에서 1개에 100원인 사과를 2개 샀습니다. 이 때 지불 금액을 구하세요. 단, 소비세가 10% 부과됩니다.
계산 그래프는 계산 과정을 노드와 화살표로 표현한다.
노드는 원으로 표기하고 원 안에 연산 내용을 적는다.
계산 결과를 화살표 위에 적어 각 노드의 계산 결과가 왼쪽에서 오른쪽으로 전해지게 한다.
문제 1을 계산 그래프로 풀면 아래 그림처럼 된다.
x2와 x1.1을 각각 하나의 연산으로 취급해 원 안에 표기했지만, 곱셈만을 연산으로 생각할 수도 있다.
그렇게 되면 사과의 개수와 소비세가 변수가 되어 원 밖에 표기하게 된다.
문제 2: 현빈 군은 슈퍼에서 사과를 2개, 귤을 3개 샀습니다. 사과는 1개에 100원, 귤은 1개 150원입니다. 소비세가 10%일때 지불 금액을 구하세요.
덧셈 노드가 새로 등장하여 사과와 귤의 금액을 합산한다.
계산 그래프를 이용한 문제풀이는 다음 흐름으로 진행한다.
- 계산 그래프를 구성한다.
- 그래프에서 계산을 왼쪽에서 오른쪽으로 진행한다.
여기서 2번째 '계산을 왼쪽에서 오른쪽으로 진행'하는 단계를 순전파(forward propagation)라고 한다.
반대 방향의 전파는 역전파(backward propagation)라고 한다.
국소적 계산
계산 그래프의 특징은 '국소적(자신과 직접 관계된 작은 범위) 계산'을 전파함으로써 최종 결과를 얻는다는 점에 있다.
국소적 계산은 결국 전체에서 어떤 일이 벌어지든 상관없이 자신과 관계된 정보만으로 결과를 출력할 수 있다는 것이다.
위의 그림처럼 여러 식품을 구입하여(복잡한 계산을 거쳐) 총 금액이 4,000원이 되었다.
핵심은 각 노드에서의 계산은 국소적 계산이라는 점이다.
각 노드는 자신과 관련한 계산 외에는 아무것도 신경쓸 게 없다.
왜 계산 그래프로 푸는가?
계산 그래프의 이점은 국소적 계산이다.
전체가 아무리 복잡해도 각 노드에서는 단순한 계산에 집중하여 문제를 단순화할 수 있다.
또다른 이점으로는, 중간 계산 결과를 모두 보관할 수 있다.
실제 계산 그래프를 사용하는 가장 큰 이유는 역전파를 통해 '미분'을 효율적으로 계산할 수 있는 점에 있다.
문제 1을 다시 보자.
문제 1: 현빈 군은 슈퍼에서 1개에 100원인 사과를 2개 샀습니다. 이 때 지불 금액을 구하세요. 단, 소비세가 10% 부과됩니다.
사과 2개를 소비세를 포함한 최종 금액을 구하는 문제였다.
여기서 사과 가격이 오르면 최종 금액에 어떤 영향을 끼치는지 알고 싶다고 하자.
'사과 가격에 대한 지불 금액의 미분'을 구하는 문제에 해당한다.
이 값은 계산 그래프에서 역전파를 하면 구할 수있다.
그림과 같이 역전파는 굵은 선으로 그린다.
이 전파는 국소적 미분을 전달하고 그 미분 값은 화살표의 아래에 적는다.
이 결과로부터 '사과 가격에 대한 지불 금액의 미분'값은 2.2라고 할 수 있다.
사과가 1원 오르면 최종 금액은 2.2원 오른다는 뜻이다.(정확히는 사과 값이 아주 조금 오르면 최종 금액은 그 작은 값의 2.2배만큼 오른다는 뜻이다.)
연쇄법칙
국소적 미분을 전달하는 원리는 연쇄법칙(chain rule)에 따른 것이다.
y = f(x)라는 계산의 역전파를 그림으로 그린 것이다.
역전파의 계산 절차는 신호 E에 노드의 국소적 미분을 곱한 후 다음 노드로 전달하는 것이다.
y = f(x) = x^2라면 dx/dx = 2x가 된다.
이 국소적인 미분을 상류에서 전달된 값(E)에 곱해 앞쪽 노드로 전달하는 것이다.
연쇄법칙이란?
합성함수란 여러 함수로 구성된 함수이다.
예를 들어 z = (x+y)^2 식은 아래의 그림처럼 두개의 식으로 구성된다.
연쇄법칙은 합성 함수의 미분에 대한 성질이며 다음과 같이 정의된다.
합성함수의 미분은 합성함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다.
이것이 연쇄법칙의 원리이다.
위의 식을 예로 들자면, x에 대한 z의 미분은 t에 대한 z의 미분과 x에 대한 t의 미분의 곱으로 나타낼 수 있다.
위의 식은 dt를 지울 수 있다.
연쇄 법칙을 써서 dz/dx를 구해본다. 국소적 미분(편미분)을 구한다.
최종적으로 구하고 싶은 dz/dx는 두 미분을 곱해 계산한다.
연쇄법칙과 계산 그래프
맨 왼쪽 역전파를 보면 다 지워져 dz/dx, 즉 x에 대한 z의 미분이 된다.
역전파가 하는 일은 연쇄법칙의 원리와 같다는 것이다.
역전파
- 덧셈 노드의 역전파
z = x + y 의 미분은 다음과 같이 계산할 수 있다.
이를 계산 그래프로 그린다면 아래와 같이 구성된다.
여기서 나오는 L은 임의의 계산을 통해 나오는 최종적인 값이다.
아래 그림을 참고하자.
예를 들어 10 + 5 = 15라는 계산이 있고, 상류에서 1.3이라는 값이 흘러온다고 생각해보자.
다음과 같은 계산그래프가 된다.
덧셈 노드 역전파는 입력 신호를 다음 노드로 출력할 뿐이므로 1.3을 그대로 다음 노드로 전달한다.
- 곱셈 노드의 역전파
z = xy라는 식을 보자. 미분은 다음과 같이 된다.
계산 그래프는 다음과 같다.
곱셈 노드 역전파는 상류의 값에 순전파 때의 입력 신호들을 '서로 바꾼 값'을 곱해서 하류로 보낸다.
예를 들어 10 x 5 = 50 과 상류에서는 1.3값이 흘러온다면, 계산 그래프는 다음과 같이 된다.
곱셈의 역전파에서 입력 신호를 바꾼 값을 곱하여 각각 1.3 x 5 = 6.5 , 1.3 x 10 = 13이 된다.
덧셈의 역전파에서는 상류의 값을 그대로 흘려보내기 때문에 순방향 입력 신호의 값은 필요하지 않다.
곱셈의 역전파는 순방향 입력 신호의 값이 필요하다.
그래서 곱셈 노드를 구현할 때는 순전파의 입력 신호를 변수에 저장해둔다.
사과 쇼핑의 예
사과의 가격, 사과의 개수, 소비세라는 세개의 변수가 각각 최종 금액에 어떤 영향을 주느냐를 풀어본다.
'사과 가격에 대한 지불 금액의 미분', ' 사과 개수에 대한 지불 금액의 미분', '소비세에 대한 지불 금액의 미분'을 구하는 것에 해당한다.
이를 계산 그래프의 역전파를 사용해서 풀면 다음과 같이 된다.
단순한 계층 구현
곱셈 계층 구현하기
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
init에서는 x,y를 초기화한다. 순전파 시의 입력값을 유지하기 위해 사용한다.
이 클래스를 이용하여 사과 쇼핑을 구현해보자.
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(price) #220
#역전파
dprice = 1
dapple_price,dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(dapple,dapple_num,dtax) # 2.2 110 220
덧셈 계층
덧셈 계층의 구현은 다음과 같다.
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
덧셈 계층에서는 초기화가 필요없으니 pass한다.
이제 덧셈,곱셈 계층을 이용하여 사과 2와 귤 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) #1
orange_price = mul_orange_layer.forward(orange, orange_num) #2
all_price = add_apple_orange_layer.forward(apple_price, orange_price) #3
price = mul_tax_layer.forward(all_price, tax) #4
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice) #4
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price) #3
dorange, dorange_num = mul_orange_layer.backward(dorange_price) #2
dapple, dapple_num = mul_apple_layer.backward(dapple_price) #1
print(price) #715
print(dapple_num,dapple,dorange,dorange_num,dtax) # 110 2.2 3.3 165 650
활성화 함수 계층 구현하기
ReLU와 Sigmoid 구현
ReLU 계층
활성화 함수 ReLU의 수식은 다음과 같다.
이 식에서 x에 대한 y의 미분은 다음과 같다.
순전파 때의 입력인 x가 0보다 크면 역전파는 상류의 값을 그대로 하류로 흘린다. 반면 순전파때 x가 0 이하면 역전파때는 하류로 신호를 보내지 않는다(0을 보냄).
계산 그래프는 아래와 같다.
코드로 구현하면 다음과 같다.
신경망 계층의 함수는 Numpy Matrix를 인수로 받는다고 가정한다.
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는 boolean형 numpy matrix로, 순전파의 입력인 x의 원소값이 0 이하인 인덱스는 True, 그외는 False로 유지한다.
순전파의 입력값이 0 이하면 역전파 때의 값은 0이 되어야 한다.
그래서 역전파 때는 순전파 때 만들어둔 mask를 써서 mask의 원소가 True인 곳에는 상류에서 전파된 dout을 0으로 설정한다.
Sigmoid계층
시그모이드의 수식은 다음과 같다.
이것을 계산 그래프로 만든다면 아래와 같다.
곱셈,덧셈 노드 이외에 나눗셈과 exp노드가 등장했다.
exp노드는 y = exp(x) 계산을 수행하고 '/'노드는 y = 1 / x 계산을 수행한다.
위의 계산은 국소적 계산의 전파로 이뤄진다.
역전파의 흐름을 오른쪽에서 왼쪽으로 한단계씩 짚어보자.
1단계
'/'노드, 즉 y = 1 / x 를 미분하면 다음과 같다.
위 식에 따르면 역전파 때는 상류에서 흘러온 값이 -y^2를 곱해 하류로 전달한다.
계산 그래프는 다음과 같다.
2단계
덧셈 노드는 상류의 값을 여과없이 흘려보내는게 전부다.
3단계
exp노드는 y = exp(x) 연산을 수행하며, 그 미분은 다음과 같다.
계산 그래프에서는 상류의 값에 순전파 때의 출력(exp(-x))을 곱해 하류로 전달한다.
4단계
곱셈 노드는 순전파 때의 값을 '서로 바꿔' 곱한다. 이 예에서는 -1을 곱하면 되겠다.
계산 그래프의 중간 과정을 모두
묶어 다음과 같이 sigmoid 노드 하나로 대체할 수 있다.
또한, 이 식은 다음과 같이 정리해서 쓸 수 있다.
이처럼 Sigmoid 계층의 역전파는 순전파의출력(y)만으로 계산할 수 있다.
Sigmoid계층을 Python으로 구현하면 다음과 같다.
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
이 구현에서는 순전파의 출력을 out에 보관했다가, 역전파 계산때 그 값을 사용한다.
Affine/Softmax 계층 구현하기
복습
신경망의 순전파에서는 가중치 신호의 총합을 계산하기 때문에 행렬의 곱(np.dot())을 사용했다.
뉴런의 가중치 합은 Y = np.dot(X,W) + B 처럼 계산한다.
Y를 활성화 함수로 변환해 다음 층으로 전파하는 것이 신경망 순전파의 흐름이였다.
X와 W의 곱은 아래 그럼처럼 대응하는 차원의 수를 일치시켜야 한다.
신경망의 순전파 때 수행하는 행렬의 곱은 기하학에서는 어파인 변환(affine transformation)이라고 한다.
어파인 변환을 수행하는 처리를 Affine 계층 이라는 이름으로 구현한다.
지금까지의 계산 그래프는 노드 사이에 스칼라값이 흘렀는데, 이 예에서는 행렬이 흐르고 있다.
행렬을 사용한 역전파도 행렬의 원소마다 전개해보면 스칼라값을 사용한 지금까지의 계산 그래프와 같은 순서로 생각할 수 있다.
전개해보면 아래 식이 도출된다.
T는 전치행렬을 뜻한다. (i,j)위치의 원소를 (j,i)위치로 바꾼 것을 말한다.
수식은 다음과 같다.
위의 식과 같이 W의 형상이 (2,3)이였다면 전치행렬의 형상은 (3,2)가 된다.
계산 그래프에서는 각 형상이 같음을 기억하자
X와 dL/dX는 같고, W와 dL/dW도 같은 형상이다.
행렬의 곱에서는 대응하는 차원의 원소 수를 일치시켜야 하는데, 이를 위해서는 위에 전치행렬을 이용한 식을 이용해야 할 수도 있기 때문이다.
배치용 Affine 계층
위에서 설명한 Affine 계층은 입력 데이터로 X 하나만을 고려한 것이였다.
데이터 N개를 묶어 순전파하는 경우, 즉 배치용 Affine 계층을 계산 그래프로 그려본다.
기존과 다른 부분은 X의 형상이 (N,2)가 된 것이다.
편향을 더할때 주의해야 한다. 순전파 때의 편향 덧셈은 X * W에 대한 편향이 각 데이터에 더해진다.
예를 들어 N = 2(데이터가 2개)인 경우 편향은 그 두 데이터 각각에 더해진다.
아래 코드 예시를 보자.
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)
순전파의 편향 덧셈은 각각의 데이터에 더해진다.
그래서 역전파 때는 각 데이터의 역전파 값이 편향의 원소에 모여야 한다.
dY = np.array([[1,2,3],[4,5,6]])
print(dY)
dB = np.sum(dY,axis=0)
print(dB)
데이터가 2개라고 가정한다.
편향의 역전파는 그 두 데이터에 대한 미분을 데이터마다 더해서 구한다.
그래서 np.sum()에서 0번째 축(데이터를 단위로 한 축)에 대해서 (axis=0)의 총합을 구하는 것이다.
다음은 Affine 클래스 구현 코드이다.
import math as m
import numpy as np
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 계층
소프트맥스함수는 입력값을 정규화하여 출력한다.
예를 들어 손글씨 숫자 인식에서의 Softmax 계층의 출력은 다음과 같다.
Softmax 계층은 입력값을 정규화(출력의 합이 1이 되도록)하여 출력한다.
숫자는 10개이므로 계층의 입력도 10개가 된다.
신경망에서 수행하는 작업은 학습과 추론 두가지이다.
추론할때는 일반적으로 Softmax를 사용하지 않는다.
신경망에서 정규화하지 않는 출력 결과를 점수(Score)라고 한다.
즉, 신경망 추론에서 답을 하나만 내는 경우에는 가장 높은 점수만 알면 되니 Softmax가 필요없다는 것이다.
반면, 신경망을 학습할 때는 Softmax 계층이 필요하다.
Softmax 계층을 구현할때, 손실함수인 교차 엔트로피 함수도 포함하여 'Softmax-with-Loss 계층'이라는 이름으로 구현한다.
이 계{층의 계산 그래프는 다음과 같다.
위의 계산 그래프는 다소 복잡한데, 다음과 같이 간소화 할 수 있다.
Softmax는 (a1,a2,a3) 입력 데이터를 (y1,y2,y3)로 정규화하여 출력한다.
교차 엔트로피 계층은 Softmax의 출력 (y1,y2,y3)과 정답 레이블(t1,t2,t3)를 받고 이 테이터들로부터 손실 L을 출력한다.
(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 계층의 앞 계층들은 그 큰 오차로부터 큰 깨달음을 얻게 된다.
이번에는 Softmax 계층이 (0.01,0.99,0)을 출력했을 때의 역전파는 (0.01,-0.01,0)이다/ 앞 계층으로 전달된 오차가 작으므로 학습하는 정도도 작아진다.
다음은 Softmax-with-Loss 계층을 구현한 코드이다.
import numpy as np
class SoftmaxWithLoss:
def __init__(self):
self.loss = None #loss
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개당 오차를 앞 계층으로 전파하는 점에 주의하자.
오차역전파법 구현하기
신경망 학습의 순서이다.
전체 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습'이라고 한다. 신경망 학습은 다음과 같이 4단계로 수행한다.
- 비니배치: 훈련 데이터 중 일부를 무작위로 가져온다. 미니배치의 손실함수 값을 줄이는 것이 목표이다.
- 기울기 산출: 미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다.
- 매개변수 갱신: 가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다
- 반복: 1~3단계를 반복한다
오차역전파법이 등장하는 단계는 2단계 '기울기 산출'이다.
앞장에서는 수치미분을 통해 기울기는 산출했는데, 구현하기는 쉽지만 계산이 오래 걸린다.
오차역전파법을 이용하면 기울기를 효율적이고 빠르게 구할 수 있다.
TwoLayerNet Class를 구현한다.(2층 신경망)
우선 클래스의 인스턴수 변수와 메서드를 정리한 표를 살펴보자.
인스턴스 변수 | 설명 |
params | 딕셔너리 변수, 신경망의 매개변수를 보관 W는 가중치, b는 편향 (숫자는 Layer) |
layers | 순서가 있는 딕셔너리, 신경망의 계층을 보관 layers['Affine1'], layers['Relu1'], layers['Affine2']와 같이 각 계층을 순서대로 유지 |
lastLayer | 신경망의 마지막 계층 이 예에서는 SoftmaxWithLoss 계층 |
메서드 | 설명 |
__init__(self,input_size,hidden_size,output_size,weight_init_std) | 초기호 수행 입력층 뉴런 수,은닉층 뉴런 수,출력층 뉴런 수,가중치 초기화시 정규분포의 스케일 |
predict(self,x) | 예측(추론) 수행 x는 이미지 데이터 |
loss(self,x,t) | 손실 함수의 값 구함 x는 이미지 데이터, t는 정답 레이블 |
accuracy(self,x,t) | 정확도 구하기 |
numerical_gradient(self,x,t) | 가중치 매개변수의 기울기를 수치 미분 방식으로 구함 |
gradient(self,x,t) | 가중치 매개변수의 기울기는 오차역전파법으로 구함 |
구현 코드이다.
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.rand(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * \
np.random.rand(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.array(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
신경망 구현 요소를 '계층'으로 구현한 덕분에 신경망을 쉽게 구축할 수 있었다.
계층으로 모듈화하여 구현한 효과는 아주 큰데, 더 깊은 신경망을 만들고 싶다면 레고 조립과 같이 필요한 만큼 계층을 추가하면 되기 때문이다.
오차역전파법으로 구한 기울기 검증하기
수치미분은 느리다. 오차역전파법을 제대로 구현해두면 수치 미분은 더이상 필요없다.
수치미분은 사실 오차역전파법을 정확히 구현했는지 검증하기 위해 필요하다.
수치 미분의 이점은 구현하기 쉬워 버그가 숨어있을 확률이 낮은 반면, 오차역전파법은 구현하기 복잡하여 종종 실수를 하곤 한다.
그래서 두 방식을 모두 구현해 교차검증하는 작업을 기울기 확인(gradient check)라고 한다.
기울기 확인 코드는 다음과 같다.
import sys,os
sys.path .append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from TwoLayerNet_page181 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(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from TwoLayerNet_page181 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.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)
정리
계산 과정에서 시각적으로 보여주는 계산 그래프
계산 그래프를 이용하여 신경망과 오차역전파법의 처리과정을 계층 단위로 구현(Relu, Softmax-with-Loss, Affine, Softmax 등)
모든 계층에서 forward, backward 메서드 구현
※dataset 및 코드 제공 - 밑바닥부터 시작하는 딥러닝 GitHub: https://github.com/kchcoo/WegraLee-deep-learning-from-scratch/tree/master
GitHub - kchcoo/WegraLee-deep-learning-from-scratch
Contribute to kchcoo/WegraLee-deep-learning-from-scratch development by creating an account on GitHub.
github.com
'공부 > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
밑바닥부터 시작하는 딥러닝1 - Chap7. 합성곱 신경망(CNN) (4) | 2024.12.06 |
---|---|
밑바닥부터 시작하는 딥러닝1 - Chap6. 학습 관련 기술들 (3) | 2024.11.29 |
밑바닥부터 시작하는 딥러닝1 - Chap4. 신경망 학습 (2) | 2024.11.20 |
밑바닥부터 시작하는 딥러닝1 - Chap3. 신경망 (1) | 2024.11.20 |
밑바닥부터 시작하는 딥러닝1 - Chap2. 퍼셉트론 (1) | 2024.11.19 |