Data/ML

Deep Learning(딥러닝) - 4.오차역전파(Error Backpropagation)

뚱요 2021. 10. 17. 00:00
반응형

이전에 배웠던것은 신경망 학습으로 순전파와 그래디언트 디센트를 이용하여 손실함수 최소화를 통해 최적의 매개변수(가중치)계산하였으나 수치미분은 단순하지만 계산이 오래걸림.
이번에는 수치미분보다 기울기를 효율적으로 계산하는 오차역전파를 이용하여 순전파에서의 결과값의 에러를 계산하여 최적의 매개변수로 계속 업데이트 하는 방법

가중치 매개변수 기울기 구하는 방법:

  • 수치 미분(기울기 =0)
    • 간단
    • 오래걸림
  • 오차역전파

표현방법 : 수식, 계산 그래프

1. 계산 그래프(computational graph)

계산 과정을 그래프로 나타내며출력값 변수를 최적화 하고싶을때 유용하며 단순화 하여 이해가 쉽다.

  • 국소적 계산: 직접 관계된 범위 내에서만 계산을 통해 각 노드의 계산에 집중하여 문제를 단순화
  • 역전파를 통해 '미분'을 효율적으로 계산할 수 있다..
  • 노드(node): 연산 정의
    • 덧셈,곱셈,ReLu,Sigmoid..
  • 엣지(edge): 데이터가 흘러가는 방향
    • 순전파(forward propagation): 입력층에서 출력층 방향으로 출력값 계산 전달
    • 오차역전파법 (Backpropagation):출력층에서 입력층 방향으로 경사,도함수 계산
슈퍼에서 사 과를 2개, 귤을 3개 구매하였는데 사과는 1개에 100원, 귤은 1개 150원입니다. 소비세가 10%일 때 지불 금액을 구하라.

1) 순전파(forward propagation): 계산을 각 품목에서 최종 계산으로 왼쪽에서 오른쪽으로 진행

  • 사과의 값 2*100 =200    ;  귤의 값  3*150 =450
  • 총 과일의 값: 200 + 450 = 650
  • 총 지불: 650  * 1.1  =  715

순전파 예시

2) 역전파 (Backpropagation): 순전파에서 계산한 결과의 에러값을 계산하여 출력에서 입력으로 전달하여가중치를 계산하여 조정( 중요한값의 가중치 늘림)

'사과 가격이 오르면 최종 금액에 어떠한 영향을 주는가'에 대해서 사과 가격에 대한 지불 금액의 미분을 구해 계산할 수 있다. 사과의 값을 ​, 지불 금액을 ​라 했을 때, ​를 구하는 것이다. 이러한 미분 값은 사과 값(​)가 '아주 조금'올랐을 때 지불 금액(​)이 얼마나 증가하는지를 나타낸다

연쇄법칙(Chain Rule) :순전파의 역반향(굵은 붉은색 노드) 국소적 미분을 전달 1 -> 1.1 -> 2.2 로 전달

사과 가격에 대한 금액의 미분 값 2.2 (사과 값이 아주 조금 오르면 최종 금액은 그 값의 2.2배 만큼 오름)

이 한번의 턴을 1 epoch(주기)이며 epoch를 반복하여 오차를 최소화 할때까지 가중치를 계속 업데이트

역전파 예시

Andrew Ng 컴퓨터 그래프 순전파 

Andrew Ng 컴퓨터 그래프 이용 도함수 구하기 

2. 연쇄 법칙 Chain Rule

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

y=f(x) 계산의 역전파 계산 그래프

E에 노드의 국소적 미분값을 곱한 후 다음노드 전달

예. f(x)= y=x**2   에서 E*2x 가 다음 노드로 전달됨

합성함수  : y= (x+y)**2

 

3. 역전파

 덧셈 노드

gradient distributor: 입력값을 그대로 흘려보낸다. 

위의 ​계산은 큰 계산 그래프의 중간 어딘가에 존재한다고 가정했기 때문에, 이 계산 그래프의 앞부분(상류)에서부터 ​가 전해졌다고 가정한다.

classAddLayer:
	def __init__(self): #덧셈 계층에선느 초기화 불필요 -> skip(pass)
		pass

	def forward(self,x,y):
		out=x+y
		returnout

	def backward(self,dout):
		dx=dout*1
		dy=dout*1
		returndx,dy

 곱셈 노드

gradient switcher : 입력값의 위치를 서로 바꾼 다음 곱해서 흘려보낸다.]

classMulLayer:
	def __init__(self):  #x,y 변수(순전파 시의 입력값 유지 위해 사용) 초기화
		self.x=None
		self.y=None
#순전파: x*y
	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

   		returndx,dy
        
        


4. 단순한 계층 구현(곱셈, 덧셈)

