혼자 아닌 혼자가 되고 싶은 나의 인생

파이썬을 이용한 뉴럴 네트워크 실전 - 경사하강법 (역전파) 본문

컴퓨터 사이언스/신경망 기초

파이썬을 이용한 뉴럴 네트워크 실전 - 경사하강법 (역전파)

KanzesT 2022. 1. 6. 18:37

▶경사 하강법

 

● 손실 함수는 최대한의 정확도를 위한 매개변수를 위한 값을 찾기 위해 존재한다.

앞선 포스팅에서 경사하강법에 대해 설명한적 있다.

아래의 그림은 가중치 w에 따른 오차를 나타낸 함수이다.

즉, 오차는 가중치 w_ij의 함수로 나타내었으며, 경사하강법으로 최소한의 오차를 찾아내는 과정이다.

가중치가 단 한개일 경우 이렇게 쉽게 오차를 찾아낼 수 있다.

또한 가중치가 x0 그리고 x1 과 같이 2개라면 그림과 같이 찾아 낼 수 있을 것 이다.

 

 

 

만일 가중치가 여러개 일 경우라면 ?

Error = f(w_11,w_12,w_13 .... w 33) 일 경우, 즉 9개의 가중치에 의해 결정될 경우 9개의 축으로 이루어진 상상 할 수도 없이 복잡한 형상이 될 것 이다.

 

 

기계학습에서 문제의 대부분은 학습하는 과정에서 최적의 가중치와 편향(매개변수)을 를 찾아내는 과정이다. 최적이라는 뜻은 손실 함수값이 최소가 될 때 이다. 앞서 말한 것과 같이 손실 함수는 상상도 못할 정도로 복잡한 형태가 될 것이다. 이러한 상황에서는 기울기를 이용하여 최대한 작은 기울기를 찾기 위한 방법이 경사법이다.

 

 

▶경사 하강법 코드

 

경사 하강법을 위해서는 반드시 수치미분이 필요로 한다

전방차분 또는 후방차분의 오차를 줄이기 위한 중앙차분의 코드는 다음과 같다

 

----------------------------------------------------------------------------------

#중앙차분

 

def numerical_diff(f,x):

    h = 1e-4 # 0.0001 정도가 제일 좋은 결과를 얻을 수 있는 값 이다. ( 그 이하의 값은 0으로 반올림되어버림 )

    return ((f(x+h)-f(x-h))/(2*h)) # 중앙차분법이 비교적 오차가 적다

 

def function_a(x):

    return (0.01*x**2+0.1*x) 

 

import numpy as np

import matplotlib.pylab as plt 

 

x = np.arange(0.0,50.0,0.1)

y = function_a(x)

plt.xlabel("x")

plt.ylabel("f(x)")

plt.plot(x,y)

plt.show()

 

print(numerical_diff(function_a,2)) 

print(numerical_diff(function_a,5))

----------------------------------------------------------------------------------

 

앞서 보여준 코드는 한개의 변수만을 미분하는 코드이다.

둘이상의 변수를 가진 함수에서의 기울기를 구하기 위해서는 각각의 변수에 대한 편미분이 필요로 한다.

 

----------------------------------------------------------------------------------

#편미방

 

import numpy as np

 

def numerical_gradient(f,x): # 편미분 코드

    h = 1e-4

    grad = np.zeros_like(x)  # x가 n개의 array라면 (n개의 변수로 된 편미분 방정식) n개의 array로 된 grad 라는 변수 생성

    

    for idx in range(x.size): # x의 size (x.size) 만큼의 인덱스 (즉 0부터 x-1만큼) 생성 한다

                              # 또한 for문을 사용해서 x.size 의 개수만큼 미분을 반복하여 결과를 grad[] 로 반환한다.

        tem_val = x[idx]

        

        x[idx] = tem_val + h

        fxh1 = f(x)

        

        x[idx] = tem_val - h

        fxh2 = f(x)

        

        grad[idx] = (fxh1-fxh2)/(2*h)

        x[idx] = tem_val

        

        

    return grad

 

 

def function_b(x):

    return x[0]**2+x[1]**3

 

print(numerical_gradient(function_b,np.array([1.0,2.0])))

print(numerical_gradient(function_b,np.array([2.0,4.0])))

print(numerical_gradient(function_b,np.array([3.0,6.0])))

----------------------------------------------------------------------------------

 

상단의 코드에서 미분한 x[0]^2 + x[1]^2 의 그래프는 위의 그림과 같이 그려진다.

그래프위에 수 많은 점이 있을 것 이며 그 점에서의 기울기 또한 위의 코드로 얻어질 수 있을 것 이다.

한가지 확실한 점은 우리는 에러값(error)이 제일 작아지는 중앙 (0,0)을 최정 목표로 지정해야 하며,

그 최종 목표지점을 찾아가기 위해서는 기울기라는 이정표를 잘 이용해야 한다는 점이다.

 

기울기라는 이정표로 원하는 점을 찾아가는 경사법(gradient method)을 나타내는 식은 다음과 같다

 

1. x_o점을 기준으로 한다.

