본문 바로가기
Computer Science/AI

XOR을 위한 다층 퍼셉트론

by HaningYa 2020. 9. 11.
728x90

XOR

퍼셉트론

1943년 신경과학자 Warren S McCulloch 와 논리학자 Walter Pitts는 하나의 사람 뇌 신경세포를 하나의 이진(Binary)출력을 가지는 단순 논리 게이트로 설명.

출처: https://blog.naver.com/samsjang/220948258166

여러개의 입력 신호가 가지돌기에 도착하면 신경세포 내에서 이들을 하나의 신호로 통합하고 통합된 신호값이 입계값을 초과하면 하나의 단일신호가 생성되며 이 신호가 축삭돌기를 통해 다른 신경세포로 전달하는 것으로 이해함. 이렇게 단순화 된 원리로 동작하는 뇌 세포를 MCP뉴런이라고 부름

이 MCP뉴런 모델을 기초로 퍼셉트론 학습 규칙이라는 개념을 고안하게됨

MCP뉴런이 출력신호를 발생할지 안할지 결정하기 위해, MCP뉴런으로 들어오는 각 입력값에 곱해지는 가중치 값을 자동적으로 학습하는 알고리즘을 제안

*활성함수: 순입력 함수의 결과값을 특정 임계값과 비교하고 순입력 함수 결과값이 이 임계값 보다 크면 1, 그렇지 않으면 -1로 출력하는 함수

출처: https://blog.naver.com/samsjang/220948258166

  • 단층 퍼셉트론: 중간층이 하나의 노드로 구성되어 중간층과 출력층의 구분이 없는 구조
  • 다층 퍼셉트론: 중간층을 구성하는 노드가 여러개 이고, 이러한 중간층이 다수로 구성되어 있는 구조, 여러개의 활성함수, 이에 따른 가중치도 여러개

이러한 다층 인공신경망을 학습하는 알고리즘을 딥러닝 이라고 함

단층 퍼셉트론에서 XOR게이트 구현이 불가능한 이유

단층 퍼셉트론의 한계: 비선형적으로 분리되는 데이터에 대해서는 제대로 된 학습이 불가능함.
그 예시가 AND연산에 대해서는 학습이 가능하지만 XOR에 대해선 학습이 불가능함

이를 극복하기 위한 방안으로 나온 것이 입력층과 출력층 사이에 하나 이상의 중간층을 두어 비선형적으로 분리되는 데이터에 대해서 학습 가능하도록 다층 퍼셉트론이 고안됨


다층 퍼셉트론을 학습할 때, 학습률(learning rate)이 모델의 학습에 미치는 영향

퍼셉트론 알고리즘에서 가중치를 업데이트 하는 식은 다음과 같습니다.

출처: http://blog.naver.com/PostView.nhn?blogId=samsjang&logNo=220948258166&parentCategoryNo=49&categoryNo=&viewDate=&isShowPopularPosts=true&from=search

  • wj: 트레이닝 데이터의 j번째 특성값 xj와 곱하는 가중치
  • y: 트레이닝 데이터의 실제 결과값에 대한 활성 함수 리턴값
  • y^: 예측값에 대한 활성 함수 리턴값
  • n: 학습률

퍼셉트론에서 학습률을 매우 작은 값으로 할당합니다.

여기서 learning rate 에 대해 알아보면

The learning rate is a hyperparameter that controls how much to change the model in response to the estimated error each time the model weights are updated

즉 학습률은 모델 학습에 있어 변화의 폭을 담당하는 것 같습니다.

학습 파라미터를 어떤식으로 초기화 하는게 좋은가

[파라미터 접근방식]

  • 지정된 파라미터: 접근 가능한 고유의 파라미터 사전을 통해 직접 접근하여 초기화
  • 한번에 모든 파라미터 지정: collect_params 메소드를 통해 하나의 사전에 담아두고 쉽게 조회 가능

[파라미터 초기화 방식]

  • 제공되는 초기화 (빌트인 초기화)
  • 커스텀 초기화 (init 모듈에 없을 경우)

 

코드

Input 데이터와 label을 행렬로 표현하는 코드입니다.