모든 계층은 forward() 순전파, 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('%d'%price) #>>>220
​
# 역전파
dprice=1
dapple_price,dtax=mul_tax_layer.backward(dprice)
dapple,dapple_num=mul_apple_layer.backward(dapple_price)
​
print("%.1f, %d, %d"%(dapple,dapple_num,dtax))220
# >>> 2.2, 110, 200

덧셈 계층 + 곱셈 계층 

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('%d'%price) 
#>>>715

print("%d, %.1f, %.1f, %d, %d"%(dapple_num,dapple,dorange,dorange_num,dtax))
# >>> 110, 2.2, 3.3, 165, 650

 

5 활성화 함수 계층

1 )ReLU 계층

 

import numpy as np# common/layers.py
​
classRelu:
	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

 

 

x=np.array([[1.,-.5],
[-2.,3.]])
print('x:\n',x)
​
mask= (x<=0)
print('mask:\n',mask)x:
[[ 1. -0.5]
[-2. 3. ]]
mask:
[[False True]
[ True False]]

 

2) Sigmoid 계층

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

 

6 Affine/Softmax 계층 구현하기

6.1 Affine 계층

6.2 배치용 Affine 계층

​
class Affine:
	def __init__(self,W,b):
	  self.W=W
      self.b=b
      self.x=None
      self.dW=None
      self.db=None

	defforward(self,x):
      self.x=x
      out=np.dot(x,self.W)+self.b

      return out

	defbackward(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

6.3 Softmax-with-Loss 계층

딥러닝에서는 학습 추론 두 가지가 있다. 일반적으로 추론일 때는 Softmax 계층(layer)을 사용하지 않는다. Softmax 계층 앞의 Affine 계층의 출력을 점수(score)라고 하는데, 딥러닝의 추론에서는 답을 하나만 예측하는 경우에는 가장 높은 점수만 알면 되므로 Softmax 계층이 필요없다. 반면, 딥러닝을 학습할 때는 Softmax 계층이 필요하다.

소프트맥스(softmax) 계층을 구현할때, 손실함수인 교차 엔트로피 오차(cross entropy error)도 포함하여 아래의 그림과 같이 Softmax-with-Loss 계층을 구현한다.

# common/layers.py
import os,sys
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from common.functions import softmax,cross_entropy_error
​
​
class SoftmaxWithLoss:
   def __init__(self):
    self.loss=None # 손실
    self.y=None # softmax의 출력
    self.t=None # 정답 레이블(one-hot)

   def forward(self,x,t):
    self.t=t
    self.y=softmax(x)
    self.loss=cross_entropy_error(self.y,self.t)
    returnself.loss

   def backward(self,dout=1):
    batch_size=self.shape[0]
    dx= (self.y-self.t)/batch_size

    return dx

7. 오차역전파법 구현

  1. 미니배치(mini-batch) : Train 데이터 중 랜덤하게 샘플을 추출하는데 이것을 미니배치라고 하며, 미니배치의 손실함수 값을 줄이는 것이 학습의 목표이다.
  2. 기울기 계산 : 손실함수의 값을 작게하는 방향을 가리키는 가중치(​) 매개변수의 기울기를 구한다.
  3. 매개변수 갱신 : 가중치 매개변수를 기울기 방향으로 학습률(learning rate)만큼 갱신한다.
  4. 반복 1~3 단계를 반복한다.
# two_layer_net.py
import sys,os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from collections import OrderedDict
from common.layersimport*
from  common.gradientimport numerical_gradient
​
​
classTwoLayerNet:
'''2층 신경망 구현'''
   def __init__(self,input_size,
    hidden_size,output_size,weight_init_std=0.01):
    '''
    초기화 수행
    Params:
    - input_size: 입력층 뉴런 수
    - hidden_size: 은닉층 뉴런 수
    - output_size: 출력층 뉴런 수
    - weight_init_std: 가중치 초기화 시 정규분포의 스케일
    '''
    # 가중치 초기화
    self.params= {
    'W1':weight_init_std*np.random.randn(input_size,hidden_size),
    'b1':np.zeros(hidden_size),
    'W2':weight_init_std*np.random.randn(hidden_size,output_size),
    'b2':np.zeros(output_size)
    }
    \# 계층 생성  
       self.layers\=OrderedDict({  
           'Affine1':Affine(self.params\['W1'\],self.params\['b1'\]),  
           'Relu1':Relu(),  
           'Affine2':Affine(self.params\['W2'\],self.params\['b2'\])  
      })  

       self.last\_layer\=SoftmaxWithLoss()  

 

defpredict(self,x):
'''예측(추론)
Pararms:
- x: 이미지 데이터'''
forlayerinself.layers.values():
x=layer.forward(x)

return x

defloss(self,x,t):
'''
손실함수의 값을 계산
Params:
- x: 이미지데이터, t: 정답 레이블
'''
y=self.predict(x)
returnself.last_layer.forward(y,t)

def accuracy(self,x,t):
'''
정확도 계산
Params:
- x: 이미지 데이터
- t: 정답 레이블
'''
y=self.predict(x)
y=np.argmax(y,axis=1)
ift.ndim!=1:
t=np.argmax(t,axis=1)

   accuracy\=np.sum(y\==t)/float(x.shape\[0\])  
   returnaccuracy  

\def numerical_gradient(self,x,t):
'''
미분을 통한 가중치 매개변수의 기울기 계산
Params:
- x: 이미지 데이터
- t: 정답 레이블
'''
loss_W=lambdaW:self.loss(x,t)
grads\= {  
       'W1':numerical\_gradient(loss\_W,self.params\['W1'\]),  
       'b1':numerical\_gradient(loss\_W,self.params\['b1'\]),  
       'W2':numerical\_gradient(loss\_W,self.params\['W2'\]),  
       'b2':numerical\_gradient(loss\_W,self.params\['b2'\])  
  }  
   returngrads  
   
def gradient(self,x,t):
# forward
self.loss(x,t)
\# backward  
   dout\=1  
   dout\=self.last\_layer.backward(dout)  

   layers\=list(self.layers.values())  
   layers.reverse()  
   forlayerinlayers:  
       dout\=layer.backward(dout)  

   \# 결과 저장  
   grads\= {  
       'W1':self.layers\['Affine1'\].dW,'b1':self.layers\['Affine1'\].db,  
       'W2':self.layers\['Affine2'\].dW,'b2':self.layers\['Affine2'\].db  
  }  
   return grads

 

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

 기울기 확인(gradient check):수치 미분을 통해 구한 기울기와 오차역전파법의 결과를 비교하여 오차역전파를 제대로 구현했는지 검증하는 작업

%%time
# gradient_check.py
importsys,os
sys.path.append(os.pardir)
importnumpyasnp
fromdataset.mnistimportload_mnist
​
# mnist load
(x_train,t_train), (x_test,t_test) =load_mnist(normalize=True)
​
network=TwoLayerNet(input_size=28*28,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)
​
# 각 가중치의 절대 오차의 평균을 구한다.
forkeyingrad_numerical.keys():
diff=np.average(np.abs(grad_backprop[key]-grad_numerical[key]))
print(key,":",str(diff))W1 : 4.479721446541244e-10
b1 : 2.5485543061868916e-09
W2 : 4.349602602871501e-09
b2 : 1.393278526204411e-07
Wall time: 6.78 s

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

%%time
# train_neuralnet.py
importsys,os
sys.path.append(os.pardir)
importnumpyasnp
fromdataset.mnistimportload_mnist
​
# mnist load
(x_train,t_train), (x_test,t_test) =load_mnist(normalize=True)
​
network=TwoLayerNet(input_size=28*28,hidden_size=50,output_size=10)
​
# Train Parameters
iters_num=10000
train_size=x_train.shape[0]
batch_size=100
learning_rate=0.1
iter_per_epoch=max(train_size/batch_size,1)
​
train_loss_list,train_acc_list,test_acc_list= [], [], []
​
forstepinrange(1,iters_num+1):
# get mini-batch
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)# 오차역전파법 방식(압도적으로 빠르다)