2. x_o점의 기울기가 + 일 경우 x_o점을 기준으로 - 만큼 움직인다. (기울기가 줄어드는 방향으로 움직인다 - 2차함수 생각)

   x_o점의 기울기가 - 일 경우 x_o점을 기준으로 + 만큼 움직인다. (기울기가 줄어드는 방향으로 움직인다)

   => 경사하강법 (gradient descent method) : 경사가 줄어드는 방향으로 움직인다

3. 위와 같은 과정을 여러번 반복한다면 결국에는 원하는 점에 도착한다.

 

----------------------------------------------------------------------------------

# 경사하강법 코드

 

import numpy as np

 

def numerical_gradient(f,x):

    h = 1e-4

    grad = np.zeros_like(x)

    

    for idx in range(x.size):

        tem_val = x[idx]

        

        x[idx] = tem_val + h

        fxh1 = f(x)

        

        x[idx] = tem_val - h

        fxh2 = f(x)

        

        grad[idx] = (fxh1-fxh2)/(2*h)

        x[idx] = tem_val

        

        

    return grad

 

def gradient_descent(f,init_x,relaxation_f,iteration):

    x = init_x

    

    for i in range(iteration):

        grad = numerical_gradient(f,x)

        x -= relaxation_f*grad

        print(x)

    return x

 

 

def function_b(x):

    return (x[0]**2+x[1]**2)

 

init_x = np.array([-3.0,4.0])

 

gradient_descent(function_b,init_x,0.1,50)

----------------------------------------------------------------------------------

 

 

▶경사 하강법을 위한 클래스 (TwoLayerNet)

 

경사하강법을 위한 클래스의 매서드 순서는 다음과 같다.

1. 가중치 초기화 -> Weight 그리고 bias 값을 랜덤하게 설정한다

2. 예측(추론) -> 인풋값인 x과 Weight 그리고 bias값을 이용하여 계산한다

3. 에러값을 계산 -> 교차 엔트로피 에러

4. 기울기를 계산  

 

----------------------------------------------------------------------------------

# 경사하강법을 위한 클래스

 

import sys, os

sys.path.append(os.pardir) 

from common.functions import * # 커몬펑션을 와일드카드 변수(*)를 이용하여 모든 변수를 전부 불러온다

from common.gradient import numerical_gradient # numerical gradient (경사하강법) 코드를 불러온다

 

 

class TwoLayerNet:

 

    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):

        # 1. 가중치 초기화

# Params 는 w와 b를 저장하는 딕셔너리 변수

        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)

 

    def predict(self, x):

# 2. 예측(추론)단계

W1, W2 = self.params['W1'], self.params['W2']

        b1, b2 = self.params['b1'], self.params['b2']

    

        a1 = np.dot(x, W1) + b1

        z1 = sigmoid(a1)

        a2 = np.dot(z1, W2) + b2

        y = softmax(a2)

        

        return y

        

   

    def loss(self, x, t):  # x : 입력 데이터, t : 정답 레이블

        # 3. 에러률의 값 계산

y = self.predict(x)

        

        return cross_entropy_error(y, t)

    

    def accuracy(self, x, t):

        y = self.predict(x)

        y = np.argmax(y, axis=1)

        t = np.argmax(t, axis=1)

        

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

        return accuracy

        

    def numerical_gradient(self, x, t): # x : 입력 데이터, t : 정답 레이블

# 4. 기울기 계산

        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):

        W1, W2 = self.params['W1'], self.params['W2']

        b1, b2 = self.params['b1'], self.params['b2']

        grads = {}

        

        batch_num = x.shape[0]

        

        # forward

        a1 = np.dot(x, W1) + b1

        z1 = sigmoid(a1)

        a2 = np.dot(z1, W2) + b2

        y = softmax(a2)

        

        # backward

        dy = (y - t) / batch_num

        grads['W2'] = np.dot(z1.T, dy)

        grads['b2'] = np.sum(dy, axis=0)

        

        da1 = np.dot(dy, W2.T)

        dz1 = sigmoid_grad(a1) * da1

        grads['W1'] = np.dot(x.T, dz1)

        grads['b1'] = np.sum(dz1, axis=0)

 

        return grads

----------------------------------------------------------------------------------
 
커몬펑션의 내용은 다음과 같다
 
 
----------------------------------------------------------------------------------
#커먼펑션
 
import numpy as np
 
 
def identity_function(x):
    return x
 
 
def step_function(x):
    return np.array(x > 0, dtype=np.int)
 
 
def sigmoid(x):
    return 1 / (1 + np.exp(-x))    
 
 
def sigmoid_grad(x):
    return (1.0 - sigmoid(x)) * sigmoid(x)
    
 
def relu(x):
    return np.maximum(0, x)
 
 
def relu_grad(x):
    grad = np.zeros(x)
    grad[x>=0] = 1
    return grad
    
 
def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T 
 
    x = x - np.max(x) # 오버플로 대책
    return np.exp(x) / np.sum(np.exp(x))
 
 
def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)
 
 
def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
        
    # 훈련 데이터가 원-핫 벡터라면 정답 레이블의 인덱스로 반환
    if t.size == y.size:
        t = t.argmax(axis=1)
             
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
 
 
def softmax_loss(X, t):
    y = softmax(X)
    return cross_entropy_error(y, t)
 
----------------------------------------------------------------------------------

 

 

Comments