퍼셉트론(Perceptron)
1957년 미국의 로젠블랫(𝐹𝑟𝑎𝑛𝑘 𝑅𝑜𝑠𝑒𝑛𝑏𝑙𝑎𝑡𝑡)은 동물의 신경세포인 뉴런을 모델 삼아 퍼셉트론 구조를 처음 개발했다.
퍼셉트론의 구조
퍼셉트론에 주어지는 입력 𝑥1, … , 𝑥𝑛은 다른 뉴런들로부터 전달되는 전기 신호에 해당한다.
각 입력 항에 곱해지는 가중치 𝑤1, … , 𝑤𝑛은 뉴런 연결 부위에 형성된 시냅스의 발달 정도에 해당한다.
𝑥𝑖, 𝑤𝑖 값들을 합산하는 ∑(시그마) 처리는 각 가지돌기로부터 들어오는 전기 신호들이 뉴런 세포체 안에서 합해지는 과정에 해당한다.
가중치라고 부르는 이 weight는 각각의 입력신호에 부여되어 입력신호와의 계산을 하고 신호의 총합이 정해진 임계값(θ; theta,세타)을 넘었을 때 1을 출력한다.
각 입력신호에는 고유한 weight가 부여되며 weight가 클수록 해당 신호가 중요하다고 볼 수 있다.
비선형 함수 𝑓 는 ’활성화 함수’라고도 불리며 뉴런 세포체가 단순히 입력 전기신호를 합한 값을 출력으로 삼지 않고 나름의 처리를 거쳐 출력값을 결정하는 것처럼 최종 출력을 결정한다.
결국 퍼셉트론의 출력은 𝑦 = 𝑓 ( 𝑥1𝑤1 + ⋯ + 𝑥𝑛𝑤𝑛 + 𝑏 ) 로 계산되며 딥러닝 알고리즘에서 퍼셉트론을 이용한다는 말은 이런 계산식을 이용해 입력으로부 터 출력을 구한다는 것을 의미한다.
퍼셉트론의 출력 값은 앞에서 말했듯이 1 또는 0(or -1)이기 때문에 선형 분류(linear classifier) 모형이라고도 볼 수 있다. 보통 실수형의 입력 벡터를 받아 이들의 선형조합을 계산하는 것이다.
퍼셉트론의 학습 방법
처음에는 임의로 설정된 weight로 결과를 나타낸다.
학습 데이터를 퍼셉트론 모형에 입력하며 분류가 잘못됐을 때 가중치를 개선해 나간다.
퍼셉트론의 오류가 최소화될 수 있는 방향으로 가중치를 조금씩 조정해나간다.
정답을 최대한 많이 맞출 수 있도록 최적의 가중치 조합을 찾는 게 학습의 목표가 된다.
가중치와 편향
앞의 퍼셉트론 수식에서 나오는 세타θ를 -b로 치환하여 좌변으로 넘기면
𝑥1𝑤1 + ⋯ + 𝑥𝑛𝑤𝑛 + 𝑏 <0 -> 0
𝑥1𝑤1 + ⋯ + 𝑥𝑛𝑤𝑛 + 𝑏 >=0 -> 1
과 같이 되며 여기에서 b를 편향(bias)라고 할 수 있다.
기계학습 분야에서는 모델이 학습 데이터에 과적합(overfitting)되는 것을 방지하는 것이 중요하다.
θ(theta)로 학습 데이터(Input)이 가중치와 계산되어 넘어야 하는 임계점으로 이 값이 높으면 높을 수록 그만큼 분류의 기준이 엄격하다는 것을 의미한다. 그래서 편향이 높을 수록 모델이 간단해지는 경향이 있으며 (변수가 적고 더 일반화 되는 경우) 오히려 과소적합(underfitting)의 위험이 발생하게 된다.
반대로 편향이 낮을수록 한계점이 낮아 데이터의 허용범위가 넓어지는 만큼 학습 데이터에만 잘 들어맞는 모델이 만들어질 수 있으며 모델이 더욱 복잡해질 것이다. 허용범위가 넓어지는 만큼 필요 없는 노이즈가 포함될 가능성도 높다. 이를 편향과 분산의 트레이드오프 관계라고 한다.
학습 과정 중에 끊임없이 변경되어 가면서 퍼셉트론의 동작 특성을 결정하는 값들을 파라미터(𝒑𝒂𝒓𝒂𝒎𝒆𝒕𝒆𝒓)라고 한다.
가중치(weight)는 입력신호가 결과 출력에 주는 영향도를 조절하는 파라미터이고,
편향(bias)은 뉴런(또는 노드; x를 의미)이 얼마나 쉽게 활성화(1로 출력; activation)되느냐를 조정하는(adjust) 파라미터이다.
퍼셉트론의 한계점
AND 게이트
AND 연산은 입력이 모두 참 값일 때 참이 되고, 그 밖의 경우에는 모두 거짓이 됩니다.
# 시그모이드
def sigmoid(x):
return 1 / (1 + np.exp(-x))
import numpy as np
def AND_gate(x1, x2):
w1, w2, theta = 0.5, 0.5, 0.7
tmp = x1*w1 + x2*w2
if tmp <= theta:
return 0
elif tmp > theta:
return 1
inputs = [(0, 0), (1, 0), (0, 1), (1, 1)]
for x1, x2 in inputs:
y = AND_gate(x1, x2)
print('({x1}, {x2}) -> {y}'.format(x1=x1, x2=x2, y=y))
import numpy as np
x = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
y = np.array([[1], [0], [0], [0]])
w = tf.random.normal([2], 0, 1)
b = tf.random.normal([1], 0, 1)
b_x = 1
for i in range(2000):
error_sum = 0
for j in range(4):
output = sigmoid(np.sum(x[j] * w) + b_x * b)
error = y[j][0] - output
w = w + x[j] * 0.1 * error # element-wise
b = b + b_x * 0.1 * error
error_sum += error
if i % 200 == 199:
print(i, error)
# 네트워크 평가
for i in range(4):
print('X : ', x[i], 'Y : ', y[i], 'Output : ', sigmoid(np.sum(x[i] * w) + b) )
OR 게이트
OR 연산은 입력 중 하나만 참 이어도 결괏값이 참이 되고, 모두 거짓일 때만 거짓이 됩니다.
import numpy as np
def OR_gate(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.2
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
inputs = [(0, 0), (1, 0), (0, 1), (1, 1)]
for x1, x2 in inputs:
y = OR_gate(x1, x2)
print('({x1}, {x2}) -> {y}'.format(x1=x1, x2=x2, y=y))
import numpy as np
x = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
y = np.array([[1], [1], [1], [0]])
w = tf.random.normal([2], 0, 1)
b = tf.random.normal([1], 0, 1)
b_x = 1
for i in range(2000):
error_sum = 0
for j in range(4):
output = sigmoid(np.sum(x[j] * w) + b_x * b)
error = y[j][0] - output
w = w + x[j] * 0.1 * error
b = b + b_x * 0.1 * error
error_sum += error
if i % 200 == 199:
print(i, error)
# 네트워크 평가
for i in range(4):
print('X : ', x[i], 'Y : ', y[i], 'Output : ', sigmoid(np.sum(x[i] * w) + b) )
NAND 게이트
NAND 연산은 입력 중 하나만 거짓이어도 결괏값이 참이 되고, 모두 참일 때만 거짓이 됩니다.
import numpy as np
def NAND_gate(x1, x2):
x = np.array([x1, x2])
w = np.array([-0.5, -0.5])
b = 0.7
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
inputs = [(0, 0), (1, 0), (0, 1), (1, 1)]
for x1, x2 in inputs:
y = NAND_gate(x1, x2)
print('({x1}, {x2}) -> {y}'.format(x1=x1, x2=x2, y=y))
import numpy as np
x = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
y = np.array([[0], [1], [1], [1]])
w = tf.random.normal([2], 0, 1)
b = tf.random.normal([1], 0, 1)
b_x = 1
for i in range(2000):
error_sum = 0
for j in range(4):
output = sigmoid(np.sum(x[j] * w) + b_x * b)
error = y[j][0] - output
w = w + x[j] * 0.1 * error
b = b + b_x * 0.1 * error
error_sum += error
if i % 200 == 199:
print(i, error)
for i in range(4):
print('X : ', x[i], 'Y : ', y[i], 'Output : ', sigmoid(np.sum(x[i] * w) + b) )
XOR 게이트
홀수 개의 입력이 참일 때만 결괏값이 참이 된다. 간단하게 입력이 2개일 때는 입력 2 개의 값이 서로 다를 때라고 생각해도 좋다.
import numpy as np
def XOR_gate(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
y = AND(s1, s2)
return y
inputs = [(0, 0), (1, 0), (0, 1), (1, 1)]
for x1, x2 in inputs:
y = XOR_gate(x1, x2)
print('({x1}, {x2}) -> {y}'.format(x1=x1, x2=x2, y=y))
import numpy as np
x = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
y = np.array([[0], [1], [1], [0]])
w = tf.random.normal([2], 0, 1)
b = tf.random.normal([1], 0, 1)
b_x = 1
for i in range(2000):
error_sum = 0
for j in range(4):
output = sigmoid(np.sum(x[j] * w) + b_x * b)
error = y[j][0] - output
w = w + x[j] * 0.1 * error
b = b + b_x * 0.1 * error
error_sum += error
if i % 200 == 199:
print(i, error_sum)
for i in range(4):
print('X : ', x[i], 'Y : ', y[i], 'Output : ', sigmoid(np.sum(x[i] * w) + b) )
단층 퍼셉트론으로 AND, NAND, OR 게이트는 구현 가능하지만, XOR 게이트는 구현할 수 없다.
퍼셉트론은 아래와 같이 직선 으로 나뉜 두 영역을 만든다. 하지만 XOR은 직선으로 두 영역을 나눌 수 없다.
퍼셉트론의 한계를 간략히 말하면, 직선 하나로 나눈 영역만 표현할 수 있어 XOR과 같은 데이터 형태는 분류가 불가능하다는 한계가 있다.
다층 퍼셉트론
단층 퍼셉트론으로는 XOR을 구현할 수 없지만, 다층 퍼센트론(multi-layer perceptron) 으로 XOR 게이트를 구현할 수 있다.
다층(multi-layer)이라는 말은 하나의 퍼셉트론에 또 다른 퍼셉트론을 덧붙인다는 의미로 볼 수 있다. 단층 퍼셉트론이 비선형 영역을 분리할 수 없다는 것이 문제이며 다층으로 할 경우 비선형으로 이를 해결할 수 있다.
다층 퍼셉트론의 모델링
'입력층 - 은닉층 - 출력층'이라는 3층 네트워크 구조로 만들면 XOR 게이트처럼 선형 분리 불가능한 데이터도 분리 가능하기 때문에 MLP 모델을 일반화하면 더욱 복잡한 데이터도 분류 가능하다.
'DL(Deep-Learning) > 개념' 카테고리의 다른 글
활성화 함수 activation function (0) | 2022.01.29 |
---|---|
[실습] numpy로 만드는 단층 신경망 - 회귀 문제 (0) | 2022.01.29 |
[DL] 오차 역전파 Error Backpropagation (0) | 2022.01.28 |
[DL] 신경망 Neural Network (0) | 2022.01.28 |
인공지능이란? (0) | 2022.01.28 |