Single Layer Perceptron (SLP)이란?¶

Single Layer Perceptron은 가장 기본적인 인공신경망 모델입니다.

특징¶

  • 구조: 입력층 → 출력층 (단 2개 레이어)
  • 용도: 선형 분류(Linear Classification) 문제 해결
  • 활성화 함수: Step 함수 또는 Sigmoid 함수 사용

수식¶

$$y = f(w \cdot x + b)$$

  • $w$: 가중치 벡터
  • $x$: 입력 벡터
  • $b$: 편향(Bias)
  • $f$: 활성화 함수

학습 방식¶

  1. 초기 가중치와 편향 설정
  2. 각 샘플에 대해 예측 수행
  3. 오류 계산 (실제값 - 예측값)
  4. 오류에 따라 가중치 업데이트: $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)
No description has been provided for this image

📊 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)
No description has been provided for this image

활성화 함수(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()
No description has been provided for this image