reshape 를 통해 인풋인 X는 2x4의 행렬을 만들어 주고 출력인 Y는 1x4 행렬을 만들어 줍니다.

import numpy as np

X = np.array([0, 0, 1, 1, 0, 1, 0, 1]).reshape(2,4)
Y = np.array([0, 1, 1, 0]).reshape(1,4)

print(X)
print(Y)

모든 파라미터를 0으로 초기화합니다.

XOR를 구현하기 위해선 다층 퍼셉트론이 필요하기 때문에 2개의 weight 와 bias를 사용합니다.

def init_parameters (num_hidden=2):
    W1 = np.zeros((2,num_hidden))
    B1 = np.zeros((num_hidden,1))
    W2 =  np.zeros((num_hidden,1))
    B2 = np.zeros((1,1))
    return W1, B1, W2, B2

W1, B1, W2, B2 = init_parameters()

Hidden Layer 행렬로 표현합니다.

def affine (W, X, B):
    return np.dot(W.T, X) + B

def sigmoid (o):
    return 1./(1+np.exp(-1*o))

Z1=affine(W1,X,B1)
H=sigmoid(Z1)
print(H)

output Layer 정의

Z2 = affine(W2,H, B2)
Y_hat = sigmoid(Z2)
print(Y_hat)

Loss Function 행렬로 구하기

def loss_eval (_params):
    
    W1, B1, W2, B2 = _params
    
    # Forward: input Layer
    Z1 = affine(W1, X, B1)
    H  = sigmoid(Z1)

    # Forward: Hidden Layer
    Z2 = affine(W2, H, B2)
    Y_hat = sigmoid(Z2)

    loss = 1./X.shape[1] * np.sum(-1 * (Y * np.log(Y_hat) + (1-Y) * np.log(1-Y_hat)))
    return Z1, H, Z2, Y_hat, loss

loss_eval ([W1, B1, W2, B2])[-1]

Loss에 대한 parameter 별 편미분을 이용한 parameter update rule 표현 중 gradient 구하기

def get_gradients (_params):
       
    W1, B1, W2, B2 = _params
    m = X.shape[1]
    
    Z1, H, Z2, Y_hat, loss = loss_eval([W1, B1, W2, B2])
    
    # BackPropagate: Hidden Layer
    dW2 = np.dot(H, (Y_hat-Y).T)
    dB2 = 1. / 4. * np.sum(Y_hat-Y, axis=1, keepdims=True)    
    dH  = np.dot(W2, Y_hat-Y)

    # BackPropagate: Input Layer
    dZ1 = dH * H * (1-H)
    dW1 = np.dot(X, dZ1.T)
    dB1 = 1. / 4. * np.sum(dZ1, axis=1, keepdims=True)
    
    return [dW1, dB1, dW2, dB2], loss

BackPropagation을 통해 Multi-layer neural network 훈련

1000번의 iteratione동안 학습시켜 봅니다.

def optimize (_params, learning_rate = 0.1, iteration = 1000, sample_size = 0):
    
    params = np.copy(_params)

    loss_trace = []

    for epoch in range(iteration):
        
        dparams, loss = get_gradients(params)
        
        for param, dparam in zip(params, dparams):
            param += - learning_rate * dparam
        
        if (epoch % 100 == 0):
            loss_trace.append(loss)
        
    _, _, _, Y_hat_predict, _ = loss_eval(params) 
    
    return params,loss_trace, Y_hat_predict
    

모델 돌리고 loss 변화하는 cost 그려보기

params = init_parameters(2)
new_params, loss_trace, Y_hat_predict = optimize(params, 0.1, 100000, 0)
print(Y_hat_predict)
print(new_params)

코스트 함수가 변하지 않음

parameter를 random으로 초기화 하여 cost 함수 문제 해결

def init_random_parameters (num_hidden = 2, deviation = 1):

    W1 = np.random.rand(2,num_hidden)*deviation
    B1 = np.random.random((num_hidden,1))*deviation
    W2 = np.random.rand(num_hidden,1)*deviation
    B2 = np.random.random((1,1))*deviation
    return W1, B1, W2, B2

