본문 바로가기

Machine Learning(이론)/NN(Neural network)(Basic)

[ML-t][MLP] (02) 오차역전파(Error Backpropagation) 구현 - 회귀모델

개요

  앞서 설명했던 오차역전파 이론에 따라 python으로 회귀모델을 구현 하겠다. 아래 코드는 numpy 라이브러리 외의 라이브러리는 사용하지 않았고 추후 keras와 pytorch를 이용하여 구현된 MLP 회귀모델에 관한 포스팅을 작성하겠다.

  먼저, 아래는 구현할 모델에 대한 설계이다.

  • 데이터 : Iris Dataset

    Iris Dataset는 keras 에서도 로드 할 수 있다. 하지만 tensorflow 를 로드하는데 시간이 오래 걸리고 본 포스팅에서는 keras를 사용하지 않을 것이므로 sklearn 에서 제공하는 Iris Dataset을 이용 했다. Iris Dataset은 입력변수로 꽃받침의 길이와 길이, 꽃잎의 길이와 너비('sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)') 데이터를 제공하고 출력변수로 해당 입력변수에 해당하는 붓꽃의 종을 제공한다. 출력변수로 나올수 있는 붓꽃의 종은 총 세개('setosa', 'versicolor', 'virginica') 이고 순서대로 0,1,2 데이터로 출력해 준다.

   우리는 해당 데이터를 회귀로 학습하기 위해 입력변수와 출력변수의 구성을 바꾸겠다. 기존에 입력변수 중 종별로 뚜렷한 특징을 보이는 변수를 출력변수를 바꾸고 기존에 출력변수였던 종에 대한 데이터를 입력변수에 추가 하겠다. 따라서 아래와 같은 모델을 구현하게 된다.

붓꽃의 'sepal length (cm)', 'sepal width (cm)', 'petal width (cm)' 와 종('setosa', 'versicolor', 'virginica') 이 주어졌을 때 'petal length (cm)' 을 예측하는 회귀모델

 

  • 층별 갯수

      입력변수와 출력변수의 갯수와 입력층,출력층의 갯수를 맞추고 은닉층은 적당한 갯수를 임의로 정한다. 따라서 입력층 4개, 은닉층 2개, 출력층 1개의 모델을 구현하겠다.

  • 함수

      활성화 함수와 손실함수, 최적화로는 이론에서 예시로 들었던 함수와 같게 한다. 활성화 함수로는 시그모이드 함수, 손실함수로는 MSE, 최적화로는 경사하강법을 이용하겠다.

  • 기타

      이외 epoch(반복)나 learning rate(학습률)은 구현을 진행 하며 모델에 최적화 된 값을 임의로 설정하겠다.

 

전처리 구현

  데이터를 로드하고 위에 설명한것과 같이 입력변수와 출력변수를 배열로 생성한다. 더하여 sklearn 라이브러리에서 제공하는 train_test_split을 통해 Iris Dataset을 train data와 test data(validatiaon data) 로 나눈다.

import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# 전처리
data_x = np.concatenate([np.delete(iris.data, 2, axis=1), iris.target.reshape(iris.target.shape[0], 1)], axis = 1)
data_y = iris.data[:, 2]

def nomalization(x):
    max_var = np.max(data_y)
    min_var = np.min(data_y)
    return (x-min_var)/(max_var-min_var)

data_y = nomalization(data_y)

train_x, test_x, train_y, test_y = train_test_split(data_x, data_y, test_size=0.3, random_state=42)

 

  위 코드에서 데이터를 나누기 전 y 데이터에 대해 정규화(nomalization)를 진행 한것을 볼 수 있다. 해당 처리를 해주는 이유는 Sigmoid 함수를 출력층에 배치 하게 되면 함수 특성상 0 ~ 1 사이의 결과만 리턴하게 되기 때문이다. 학습 모델이 출력할 수 있는 범위 내에 y값이 존재 하도록 하였다.

 

학습시 필요 함수 구현
def w_sum(x, w, b):
    return np.sum(x * w, axis=1) + b
    
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
    
def Derivative_sigmoid(x):
    return sigmoid(x)*(1-sigmoid(x))
    
def MSE(y, t): #y: 신경망 출력값 t: 정답
    return (1/2)*np.sum((t-y)**2)

def Derivative_MSE(y,t):
    return -(t-y)

 

층별 가중치/편향 변수 생성
iLnum = 4 # 입력층의 갯수
hLnum = 2 # 은닉층의 갯수
oLnum = 1 # 출력층의 갯수

#입력층
# input - hidden layer
# w : 다음층 갯수 by 현재 층 갯수 행렬 (h by w / y by x)
# b : 다음층 갯수
iW = np.random.randn(hLnum,iLnum)
iB = np.random.randn(hLnum)

#은닉층
# hidden - output layer
# w : 다음층 갯수 by 현재 층 갯수 행렬 (h by w / y by x)
# b : 다음층 갯수
hW = np.random.randn(oLnum,hLnum)
hB = np.random.randn(oLnum)

 

오차역전파 구현
epoch = 100
learnRate = 0.7
Error = np.zeros((epoch, train_x.shape[0]), dtype="float")

for ind_epoch in range(epoch):
    print(ind_epoch + 1, "번째 학습입니다.", ind_epoch + 1, "/", epoch)
    for index_train in range(len(train_x)):
        # FeedForword(순전파)
        # 은닉층 구현
        hSum = w_sum(train_x[index_train], iW, iB)
        hLayer = sigmoid(hSum)

        # 출력층 구현
        oSum = w_sum(hLayer, hW, hB)
        oLayer = sigmoid(oSum)

        # 실제 값과 오차 구하기
        error = MSE(oLayer, train_y[index_train])

        # Back-Propagation(역전파)
        # 은닉층의 가중치와 편향 갱신
        temphW = hW  # 다음층 갱신을 위해 저장
        a2 = Derivative_MSE(oLayer, train_y[index_train]) * Derivative_sigmoid(oSum)
        hB = hB - learnRate * a2
        hW = hW - (learnRate * a2.reshape(a2.shape[0], 1) * hLayer)

        # 입력층의 가중치와 편향 갱신
        a1 = np.sum(a2.reshape(a2.shape[0], 1) * temphW * Derivative_sigmoid(hSum), axis=0)
        iB = iB - learnRate * a1
        iW = iW - (learnRate * a1.reshape(a1.shape[0], 1) * train_x[index_train])

        Error[ind_epoch][index_train] = error

    print(ind_epoch + 1, "번째 학습 MSE :: ", np.sum(Error[ind_epoch]) / Error.shape[1])

 

모델 테스트
testerr = []
for index_test in range(len(test_x)):
    # FeedForword(순전파)
    # 은닉층 구현
    hLayer = sigmoid(w_sum(test_x[index_test], iW, iB))

    # 출력층 구현
    oLayer = sigmoid(w_sum(hLayer, hW, hB))

    print("oLayer[0]", oLayer[0])
    print("test_y[index_test]", test_y[index_test])
    testerr.append(MSE(oLayer[0], test_y[index_test]))
    
print("테스트 MSE 평균 ::", sum(testerr) / len(testerr))