Neural Network Gradient Descent With Only Numpy(패키지 없이 코딩하기)

2022. 4. 23. 16:13인공지능

반응형

안녕하세요.

아래와 같은 딥러닝(사실 딥러닝은 Hidden layer가 2개이상부터 이지만, 여기선 기초를 잡기위해 1개로 생각해보겠습니다) 네트워크를 딥러닝 패키지 없이 Numpy와 Matplotlib만 사용하여 Neural Network Gradient Descent를 설계해보도록 하겠습니다.

 

# Import Library
import numpy as np
from numpy import loadtxt
import matplotlib.pyplot as plt

 

## DATA SET 
dataset = loadtxt('./training.txt')
training_data  = dataset[:,0:2]
test_data  = dataset[:,-1]
label = np.zeros((1000,2))
for i in range(1000):
    if test_data[i]== 1.:
        label[i,1]=1
    if test_data[i]== 0.:
        label[i,0]=1

데이터 셋은 아래와 같습니다. 

총 1000개가 있고, input data는 2개의 열, label은 마지막 열(값이 0또는 1)입니다. 

## Activate Fuction Sigmoid 
def sigmoid(x):
    return 1 / (1+np.exp(-x))

활성함수는 sigmoid함수로 하겠습니다.

 

## Basic Deep Learning Network
class BasicDeepLearning:
    
    def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate):
        
        self.input_nodes = input_nodes
        self.hidden_nodes = hidden_nodes
        self.output_nodes = output_nodes
        
        # hidden layer weight init 
        self.W2 = np.random.random((self.input_nodes, self.hidden_nodes))
        
        # output layer weight init 
        self.W3 = np.random.random((self.hidden_nodes, self.output_nodes))
                        
        # output layer 
        self.Z3 = np.zeros([1,output_nodes])
        self.A3 = np.zeros([1,output_nodes])
        
        # hidden layer
        self.Z2 = np.zeros([1,hidden_nodes])
        self.A2 = np.zeros([1,hidden_nodes])
        
        # input layer 
        self.Z1 = np.zeros([1,input_nodes])    
        self.A1 = np.zeros([1,input_nodes])     
        
        # learning rate
        self.learning_rate = learning_rate
        
    ## Feed Forward    
    def feed_forward(self):  

        # input layer
        self.Z1 = self.input_data
        self.A1 = self.input_data

        # hidden layer     
        self.Z2 = self.A1.dot( self.W2)
        self.A2 = sigmoid(self.Z2)

        # output layser
        self.Z3 = self.A2.dot(self.W3)
        self.A3 = sigmoid(self.Z3)
        
        return self.A3
    
    ## loss 
    def loss_val(self):
        
        loss = ((self.A3 - self.target_data)**2)
        loss = np.mean(loss)
        
        return loss
    
    ## Train(Backwoard, Weight Update)
    def train(self, input_data, target_data):   
        
        self.target_data = target_data    
        self.input_data = input_data
        
        #feedforward
        self.feed_forward()
        
        # stage1 (output → hidden weight update) 
        loss_3 = (self.A3 - self.target_data) * self.A3 * (1- self.A3)
        self.W3 = self.W3 - self.learning_rate * np.dot(self.A2.T, loss_3) 
        
        # stage2 (hidden → input weight update)
        loss_2 = np.dot(loss_3, self.W3.T) * self.A2 * (1-self.A2)   
        self.W2 = self.W2 - self.learning_rate * np.dot(self.A1.T, loss_2)

변수설명을 먼저 할게요!

Z는 W(Weight)와 계산(선형함)된 결과를 뜻하며, A는 Z에서 활성함수를 통과한 결과를 뜻합니다.

따라서 전체적인 네트워크 Feedforward Flow를 생각해보면 아래와 같습니다. 

Z1/A1 → W2(Weight) → Z2   활성함수 → A2 W3(Weight) Z3  활성함수  A3

 

또한,  Backpropagation(Weight 없데이트) Flow를 생각해보면 아래와 같습니다. 

output → hidden weight update(STEP1)  hidden → input weight update(STEP2)

여기서, Weight를 업데이트 할때, Loss가 최소화가 될때까지 업데이트를 해줍니다. 

즉, 우리는 Loss가 최소화가 될때의 Weight값을 구하는 것입니다.

Loss Function은 다양하게 있지만, 가장 기본적으로는 MSE가 있는데 MSE는 Feedforward의 출력물과 결과물(label, 진짜값)의 차이의 거리를 뜻합니다. 즉, 우리가 계산한 출력값이 진짜값과 차이를 가장 최소화 하는 파라미터(Weight)를 찾습니다.)

수학적으로, Loss function의 최소화는 기울기가 0이 되는 것입니다. Loss function의 기울기를 Gradient라 하고 Gradient를 learning rate만큼 줄여나가며 0에 가까운 값(최소화)을 찾아나갑니다.

(여기서 중요한 체인룰(Chain Rule)기법이 나오는데, 이부분은 다음에 자세히 설명하기로 합니다.) 

 

## Parameter Settinga
input_nodes = 2
hidden_nodes = 8
output_nodes = 1
learning_rate = 0.01
epochs=1000

네트워크 설계가 다 끝났으면, 파라미터 셋팅값을 넣어줍니다. 

 

## Training for optimization 
loss = []
bdl = BasicDeepLearning(input_nodes, hidden_nodes, output_nodes, learning_rate)

for i in range(epochs):
    bdl.train( training_data, label )
    loss.append(bdl.loss_val())
    print(bdl.loss_val())

점점 줄어드는 loss를 볼 수 있습니다. 

점점 줄어는 loss를 시각화해서 보도록 합니다. 

뭔가 smooth하게 안정적으로 가진 않지만, loss가 줄어드는 것(0으로 수렴하는것)을 볼 수 있습니다.