단층 퍼셉트론과 AND, NAND, OR gate 구현

Date:     Updated:

카테고리:

태그:

1. Perceptron

perceptron은 다수의 신호를 입력(input)으로 받아 하나의 신호를 출력(output)하는 개념으로, 딥러닝 신경망의 기원이 되는 알고리즘이다.
퍼셉트론은 신호를 통해 정보를 앞으로 전달하며 정보의 흐름을 만드는데, 이때 신호는 ‘흐른다(1)’, ‘안 흐른다(0)’의 두 가지 값을 가진다.

위 그림은 input으로 2개의 신호를 받은 perceptron의 예이다.

\(x_1​, x_2\) ​: input signal
\(y\) : output signal
\(w_1​, w_2\)​ : 가중치(weight)

기본 식은 \(y = wx + b\)의 1차 방적식이다.

입력 신호(\(x\))가 각 뉴런(이하 노드)에 전달될 때, 각각 고유한 가중치(\(w\))가 곱해진다.
가중치가 곱해진 값들의 합인 가중합이 특정 한계를 넘어설 때 ‘1’을 출력한다.(이를 ‘뉴런이 활성화한다’라고 표현하기도 한다)
그 특정 한계를 ‘임계값’이라 하며, \(θ\) 기호 (theta, 세타)로 나타낸다.

\[y = \begin{cases} 0, \;if\;​(b + w_1​x_1​ + w_2​x_2 \leq θ)\\ 1, \;if\;(b + w_1​x_1 ​+ w_2​x_2​ > θ) \end{cases}\]

\(b + w_1​x_1​ + w_2​x_2\)​ : 가중합(weighted sum)
퍼셉트론의 가중치가 클수록 강한 신호를 흘려보낸다.

2. Logic gate

2.1. AND GATE

AND gate 진리표

\(x_1\) \(x_2\) \(y\)
0 0 0
1 0 0
0 1 0
1 1 1

AND gate를 perceptron으로 표현하고자 한다면, 위 진리표를 만족하는 \(w_1​, w_2​, θ\) 값을 정하면 된다.
ex_ \((w_1​, w_2​, θ) = (0.4, 0.4, 0.7) or (0.5, 0.5, 0.9) or (1.0, 1.0, 1.0)\) 인 경우에 모두 AND gate 조건을 만족
즉, 매개변수를 설정했을 때 \(x_1\)​과 \(x_2\)​가 모두 \(1\)일 때만 가중 신호의 총합이 임계값을 넘는다.

2.2. NAND GATE

NAND gate 진리표

\(x_1\) \(x_2\) \(y\)
0 0 1
1 0 1
0 1 1
1 1 0

NAND는 Not AND이고, 이 의미는 AND gate 출력을 뒤집은 것이다.
위의 AND gate에서는 \(x_1​, x_2​\)가 모두 1일 때만 1을 출력했지만 NAND는 그 반대로 \(x_1​, x_2​\)가 모두 1일 때는 0을 출력하고, 나머지는 모두 1을 출력
AND gate의 모든 매개변수 부호를 반전시키면 NAND gate가 된다.

앞서 AND gate의 예로 든 매개변수의 부호만 바꾸면 NAND gate 진리표를 만족
\((w_1​, w_2​, θ) = (0.4, 0.4, 0.7) or (0.5, 0.5, 0.9) or (1.0, 1.0, 1.0)\)
==> \((w_1​, w_2​, θ) = (-0.4, -0.4, -0.7) or (-0.5, -0.5, -0.9) or (-1.0, -1.0, -1.0)\)

2.3. OR GATE

OR gate 진리표

\(x_1\) \(x_2\) \(y\)
0 0 0
1 0 1
0 1 1
1 1 1

OR gate는 입력 신호(\(x_1​, x_2\)​) 중 하나 이상이 1 값을 가지면 1을 출력한다.

매개변수를 정하는 것은 컴퓨터가 아닌 인간이고, 직접 진리표라는 ‘train data’를 보면서 매개변수 값을 정한다.

‘기계 학습(Machine learning)’이라는 것은 이 매개변수를 컴퓨터가 알아서 정하도록 하는 것이다.

즉, 학습이란 적절한 매개변수를 정하는 과정이라고 할 수 있다.

perceptron의 구조는 AND, NAND, OR gate에서 모두 동일하고, 각 gate에서 상이한 것은 매개변수(가중치, 임계값)다.

3. 구현

import numpy as np

class Perceptron(object):

"""
eta : float, 학습률
n_iter : int
random_state : int
w_ : 1d-array
errors_ : list
"""

    def __init__(self, eta = 0.01, n_iter = 50, random_state = 1):
        self.eta = eta
        self.n_iter = n_iter
        self.random_state = random_state
    
    """
    X : {array-like}, shape = [n_samples, n_feaures] -> {}안에 데이터, n_samples개 샘플과 n_features개의 특성, 훈련데이터
    y : array-like, shape = [n_samples] -> {}안에 데이터, n_samples개의 정답 타깃
    """

    def fit(self, X, y):
        rgen = np.random.RandomState(self.random_state)
        self.w_ = rgen.normal(loc = 0.0, scale = 0.01, size = 1 + X.shape[1])
        self.errors_ = []
        for _ in range(self.n_iter):
            errors = 0
            for xi, target in zip(X, y):
                update = self.eta * (target - self.predict(xi))
                self.w_[1:] += update * xi
                self.w_[0] += update
                errors += int(update != 0.0)
            self.errors.append(errors)
    return self

    def net_input(self, X):
        return np.dot(X, self.w_[1:] + self.w_[0])
    
    def predict(self, X):
        return np.where(self.net_input(X) >= 0.0, 1, -1)

먼저, fit 함수에서 self.w_ 로 표현된 가중치는 벡터 \(R^(m+1)\)로 초기화된다.
m은 데이터 셋의 차원을 뜻한다.
2차원 데이터라면 초기 self.w_는 \(R^3\)로 초기화된다.
size에 X의 개수보다 1을 더한 이유는 self.w_[0] = 절편(b)을 만들기 위함이다.
fit 에서 쓰이고 있는 self.w_벡터는 rgen.normal(loc = 0.0, scale=0.01, size = 1+X.shape[1])로 표준편차(scale)이 0.01인 정규분포에서 뽑은 랜덤한 작은 수를 담고 있고 이 수로 가중치를 초기화한다.사실 정규분포인 것도, 0.01인 것도 크게 의미는 없다.
쓰인 rgen함수는 numpy에서 제공하는 난수 생성기로, 시드를 통해 값을 제공하기 때문에 이전과 같은 결과가 계속 나올 수 있다.  가중치를 처음에 0으로 초기화하지 않는 이유는 가중치를 0으로 초기화하면 학습률이 가중치 벡터의 방향이 아닌 크기에만 영향을 미치기 때문이다.
fit 메서드는 가중치를 초기화한 후 X에 있는 모든 샘플에 가중치를 업데이트 한다.
이 훈련 샘플이 어느 클래스인지는 predict 메서드에서 예측한다.
이 과정을 반복하면서 epoch마다 self.errors_ 리스트에 잘못 분류된 횟수를 기록한다.

DL 카테고리 내 다른 글 보러가기