# Update
forkeyin('W1','b1','W2','b2'):
network.params[key] -=learning_rate*grad[key]

# loss
loss=network.loss(x_batch,t_batch)
train_loss_list.append(loss)

if step%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('Step: {:4d}\tTrain acc: {:.5f}\tTest acc: {:.5f}'.format(step,
train_acc,
test_acc))

print('Optimization finished!')

Step: 600Train acc: 0.90450Test acc: 0.90560

Step: 1200Train acc: 0.92288Test acc: 0.92570
Step: 1800Train acc: 0.93220Test acc: 0.93200
Step: 2400Train acc: 0.94605Test acc: 0.94460
Step: 3000Train acc: 0.95430Test acc: 0.95210
Step: 3600Train acc: 0.95993Test acc: 0.95870
Step: 4200Train acc: 0.96360Test acc: 0.95870
Step: 4800Train acc: 0.96682Test acc: 0.96320
Step: 5400Train acc: 0.96930Test acc: 0.96380
Step: 6000Train acc: 0.97108Test acc: 0.96450
Step: 6600Train acc: 0.97318Test acc: 0.96690
Step: 7200Train acc: 0.97557Test acc: 0.96890
Step: 7800Train acc: 0.97698Test acc: 0.96760
Step: 8400Train acc: 0.97808Test acc: 0.96990
Step: 9000Train acc: 0.97835Test acc: 0.97030
Step: 9600Train acc: 0.98035Test acc: 0.97020
Optimization finished!
Wall time: 26.6 s

Refrence

 

반응형