혜온의 이것저것

[Chapter 1 신경망 복습] 3 신경망의 학습 본문

Deep Learning/밑바닥부터 시작하는 딥러닝2

[Chapter 1 신경망 복습] 3 신경망의 학습

혜온 :) 2022. 1. 25. 11:50

학습되지 않은 신경망은 좋은 추론을 해낼 수 없다. 그래서 학습을 먼저 수행하고, 그 학습된 매개변수를 이용해 추론을 수행하는 흐름이 일반적이다. 신경망의 학습은 최적의 매개변수 값을 찾는 작업이다.

 

1.3.1 손실함수

신경망 학습에는 학습이 얼마나 잘 되고 있는지를 알기 위한 척도가 필요하다.

일반적으로 학습단계의 특정 시점에서 긴경망의 성능을 나타내느 척도로 손실(loss)을 사용한다. 손실은 학습 데이터와 신경망이 예측한 결과를 비교하여 예측이 얼마나 나쁜가를 산출한 단일 값이다.

신경망의 손실은 손실함수(loss function)를 사용한다. 다중 클래스 분류 신경망에서는 손실함수로 교차 엔트로피 오차(Cross Entropy Error)를 이용한다. 교차 엔트로피 오차는 신경망이 출력하는 각 클래스의 확률과 정답레이블을 이용하여 구할 수 있다.

위에서 말한 확률은 Softmax함수의 출력으로 구할 수 있다.

 

Softmax함수를 식으로 쓰면 다음과 같다.

$$ y_k=\frac{exp(s_k)}{\sum_{i=1}^n exp(s_i)} $$

출력이 총 n개일 때, k번째의 출력을 구하는 계산식이다. Softmax함수의 출력의 각 원소는 0.0이상 1.0이하의 실수이다. 그리고 그 원소들을 모두 더하면 1.0이 된다. 이것이 Softmax의 출력을 확률로 해석할 수 있는 이유이다.

 

Softmax함수의 출력인 이 확률이 다음 차례인 교차엔트로피오차에 입력된다. 이때 교차 엔트로피 오차의 수식은 다음과 같다.

$$ L=-\sum_{k} t_klogy_k $$

여기서 t_k는 k번째 클래스에 해당하는 정답 레이블로 원핫 벡터로 표기한다.

데이터의 수가 N가라고 할때 식은 아래와 같이 확장된다.

$$ L=-\frac{1}{N}\sum_{n}\sum_{k} t_{nk} logy_{nk} $$

이 식에서는 1개당의 평균 손실함수를 구한다. 이렇게 평균을 구함으로써 크기에 관계없이 항상 일관된 척도를 얻을 수 있다.

 

이 책에서는 Softmax함수와 Cross Entropy Error를 계산하는 계층을 Softmax with Loss계층 하나로 구현한다.

이렇게 두 계층을 통합하면 역전파 계산이 쉬워진다.

 

1.3.2 미분과 기울기

신경망의 학습의 목표는 손실을 최소화하는 매개변수를 찾는 것이다. 이때 중요한 것이 미분과 기울기이다.

L은 스칼라, x는 벡터인 함수L=f(x)가 있을때 x_i에 대한 미분은 다음과 같다.

이처럼 벡터의 각 원소에 대한 미분을 정리한 것이 기울기(gradient)이다.

 

벡터와 마찬가지로 행렬에서도 기울기를 생각할 수 있다. W가 (m,n)행렬이라면 L=g(W) 함수의 기울기는 다음과 같다.

여기서 중요한 점은 미분하기 전(행렬)과 미분한 후(기울기)의 형상이 같다는 점이다. 그리고 이 성질을 이용하면 매개변수 갱신과 연쇄 법칙을 쉽게 구현할 수 있다.

 

1.3.3 연쇄 법칙

학습 시 신경망은 학습 데이터를 주면 손실을 출력한다. 여기서 우리가 얻고 싶은 것은 각 매개변수에 대한 소실의 기울기이다. 그 기울기를 얻을 수 있다면, 그것을 사용해 매개변수를 갱신할 수 있다.

 

신경망은 기울기를 구하는 과정에서 오차역전파법(back-propagation)이 등장한다. 오차역전파를 이해하는 열쇠는 연쇄법칙(chain rule)이다. 연쇄법칙이란 합성 함수에 대한 미분의 법칙이다.

 

y=f(x)와 z=g(y)라는 두 함수가 있을 때 z=g(f(x))가 되어 최종 출력 z는 두 함수를 조합해 계산할 수 있다.

이때 이 합성함수에 대한 미분은 다음과 같다.

 

x에 대한 z의 미분은 y=f(x)의 미분과 x=g(y)의 미분을 곱하면 구해진다.

