퍼셉트론(Perceptron)이란?
퍼셉트론은 인공 신경망의 가장 기본이 되는 알고리즘으로, 생물학적 뉴런의 동작을 모방하여 패턴 인식과 분류 문제를 해결하기 위해 고안되었다.
역사적 배경
1943년 Warren McCulloch와 Walter Pitts는 "신경활동에서 생각의 논리적 계산(A Logical Calculus of the Ideas Immanent in Nervous Activity)"이라는 논문을 발표하여, 수학적 모델을 통해 뉴런의 동작을 설명했다. 이 논문은 인공 신경망 연구의 출발점이 되었다.
1957년 Frank Rosenblatt는 이 아이디어를 발전시켜 퍼셉트론(Perceptron) 알고리즘을 고안했다. Rosenblatt의 퍼셉트론은 가중치 수정 규칙을 통해 이진 분류기를 학습할 수 있다는 것을 증명했으며, 이는 기계 학습의 중요한 이정표가 되었다.
퍼셉트론의 구조와 작동 원리
퍼셉트론은 다음과 같은 구조로 이루어져 있다:
- 입력층: 여러 개의 입력 신호를 받는다
- 가중치: 각 입력에 대한 가중치를 곱한다
- 합산: 가중치가 곱해진 입력들을 모두 합산한다
- 활성화 함수: 합산된 값을 활성화 함수를 통해 출력한다
퍼셉트론 구조 시각화
x1, x2, bias를 입력으로 받는 퍼셉트론의 구조:
구조 설명:
- 입력층: x1, x2 (입력 특징), b (bias, 항상 1)
- 가중치: w1, w2, b (각 입력에 대한 가중치)
- 가중합: z = w1·x1 + w2·x2 + wb·b
- 활성화 함수: f(z) (해당 예제에서는 계단함수 사용)
- 출력: y = f(z) (0 또는 1)
Rosenblatt 는 활성화함수로 계단함수(입력값에 따라 출력을 0 또는 1과 같은 이진값으로 나타내는 함수)를 사용했다. 계단함수는 거의 모든 구간에서 미분값이 0이고 불연속점에서는 미분이 불가능하기에 경사하강법기반 학습에는 사용할 수 없다. 해당 예제에서는 Rosenblatt’s Perceptron Learning Rule 을 적용하였다.
단층 퍼셉트론으로는 XOR문제를 해결하는 비선형 분리기를 만들 수 없다. 이러한 한계로 인해 1980년대 후반, 다층 퍼셉트론과 역전파 알고리즘이 재발견 되기까지 인공 신경망 분야는 발전하지 못했다.
퍼셉트론 학습 규칙
퍼셉트론은 다음과 같은 학습 규칙을 사용하여 가중치를 업데이트한다:
여기서:
- : 학습률(learning rate)
- : 실제 정답
- : 예측값
- : i번째 입력
이 규칙은 예측이 틀렸을 때만 가중치를 조정하며, 정답에 가까워질 때까지 반복적으로 학습한다.
논리 게이트와 퍼셉트론
논리 게이트는 퍼셉트론의 동작을 이해하기에 좋은 예제이다. AND, OR, NAND 게이트는 모두 선형적으로 분리 가능하기 때문에 단층 퍼셉트론으로 구현할 수 있다.
- AND 게이트: 두 입력이 모두 1일 때만 1을 출력
- OR 게이트: 두 입력 중 하나라도 1이면 1을 출력
- NAND 게이트: AND 게이트의 반대 (두 입력이 모두 1일 때만 0을 출력)
이러한 게이트들은 2차원 입력 공간에서 직선 하나로 두 클래스를 분리할 수 있기 때문에, 단층 퍼셉트론으로 학습이 가능하다.
이제 실제로 AND, OR, NAND 게이트를 단층 퍼셉트론으로 구현해보자.
# Rosenblatt의 퍼셉트론 알고리즘 구현
import numpy as np
# AND 게이트 데이터
X_original = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
# 각 입력에 1을 추가하여 [x1, x2, 1] 형태로 만듦
# 이렇게 하면 편향(bias)도 가중치의 일부가 됨
X = np.column_stack([X_original, np.ones(len(X_original))]) # [x1, x2, 1]
y = np.array([0, 0, 0, 1])
print("Rosenblatt의 AND 게이트 퍼셉트론 학습 알고리즘")
print("=" * 60)
print(f"입력 데이터 (augmented):\n{X}")
print(f"목표 출력: {y}\n")
Rosenblatt의 AND 게이트 퍼셉트론 학습 알고리즘 ============================================================ 입력 데이터 (augmented): [[0. 0. 1.] [0. 1. 1.] [1. 0. 1.] [1. 1. 1.]] 목표 출력: [0 0 0 1]
# 계단 함수 (Step Function) - Rosenblatt의 활성화 함수
def step_function(x):
"""입력이 0보다 크거나 같으면 1, 아니면 0을 반환"""
return 1 if x >= 0 else 0
def forward(X, w):
"""
순전파: 가중합 계산
"""
z = np.dot(X, w)
return step_function(z)
# 가중치 초기화 (bias 포함: [w1, w2, b])
# Rosenblatt는 보통 작은 랜덤 값이나 0으로 초기화
np.random.seed(1)
w = np.random.randn(3) * 0.1 # [w1, w2, bias]
print(f"초기 가중치: w = [{w[0]:.4f}, {w[1]:.4f}, {w[2]:.4f}]")
print(f"초기 가중치 해석: w1={w[0]:.4f}, w2={w[1]:.4f}, bias={w[2]:.4f}\n")
# 학습률 설정
lr = 0.1
# 학습 반복 횟수
n_epochs = 10
# 추적을 위한 리스트
epoch_errors = [] # 각 epoch의 오류 개수
epoch_accuracy = [] # 각 epoch의 정확도
print("학습 시작...")
print("-" * 60)
for epoch in range(n_epochs):
errors = 0
for i in range(len(X)):
x = X[i] # [x1, x2, 1]
y_true = y[i]
# 활성화 함수 적용 (계단 함수)
y_pred = forward(x, w)
# Rosenblatt 학습 규칙: 오류가 있을 때만 가중치 업데이트
# 가중치 업데이트: w = w + α * (y_true - y_pred) * x
if y_pred != y_true:
error = y_true - y_pred # +1 또는 -1
# error 가 1인 경우, w * x 는 증가해야 함
# error 가 -1인 경우, w * x 는 감소해야 함
w += lr * error * x
errors += 1
# 기록
accuracy = (len(X) - errors) / len(X)
epoch_errors.append(errors)
epoch_accuracy.append(accuracy)
# 학습 진행 상황 출력
if errors == 0:
print(f"Epoch {epoch+1}: 수렴 완료! (모든 샘플 정확히 분류) - 정확도 = {accuracy:.2%}")
break
print(f"Epoch {epoch+1}: 정확도 = {accuracy:.2%}")
print("-" * 60)
print(f"\n최종 가중치: w = [{w[0]:.4f}, {w[1]:.4f}, {w[2]:.4f}]")
print(f"해석: w1={w[0]:.4f}, w2={w[1]:.4f}, bias={w[2]:.4f}\n")
all_correct = True
for i in range(len(X)):
x = X[i]
z = np.dot(w, x)
y_pred = forward(x, w)
y_true = y[i]
status = "✓ 정확" if y_pred == y_true else "✗ 오류"
if y_pred != y_true:
all_correct = False
print(f"{str(X_original[i]):<15} {z:>10.4f} {y_pred:>6} {y_true:>6} {status}")
초기 가중치: w = [0.1624, -0.0612, -0.0528] 초기 가중치 해석: w1=0.1624, w2=-0.0612, bias=-0.0528 학습 시작... ------------------------------------------------------------ Epoch 1: 정확도 = 50.00% Epoch 2: 정확도 = 50.00% Epoch 3: 정확도 = 25.00% Epoch 4: 정확도 = 50.00% Epoch 5: 정확도 = 75.00% Epoch 6: 수렴 완료! (모든 샘플 정확히 분류) - 정확도 = 100.00% ------------------------------------------------------------ 최종 가중치: w = [0.1624, 0.1388, -0.2528] 해석: w1=0.1624, w2=0.1388, bias=-0.2528 [0 0] -0.2528 0 0 ✓ 정확 [0 1] -0.1140 0 0 ✓ 정확 [1 0] -0.0904 0 0 ✓ 정확 [1 1] 0.0484 1 1 ✓ 정확
import matplotlib.pyplot as plt
# Decision Boundary 그래프 - 별도 figure
plt.figure(figsize=(6, 6))
plt.scatter(X_original[y == 0, 0], X_original[y == 0, 1],
c='red', s=150, marker='o', label='Output = 0',
edgecolors='black', linewidths=2, alpha=0.7)
plt.scatter(X_original[y == 1, 0], X_original[y == 1, 1],
c='blue', s=150, marker='s', label='Output = 1',
edgecolors='black', linewidths=2, alpha=0.7)
# Draw decision boundary: w1*x1 + w2*x2 + bias = 0
# x2 = -(w1*x1 + bias) / w2
if abs(w[1]) > 1e-10: # When w2 is not zero
x1_line = np.linspace(-0.5, 1.5, 100)
x2_line = -(w[0] * x1_line + w[2]) / w[1]
plt.plot(x1_line, x2_line, 'g-', linewidth=3, alpha=0.8)
# x, y 축 그리기 (0, 0이 왼쪽 아래 구석에 오도록)
plt.axhline(y=0, color='black', linewidth=1, linestyle='-', alpha=0.5)
plt.axvline(x=0, color='black', linewidth=1, linestyle='-', alpha=0.5)
plt.xlim(-0.1, 1.5)
plt.ylim(-0.1, 1.5)
plt.xlabel('x1')
plt.ylabel('x2')
plt.title('AND Gate Decision Boundary')
plt.grid(True, alpha=0.3, linestyle='--')
plt.legend(fontsize=12, loc='upper right')
plt.show()# Accuracy 그래프 - 별도 figure
plt.figure(figsize=(7, 5))
# Epoch 범위 생성
epochs_range = range(1, len(epoch_errors) + 1)
plt.plot(epochs_range, epoch_accuracy, 'g-^', linewidth=2, markersize=8, label='Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('AND Gate Accuracy per Epoch')
plt.grid(True, alpha=0.3, linestyle='--')
plt.legend(fontsize=10)
plt.axhline(y=1.0, color='gray', linestyle='--', alpha=0.5, label='Perfect Accuracy')
plt.tight_layout()
plt.show()
OR, NAND 게이트에 대한 decision boundary 를 구하고 싶다면 목표출력을 다음과 같이 바꾸면 된다.
# OR
y = np.array([0, 1, 1, 1])
# NAND
y = np.array([1, 1, 1, 0])
단일 계층 퍼셉트론의 한계
1969년 Marvin Minsky와 Seymour Papert는 그들의 저서 "Perceptrons"에서 단일층 퍼셉트론이 가진 근본적인 한계를 수학적으로 증명했다. 그들은 XOR 문제를 예로 들어, 이 문제가 선형적으로 분리 불가능하기 때문에 단일 퍼셉트론으로는 해결할 수 없음을 보였다. 이는 단일 직선으로는 두 클래스를 분리할 수 없는 문제를 의미하며, 퍼셉트론의 수학적 한계를 명확히 보여주었다.
- AND 게이트: 선형 분리 가능 ✓
- OR 게이트: 선형 분리 가능 ✓
- NAND 게이트: 선형 분리 가능 ✓
- XOR 게이트: 선형 분리 불가능 ✗
이러한 발견으로 인해 신경망 연구에 대한 흥미와 연구 자금이 급격히 줄어들면서 인공지능 분야는 첫 번째 "겨울(AI Winter)"을 맞게 되었다.
Minsky와 Papert는 단층의 한계를 지적했지만, 동시에 다층 퍼셉트론(Multi-Layer Perceptron, MLP)은 XOR 문제를 해결할 수 있다는 가능성을 언급했다. 은닉층(hidden layer)을 도입하면 선형적으로 분리 불가능한 문제도 해결할 수 있는 비선형 결정 경계를 만들 수 있다는 것이었다.
1980년대 중반, 여러 연구자들(특히 David Rumelhart, Geoffrey Hinton, Ronald Williams 등)이 다층 신경망을 효율적으로 훈련시킬 수 있는 역전파(Backpropagation) 알고리즘을 재발견하고 발전시켰다. 이 알고리즘은 다층 퍼셉트론의 가중치를 효과적으로 조정할 수 있게 하여, XOR 문제와 같은 비선형 문제를 성공적으로 해결할 수 있게 했다.
AND, OR, NAND 문제 해결의 의미
AND, OR, NAND는 단층 퍼셉트론으로 구현이 가능하며, 이것들을 조합하면 단층 퍼셉트론으로 해결하지 못한 XOR 문제를 구현하는 것이 가능하다.
논리 회로 이론에서 **NAND 게이트는 유니버설 게이트(Universal Gate)**로 알려져 있다. 이는 NAND 게이트만으로 모든 논리 연산(AND, OR, NOT, XOR 등)을 구현할 수 있다는 의미이다. 예를 들어, XOR 게이트는 다음과 같이 NAND 게이트를 조합하여 구현할 수 있다:
XOR(A, B) = NAND(NAND(A, NAND(A, B)), NAND(B, NAND(A, B)))
이러한 사실은 다층 퍼셉트론의 계산 능력에 대한 중요한 시사점을 제공한다. Universal Approximation Theorem(Cybenko, 1989; Hornik et al., 1989)에 따르면, 하나의 은닉층을 가진 다층 퍼셉트론만으로도 연속 함수를 임의의 정확도로 근사할 수 있다. 즉, 충분한 뉴런과 적절한 활성화 함수가 주어지면, 다층 퍼셉트론은 광범위한 함수 클래스를 표현할 수 있다.