Single Layer Perceptron (SLP)이란?¶
Single Layer Perceptron은 가장 기본적인 인공신경망 모델입니다.
특징¶
- 구조: 입력층 → 출력층 (단 2개 레이어)
- 용도: 선형 분류(Linear Classification) 문제 해결
- 활성화 함수: Step 함수 또는 Sigmoid 함수 사용
수식¶
$$y = f(w \cdot x + b)$$
- $w$: 가중치 벡터
- $x$: 입력 벡터
- $b$: 편향(Bias)
- $f$: 활성화 함수
학습 방식¶
- 초기 가중치와 편향 설정
- 각 샘플에 대해 예측 수행
- 오류 계산 (실제값 - 예측값)
- 오류에 따라 가중치 업데이트: $w := w + \eta \cdot \text{error} \cdot x$
한계¶
- 비선형 문제 해결 불가능 (예: XOR 문제)
- 더 복잡한 문제는 Multi-Layer Perceptron(MLP) 필요
In [16]:
import numpy as np
import matplotlib.pyplot as plt
def visualize_perceptron_100(x1, x2):
# 1. 파라미터 설정
w1, w2, b = 0.5, 0.5, -0.7
z = (x1 * w1) + (x2 * w2) + b
y = 1 if z > 0 else 0
# 2. 너비를 꽉 채우기 위해 figsize 가로를 16으로 확장
fig = plt.figure(figsize=(16, 5), facecolor='white')
ax = plt.gca()
# 원이 찌그러지지 않게 비율 고정
ax.set_aspect('equal')
# 가로로 긴 화면에 맞춰 x축 범위 확장 (0~10 -> 0~16)
ax.set_xlim(0, 16)
ax.set_ylim(0, 5)
# 노드 위치 재배치 (너비 100% 느낌을 위해 간격 조정)
nodes = {
'x1': (2, 3.5),
'x2': (2, 1.5),
'sum': (8, 2.5),
'out': (14, 2.5)
}
r = 0.6 # 원의 반지름을 조금 더 키워 가독성 확보
# 3. 선 그리기 (글자를 침범하지 않게 원 테두리에서 멈춤)
def draw_arrow(start, end):
dist = np.sqrt((end[0]-start[0])**2 + (end[1]-start[1])**2)
x_start = start[0] + (end[0]-start[0]) * r / dist
y_start = start[1] + (end[1]-start[1]) * r / dist
x_end = end[0] - (end[0]-start[0]) * r / dist
y_end = end[1] - (end[1]-start[1]) * r / dist
ax.annotate('', xy=(x_end, y_end), xytext=(x_start, y_start),
arrowprops=dict(arrowstyle='->', lw=2, color='black', shrinkA=0, shrinkB=0))
draw_arrow(nodes['x1'], nodes['sum'])
draw_arrow(nodes['x2'], nodes['sum'])
draw_arrow(nodes['sum'], nodes['out'])
# 4. 노드 그리기 (속 채우지 않음, 정원형)
for name, pos in nodes.items():
circle = plt.Circle(pos, r, color='black', fill=False, lw=2, zorder=4)
ax.add_artist(circle)
ax.text(pos[0], pos[1], name.upper(), ha='center', va='center',
fontweight='bold', color='black', fontsize=12, zorder=5)
# 5. 텍스트 정보 (검정색)
ax.text(nodes['x1'][0], nodes['x1'][1] + 1.0, f'Input: {x1}', ha='center', fontsize=13)
ax.text(nodes['x2'][0], nodes['x2'][1] - 1.0, f'Input: {x2}', ha='center', fontsize=13)
ax.text(4.5, 3.4, f'w1={w1}', ha='center', fontsize=12)
ax.text(4.5, 1.6, f'w2={w2}', ha='center', fontsize=12)
ax.text(8, 3.8, 'Weighted Sum', ha='center', fontweight='bold', fontsize=13)
ax.text(8, 3.3, f'z = {z:.2f}', ha='center', fontsize=12)
ax.text(8, 1.2, f'Bias (b) = {b}', ha='center', fontsize=12)
# 출력값 결과 박스
ax.text(nodes['out'][0], nodes['out'][1] + 1.0, f'Output (y) = {y}',
ha='center', fontweight='bold', fontsize=14,
bbox=dict(facecolor='none', edgecolor='black', boxstyle='round,pad=0.5'))
plt.axis('off')
# 주변 여백을 최소화하여 꽉 차게 만듦
plt.tight_layout()
plt.show()
# 테스트: 입력 (1, 1) 실행
visualize_perceptron_100(1, 1)
📊 AND 게이트 진리표 (Truth Table)¶
| 입력 1 ($x_1$) | 입력 2 ($x_2$) | 출력 ($y$) |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
In [18]:
import numpy as np
# 1. 데이터셋 준비 (AND Gate)
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 0, 0, 1])
# 2. 하이퍼파라미터 설정
w = np.zeros(2) # 가중치 초기화 [0, 0]
b = 0 # 편향 초기화
lr = 0.1 # 학습률 (Learning Rate)
epochs = 10 # 학습 횟수
# 3. 학습 시작
print(f"{'Epoch':<10} | {'Weights (w1, w2)':<20} | {'Bias (b)':<10}")
print("-" * 50)
for epoch in range(epochs):
for i in range(len(X)):
# 가중합 계산 및 활성화 (Step Function)
z = np.dot(X[i], w) + b
prediction = 1 if z > 0 else 0
# 오차 계산 (실제값 - 예측값)
error = y[i] - prediction
# 가중치 및 편향 업데이트 (델타 규칙)
w += lr * error * X[i]
b += lr * error
# 1번마다 학습 상태 출력
if (epoch + 1) % 1 == 0:
print(f"Epoch {epoch+1:>3} | w:{w} | b:{b:.2f}")
# 4. 최종 결과 확인
print("\n[최종 학습 결과]")
for i in range(len(X)):
z = np.dot(X[i], w) + b
pred = 1 if z > 0 else 0
print(f"입력: {X[i]} -> 예측: {pred} (정답: {y[i]})")
Epoch | Weights (w1, w2) | Bias (b) -------------------------------------------------- Epoch 1 | w:[0.1 0.1] | b:0.10 Epoch 2 | w:[0.2 0.1] | b:0.00 Epoch 3 | w:[0.2 0.1] | b:-0.10 Epoch 4 | w:[0.2 0.2] | b:-0.10 Epoch 5 | w:[0.2 0.1] | b:-0.20 Epoch 6 | w:[0.2 0.1] | b:-0.20 Epoch 7 | w:[0.2 0.1] | b:-0.20 Epoch 8 | w:[0.2 0.1] | b:-0.20 Epoch 9 | w:[0.2 0.1] | b:-0.20 Epoch 10 | w:[0.2 0.1] | b:-0.20 [최종 학습 결과] 입력: [0 0] -> 예측: 0 (정답: 0) 입력: [0 1] -> 예측: 0 (정답: 0) 입력: [1 0] -> 예측: 0 (정답: 0) 입력: [1 1] -> 예측: 1 (정답: 1)
📊 OR 게이트 진리표 (Truth Table)¶
| 입력 1 ($x_1$) | 입력 2 ($x_2$) | 출력 ($y$) |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
In [19]:
import numpy as np
# 1. 데이터셋 준비 (AND Gate)
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 0, 0, 1])
# 2. 하이퍼파라미터 설정
w = np.zeros(2) # 가중치 초기화 [0, 0]
b = 0 # 편향 초기화
lr = 0.1 # 학습률 (Learning Rate)
epochs = 10 # 학습 횟수
# 3. 학습 시작
print(f"{'Epoch':<10} | {'Weights (w1, w2)':<20} | {'Bias (b)':<10}")
print("-" * 50)
for epoch in range(epochs):
for i in range(len(X)):
# 가중합 계산 및 활성화 (Step Function)
z = np.dot(X[i], w) + b
prediction = 1 if z > 0 else 0
# 오차 계산 (실제값 - 예측값)
error = y[i] - prediction
# 가중치 및 편향 업데이트 (델타 규칙)
w += lr * error * X[i]
b += lr * error
# 1번마다 학습 상태 출력
if (epoch + 1) % 1 == 0:
print(f"Epoch {epoch+1:>3} | w:{w} | b:{b:.2f}")
# 4. 최종 결과 확인
print("\n[최종 학습 결과]")
for i in range(len(X)):
z = np.dot(X[i], w) + b
pred = 1 if z > 0 else 0
print(f"입력: {X[i]} -> 예측: {pred} (정답: {y[i]})")
Epoch | Weights (w1, w2) | Bias (b) -------------------------------------------------- Epoch 1 | w:[0.1 0.1] | b:0.10 Epoch 2 | w:[0.2 0.1] | b:0.00 Epoch 3 | w:[0.2 0.1] | b:-0.10 Epoch 4 | w:[0.2 0.2] | b:-0.10 Epoch 5 | w:[0.2 0.1] | b:-0.20 Epoch 6 | w:[0.2 0.1] | b:-0.20 Epoch 7 | w:[0.2 0.1] | b:-0.20 Epoch 8 | w:[0.2 0.1] | b:-0.20 Epoch 9 | w:[0.2 0.1] | b:-0.20 Epoch 10 | w:[0.2 0.1] | b:-0.20 [최종 학습 결과] 입력: [0 0] -> 예측: 0 (정답: 0) 입력: [0 1] -> 예측: 0 (정답: 0) 입력: [1 0] -> 예측: 0 (정답: 0) 입력: [1 1] -> 예측: 1 (정답: 1)
📊 NAND 게이트 진리표 (Truth Table)¶
| 입력 1 ($x_1$) | 입력 2 ($x_2$) | 출력 ($y$) |
|---|---|---|
| 0 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
In [20]:
import numpy as np
# 1. 데이터셋 준비 (NAND Gate)
# 입력은 동일하지만, 정답(y)이 "AND의 반대"로 설정됩니다.
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([1, 1, 1, 0])
# 2. 하이퍼파라미터 설정
w = np.zeros(2) # 가중치 초기화
b = 0.0 # 편향 초기화
lr = 0.1 # 학습률
epochs = 10 # 학습 횟수
# 3. 학습 시작
print(f"{'Epoch':<10} | {'Weights (w1, w2)':<20} | {'Bias (b)':<10}")
print("-" * 50)
for epoch in range(epochs):
for i in range(len(X)):
# 가중합 계산 및 활성화 (Step Function)
z = np.dot(X[i], w) + b
prediction = 1 if z > 0 else 0
# 오차 계산 (실제값 - 예측값)
error = y[i] - prediction
# 가중치 및 편향 업데이트 (델타 규칙)
w += lr * error * X[i]
b += lr * error
# 매 에포크마다 학습 상태 출력
print(f"Epoch {epoch+1:>3} | w:{w} | b:{b:.2f}")
# 4. 최종 결과 확인
print("\n[최종 학습 결과]")
for i in range(len(X)):
z = np.dot(X[i], w) + b
pred = 1 if z > 0 else 0
print(f"입력: {X[i]} -> 예측: {pred} (정답: {y[i]})")
Epoch | Weights (w1, w2) | Bias (b) -------------------------------------------------- Epoch 1 | w:[-0.1 -0.1] | b:0.00 Epoch 2 | w:[-0.2 -0.1] | b:0.10 Epoch 3 | w:[-0.2 -0.1] | b:0.20 Epoch 4 | w:[-0.2 -0.1] | b:0.20 Epoch 5 | w:[-0.2 -0.1] | b:0.20 Epoch 6 | w:[-0.2 -0.1] | b:0.20 Epoch 7 | w:[-0.2 -0.1] | b:0.20 Epoch 8 | w:[-0.2 -0.1] | b:0.20 Epoch 9 | w:[-0.2 -0.1] | b:0.20 Epoch 10 | w:[-0.2 -0.1] | b:0.20 [최종 학습 결과] 입력: [0 0] -> 예측: 1 (정답: 1) 입력: [0 1] -> 예측: 1 (정답: 1) 입력: [1 0] -> 예측: 1 (정답: 1) 입력: [1 1] -> 예측: 0 (정답: 0)
📊 XOR 게이트 진리표 (Truth Table)¶
| 입력 1 ($x_1$) | 입력 2 ($x_2$) | 출력 ($y$) |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
In [21]:
import numpy as np
# 1. 데이터셋 준비 (XOR Gate)
# 입력: (0,0)=0, (0,1)=1, (1,0)=1, (1,1)=0
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
# 2. 하이퍼파라미터 설정
w = np.zeros(2)
b = 0.0
lr = 0.1
epochs = 100 # 100번이나 기회를 줘봅시다.
# 3. 학습 시작
print(f"{'Epoch':<10} | {'Weights (w1, w2)':<20} | {'Bias (b)':<10}")
print("-" * 50)
for epoch in range(epochs):
for i in range(len(X)):
z = np.dot(X[i], w) + b
prediction = 1 if z > 0 else 0
error = y[i] - prediction
w += lr * error * X[i]
b += lr * error
# 10번마다 출력해서 상태를 봅시다.
if (epoch + 1) % 10 == 0:
print(f"Epoch {epoch+1:>3} | w:{w} | b:{b:.2f}")
# 4. 최종 결과 확인
print("\n[최종 학습 결과 - 과연 정답일까?]")
correct_count = 0
for i in range(len(X)):
z = np.dot(X[i], w) + b
pred = 1 if z > 0 else 0
is_correct = "O" if pred == y[i] else "X"
if pred == y[i]: correct_count += 1
print(f"입력: {X[i]} -> 예측: {pred} (정답: {y[i]}) [{is_correct}]")
print(f"\n최종 정확도: {correct_count/4 * 100}%")
Epoch | Weights (w1, w2) | Bias (b) -------------------------------------------------- Epoch 10 | w:[-0.1 0. ] | b:0.10 Epoch 20 | w:[-0.1 0. ] | b:0.10 Epoch 30 | w:[-0.1 0. ] | b:0.10 Epoch 40 | w:[-0.1 0. ] | b:0.10 Epoch 50 | w:[-0.1 0. ] | b:0.10 Epoch 60 | w:[-0.1 0. ] | b:0.10 Epoch 70 | w:[-0.1 0. ] | b:0.10 Epoch 80 | w:[-0.1 0. ] | b:0.10 Epoch 90 | w:[-0.1 0. ] | b:0.10 Epoch 100 | w:[-0.1 0. ] | b:0.10 [최종 학습 결과 - 과연 정답일까?] 입력: [0 0] -> 예측: 1 (정답: 0) [X] 입력: [0 1] -> 예측: 1 (정답: 1) [O] 입력: [1 0] -> 예측: 0 (정답: 1) [X] 입력: [1 1] -> 예측: 0 (정답: 0) [O] 최종 정확도: 50.0%
In [24]:
import numpy as np
import matplotlib.pyplot as plt
def visualize_xor_failure(w, b):
# 1. 데이터 준비
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
# 2. 그래프 설정
plt.figure(figsize=(12, 6), facecolor='white')
ax = plt.gca()
# 배경 색상 영역 (Decision Boundary 영역 표시)
x_min, x_max = -0.5, 1.5
y_min, y_max = -0.5, 1.5
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200), np.linspace(y_min, y_max, 200))
# 퍼셉트론의 예측값 계산 (z = w1x1 + w2x2 + b)
Z = (xx * w[0] + yy * w[1] + b) > 0
# 결정 경계 영역 색칠 (0: 연한 파랑, 1: 연한 주황)
plt.contourf(xx, yy, Z, alpha=0.2, levels=[-1, 0, 1], colors=['#1f77b4', '#ff7f0e'])
# 3. 데이터 포인트 그리기
for i, (point, label) in enumerate(zip(X, y)):
color = '#ff7f0e' if label == 1 else '#1f77b4'
marker = 'D' if label == 1 else 'o'
plt.scatter(point[0], point[1], c=color, s=200, marker=marker,
edgecolor='black', linewidth=2, label=f'Label {label}' if i < 2 else "", zorder=5)
plt.text(point[0], point[1]+0.1, f'({point[0]},{point[1]})', ha='center', fontweight='bold')
# 4. 결정 경계 직선 그리기 (w1x1 + w2x2 + b = 0)
# x2 = -(w1/w2)x1 - (b/w2)
if w[1] != 0:
x_line = np.linspace(x_min, x_max, 10)
y_line = -(w[0] / w[1]) * x_line - (b / w[1])
plt.plot(x_line, y_line, color='red', linestyle='--', lw=3, label='Decision Boundary')
# 그래프 꾸미기
plt.title("XOR Problem: Linear Separation Failure", fontsize=16, fontweight='bold', pad=20)
plt.xlabel("Input X1", fontsize=12)
plt.ylabel("Input X2", fontsize=12)
plt.legend(loc='upper right')
plt.grid(True, linestyle=':', alpha=0.6)
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.tight_layout()
plt.show()
# 진규 님이 학습시킨 마지막 w와 b를 넣어서 실행해보세요!
# 예: visualize_xor_failure(w, b)
# 만약 학습이 아예 안되어 w가 [0,0]이라면 아래 예시 값으로 느낌을 확인해보세요.
visualize_xor_failure(np.array([0.1, -0.1]), -0.05)
활성화 함수(Activation Function) 비교¶
| 활성화 함수 | 수식 | 특징 |
|---|---|---|
| Step Function | $f(x) = \begin{cases} 0 & (x \le 0) \\ 1 & (x > 0) \end{cases}$ | 초창기 퍼셉트론 사용. 미분 불가능으로 현대 학습(역전파)에는 사용 불가 |
| Sigmoid | $f(x) = \frac{1}{1 + e^{-x}}$ | 0~1 압축, 이진 분류에 유용. 기울기 소실(Vanishing Gradient) 문제 있음 |
| Tanh | $f(x) = \tanh(x)$ | -1~1 압축, 출력 평균이 0에 가까워 Sigmoid보다 학습 효율이 좋음 |
| ReLU | $f(x) = \max(0, x)$ | 가장 많이 사용됨. 계산이 빠르고 양수에서 기울기가 유지됨 |
| Leaky ReLU | $f(x) = \max(0.01x, x)$ | ReLU의 단점(음수에서 뉴런이 죽는 현상)을 보완하기 위해 작은 기울기 유지 |
| Softmax | $f(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}$ | 다중 클래스 분류용. 모든 출력의 합이 1이 되어 확률로 해석 가능 |
In [23]:
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
# 데이터 준비
x = torch.linspace(-5, 5, 200)
# 1. 활성화 함수 데이터 준비
activations = [
F.relu(x),
torch.sigmoid(x),
torch.tanh(x),
F.leaky_relu(x, negative_slope=0.1), # 가독성을 위해 slope 약간 조정
F.softmax(torch.stack([x, torch.zeros_like(x)]), dim=0)[0], # 1D 시각화용 수정
F.elu(x, alpha=1.0),
(x > 0).float() # Step Function
]
labels = [
'ReLU', 'Sigmoid', 'Tanh', 'Leaky ReLU', 'Softmax', 'ELU', 'Step Function'
]
# 각 함수별 고유 색상
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#333333']
# 2. 레이아웃 설정 (한 줄에 2개)
n_cols = 2
n_rows = (len(activations) + n_cols - 1) // n_cols
# figsize 가로를 15로 설정하여 너비 꽉 차게
fig, axes = plt.subplots(n_rows, n_cols, figsize=(15, 4 * n_rows), facecolor='white')
axes = axes.flatten()
# 3. 그래프 그리기
for i, ax in enumerate(axes):
if i < len(activations):
# 컬러풀한 선과 영역 채우기(선택사항)
ax.plot(x.numpy(), activations[i].numpy(), color=colors[i], lw=3, label=labels[i])
ax.fill_between(x.numpy(), activations[i].numpy(), color=colors[i], alpha=0.1) # 연한 배경색 추가
ax.set_title(labels[i], fontsize=15, fontweight='bold', color=colors[i], pad=15)
ax.set_xlabel("Input (x)", fontsize=10)
ax.set_ylabel("Output (f(x))", fontsize=10)
ax.grid(True, linestyle=':', alpha=0.7)
# 각 함수별 최적화된 y축 범위
if labels[i] in ['Sigmoid', 'Softmax', 'Step Function']:
ax.set_ylim(-0.1, 1.1)
elif labels[i] == 'Tanh':
ax.set_ylim(-1.1, 1.1)
else:
ax.set_ylim(x.min().item()*0.2, x.max().item())
else:
# 빈 칸 숨기기
ax.axis('off')
plt.tight_layout(pad=5.0)
plt.show()