아무리 복잡한 함수를 연결하더라도, 그 미분은 개별 함수의 이분들을 이용해 구할 수 있다. 즉, 각 함수의 국소적인 미분을 계산할 수 있다면 그 값들을 곱해서 전체의 미분을 구할 수 있다.

 

1.3.4 계산 그래프

계산그래프는 계산 과정을 시각적으로 보여준다.

더하기를 +노드로 나타내고 변수 x, y를 해당 화살표 위에 썼다. 이처럼 계산 그래프에서는 연산을 노드로 나타내고 그 처리 결과가 순서대로 흐른다. 이것이 계산 그래프의 순전파이다.

계산그래프를 이용하면 계산을 시각적으로 파악할 수 있고, 기울기도 직관적으로 구할 수 있다.

여기서 중요한 점은 기울기가 순전파와 반대 방향으로 전파된다는 사실인데, 이 반대 방향의 전파가 역전파이다.

 

우리 목표는 L의 미분(기울기)을 각 변수에 대해 구하는 것이다. 이 계산 그래프의 역전파를 그려보면

역전파는 두꺼운 붉은 화살표로 그리고, 화살표 아래에 전파되는 값이 있다. 이때 전파되는 값은 최종 출력 L의 각 변수에 대한 미분이다.

여기서 다시 연쇄법칙이 등장하여 출력 족에서 흘러온 미분과 각 연산 노드의 국소적인 미분을 곱해 계산할 수 있다.

 

$$ \frac{\partial{L}}{\partial{x}}=\frac{\partial{L}}{\partial{z}}\frac{\partial{z}}{\partial{x}} , \frac{\partial{L}}{\partial{y}}=\frac{\partial{L}}{\partial{z}}\frac{\partial{z}}{\partial{y}} $$

그런데 우리는 z=x+y의 덧셈 노드에서 이뤄지는 여산을 다루고 있으므로 

$$ \frac{\partial{z}}{\partial{x}}=1, \frac{\partial{z}}{\partial{y}}=1 $$

이라는 결과를 구할 수 있다.

이 결과를 적용하면 덧셈 노드는 출력으로부터 받은 값에 1을 곱하여 입력쪽으로 기울기를 전파한다. 즉, 상류로부터 기울기를 그대로 흘리기만 한다.

 

곱셈 노드

곱셈노드의 역전파는 상류로부터 받은 기울기에 순전파 시의 입력을 서로 바꾼 값을 곱한다.

 

분기노드

분기노드는따로 그리지 않고 단순히 선이 두개로 나뉘도록 그리는데, 같은 값이 복제되어 분기된다. 따라서 복제노드라고도 부른다.

그리고 그 역전파는 상류에서 온 기울기들의 합이다.

 

Repeat 노드

 

2개로 분기하는 노드를 일반화한 것이다. 이 Repeat노드는 N개의 분기 노드로 볼 수 있으므로, 그 역전파는 N개의 기울기를 모두 더해 구할 수 있다.

import numpy as np
D,N=8,7

x=np.random.randn(1,D)		#입력
y=np.repeat(x,N,axis=0)		#순전파

dy=np.random.randn(N,D)			#무작위 기울기
dx=np.sum(dy,axis=0,keepdims=True)	#역전파

 

Sum 노드

Sum노드의 역전파는 상류로부터의 기울기를 모든 화살표에 분배한다. 덧셈노드의 역전파를 자연스럽게 확장한 것이다.

import numpy as np
D,N=8,7

x=np.random.randn(N,D)			#입력
y=np.sum(x,axis=0,keepdims=True)	#순전파

dy=np.random.randn(1,D)		#무작위 기울기
dx=np.repeat(dy,N,axis=0)	#역전파

Sum노드와 Repeat노드는 서로 반대 관계다.

Sum노드의 순전파가 Repeat노드의 역전파가 되고, Sum노드의 역전파가 Repeat노드의 순전파가 된다.

 

MatMul 노드

이때 x의 i번째 원소에 대한 미분은 다음과 같다.

$$\frac{\partial{L}}{\partial{x_i}}=\sum_j \frac{\partial{L}}{\partial{y_j}}\frac{\partial{y_j}}{\partial{x_i}}$$

x_i를 조금 변화시켰을 때 L이 얼마나 변할 것인가라는 변화의 정도를 나타낸다.

x_i를 변화시키면 벡터y의 모든 원소가 변하고, 그로 인해 최종적으로 L이 변하게 된다.

 

$$\frac{\partial{y_j}}{\partial{x_i}}=W_{ij}$$가 성립하므로 이를 대입하면 아래 식을 유도할 수 있다.