init_random_parameters ()
params = init_random_parameters(2, 0.1)
new_params, loss_trace, Y_hat_predict = optimize(params, 0.1, 100000)
print(Y_hat_predict)
# Plot learning curve (with costs)
plt.plot(loss_trace)
plt.ylabel('loss')
plt.xlabel('iterations (per hundreds)')
plt.show()


sigmoid 말고 tanh 사용하기

Hidden layer에서 sigmiod 보다 tanh 함수가 더 좋은 성능을 낸다.

왜냐하면 tanh함수는 입력의 총합을 -1 에서 1사이의 값으로 변환해 주고 원점 중심이기 때문에 시그모이드와 달리 편향 이동이 일어나지 않는다.

def tanh(x):
    ex = np.exp(x)
    enx = np.exp(-x)
    return (ex-enx)/(ex+enx)

def loss_eval_tanh (_params):
    
    W1, B1, W2, B2 = _params
    
    # Forward: input Layer
    Z1 = affine(W1, X, B1)
    H  = tanh(Z1)

    # Forward: Hidden Layer
    Z2 = affine(W2, H, B2)
    Y_hat = sigmoid(Z2)

    loss = 1./X.shape[1] * np.sum(-1 * (Y * np.log(Y_hat) + (1-Y) * np.log(1-Y_hat)))
    return Z1, H, Z2, Y_hat, loss

def get_gradients_tanh (_params):
       
    W1, B1, W2, B2 = _params
    
    Z1, H, Z2, Y_hat, loss = loss_eval_tanh([W1, B1, W2, B2])
    
    # BackPropagate: Hidden Layer
    dW2 = np.dot(H, (Y_hat-Y).T)
    dB2 = 1./4. * np.sum(Y_hat-Y, axis=1, keepdims=True)    
    dH  = np.dot(W2, Y_hat-Y)

    # BackPropagate: Input Layer
    dZ1 = dH * (1 - (H * H)) # <- Changed!
    dW1 = np.dot(X, dZ1.T)
    dB1 = 1./4. * np.sum(dZ1, axis=1, keepdims=True)
    
    return [dW1, dB1, dW2, dB2], loss

def optimize_tanh (_params, learning_rate = 0.1, iteration = 1000, sample_size = 0):
    
    params = np.copy(_params)

    loss_trace = []

    for epoch in range(iteration):
        
        dparams, loss = get_gradients_tanh(params)
        
        for param, dparam in zip(params, dparams):
            param += - learning_rate * dparam
        
        if (epoch % 100 == 0):
            loss_trace.append(loss)
        
    _, _, _, Y_hat_predict, _ = loss_eval_tanh(params) 
    
    return params,loss_trace, Y_hat_predict   
params = init_random_parameters(2, 0.1)
new_params, loss_trace, Y_hat_predict = optimize_tanh(params, 0.1, 5000)
print(Y_hat_predict)
print(loss_trace[-1])
# Plot learning curve (with costs)
plt.plot(loss_trace)
plt.ylabel('loss')
plt.xlabel('iterations (per hundreds)')
plt.show()

코드 실행해본 Colab 링크

colab.research.google.com/drive/1XRTDwqNkH05RMDxpX38sCNPyqPSl7s2s?usp=sharing

 

Google Colaboratory

 

colab.research.google.com

 

 

 

[출처]
blog.naver.com/samsjang/220948258166

 

[2편] 퍼셉트론(Perceptron) - 인공신경망의 기초개념

​퍼셉트론(Perceptron)​​인공지능(AI)은 우리 사람의 뇌를 흉내내는 인공신경망과 다양한 머신러닝 ...

blog.naver.com

excelsior-cjh.tistory.com/177

 

05-1. 심층 신경망 학습 - 활성화 함수, 가중치 초기화

5-1. 심층 신경망 학습 - 활성화 함수, 가중치 초기화 저번 포스팅 04. 인공신경망에서 예제로 살펴본 신경망은 hidden layer가 2개인 얕은 DNN에 대해 다루었다. 하지만, 모델이 복잡해질수록 hidden layer

excelsior-cjh.tistory.com

 

728x90

댓글