이를 역으로 취해 역전파의 수식을 유도할 수 있다. 

곱셈의 역전파와 마찬가지로 행렬의 영전파에서도 순전파 시의 입력을 서로 바꾼 행렬을 사용한다. 그 다음 각 행렬의 형상에 주목하여 정합성이 유지되도록 행렬 곱을 조합하면 행렬 곱의 역전파를 유도할 수 있다.

 

class MatMul:
	def __init__(self,W):
    	self.params=[W]
        self.gards=[np.zeros_like(W)]
        self.x=None
        
    def forward(self,x):
    	W,=self.params
        out=np.matmul(x,W)
        self.x=x
        return out
    
    def backward(self,dout):
    	W,=self.params
        dx=np.matmul(dout,W.T)
        dW=np.matmul(self.x.T,dout)
        self.grads[0][...]=dW
        return dx

 

1.3.5 기울기 도출과 역전파 구현

Sigmoid 계층

시그모이드 함수를 미분하면 다음과 같다.

$$\frac{\parial{y}}{\partial{x}}=y(1-y)$$

 

 

 

 

출력 쪽 계층으로부터 전해진 기울기에 시그모이드 함수의 미준을 곱하고, 그 값을 입력 쪽 계층으로 전파한다.

class Sigmoid:
	def __init__(self):
    	self.params,self.grads=[],[]
        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에 저장하고, 역전파를 계산할 때 이 out변수를 사용하는 모습을 볼 수 있다.

 

Affine 계층

Affine 계층의 순전파는 y=np.matmul(x,W)+b로 구현할 수 있다. 이를 계산그래프로 그리면 다음과 같다.

class Affine:
	def __init__(self,W,b):
    	self.params=[W,b]
        self.grads=[np.zeros_like(W),np.zeros_like(b)]
        self.x=None
    
    def forward(self,x):
    	W,b=self.params
        out=np.matmul(x,W)+b
        self.x=x
        return out
        
    def backward(self,dout):
    	W,b=self.params
        dx=np.matmul(dout,W.T)
        dW=np.matmul(self.x.T,dout)
        db=np.sum(dout,axis=0)
        
        self.grads[0][...]=dW
        self.grads[1][...]=db
        return dx

 

Softmax with Loss 계층

소프트맥스 함수와 교차 엔트로피 오차는 Softmax with Loss라는 하나의 계층으로 구현할 수 있다. 이의 계산 그래프는

3클래스 분류를 가정하여 이전 계층으로부터 3개의 입력을 받도록 했다.

Softmax 계층은 입력 (a_1,a_2,a_3)를 정규화하여 (y_1,y_2,y_3)를 출력한다.

Cross Entropy Error 계층은 Softmax의 출력 (y_1,y_2,y_3)와 정답 레이블(t_1,t_2,t_3)를 받고 이 데이터로부터 손실 L을 구해 출력한다.

 

1.3.6 가중치 갱신

오차역전파법으로 기울기를 구했으면, 그 기울기를 사용해 신경망의 매개변수를 갱신한다.

이때 신경망의 학습은 다음 순서로 수행한다.

1) 미니배치 : 훈련 데이터 중에서 무작위로 다수의 데이터를 골라낸다.

2) 기울기 계산 : 오차역전파법으로 각 가중치 매개변수에 대한 손실 함수의 기울기를 구한다.

3) 매개변수 갱신 : 기울기를 사용하여 가중치 매개변수를 갱신한다.

4) 반복 : 1)~3)을 필요한 만큼 반복한다.

 

1)~3)을 경사하강법(Gradient Descent)라고 한다.

3)에서 수행하는 가중치 갱신 기법의 종류는 아주 다양한데, 여기서는 그중 가장 단순한 확률적경사하강법(SGD)를 구현한다.

SGD는 현재의 가중치를 기울기 방향으로 일정한 거리만큼 갱신한다.

 

W가 갱신하는 가중치 매개변수이고, eta는 학습률을 나타내며, 실제로는 0.01이나 0.001같은 값을 미리 정해 사용한다.

class SGD:
	def __init__(self,lr=0.01):
    	self.lr=lr
        
    def update(self,params,grads):
    	for i in range(len(params)):
        	params[i]-=self.lr*grads[i]

 

이 SGD클래스를 사용하면 신경망의 매개변수 갱신을 다음처럼 할 수 있다.(실제로 동작하는 않는 의사코드임)

model=TwoLayerNet(...)
optimizer=SGD()

for i in range(10000):
	...
    x_batch,t_batch=get_mini_batch(...)	#미니배치 획득
    loss=model.forward(x_batch, t_batch)
    model.backward()
    optimizer.update(model.params, model.prads)
    ...
Comments