ML04. XOR문제와 단층퍼셉트론의 한계
개요
이전 글에서 다뤘던 Logistic Regression은 선형 결정 경계(직선 하나)로 두 클래스를 나누는 모델이었다.
그렇다면 아래처럼 직선 하나로는 절대 나눌 수 없는 데이터는 어떻게 해야 할까?
| x1 | x2 | y (XOR) |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 |
0 |
이것이 바로 유명한 XOR 문제다.
XOR(배타적 논리합)은 두 입력이 다를 때 1(참), 같을 때 0(거짓)을 출력한다.
1. XOR 문제
미리 말하자면 XOR 모델의 학습결과는 아래와 같이 결정경계가 분리되지 않는다
|
| [XOR 단층퍼셉트론 학습결과] |
- 그림에서 파란색 O점은 (0,1), (1,0) 일때 '참'을 의미하며
- 그림에서 오렌지색 X는 (0,0), (1,1) 일때 '거짓'을 뜻한다.
이 글에서는 XOR 문제를 통해 Logistic Regression의 한계를 확인하고, numpy로 직접 구현하여 결과를 시각화한다.
보다 머신러닝관점에서 표현하자면 이는 단층 퍼셉트론 (Single Layer Perceptron)의 한계이다.
아마도 제대로 학습된 XOR의 결정경계는 아래와 같을 것이다. 아름답다~😀
|
| [XOR 다층퍼셉트론 학습결과] |
2. XOR이 선형 분리 불가능한 이유
Logistic Regression은 결정 경계로 직선(초평면)을 사용한다.
혹시 "시그모이드는 비선형이니 결정경계도 곡선이 아닌가?" 라고 생각할 수도 있지만,
로지스틱 회귀에서 시그모이드 함수는 비선형 함수지만, 결정 경계(decision boundary)는 선형이다.
그 이유는 시그모이드 함수의 입력 $z$ 가 선형결합의 결과이기 때문이다.
$$
z = w_1 x_1 + w_2 x_2 + b
$$
$$
\sigma(z)
=
\frac{1}{1 + e^{-z}}
$$
$$
\hat{y} = \sigma(z)
$$
즉 선형결합의 결과($z$)를 단지 시그모이드 함수에 넣어 0~1의 확률분포로 바꾸는 것이므로 결정경계는 직선이다.
아래 좌표를 보자.
| 좌표 | y |
|---|---|
| (0,0) | 0 |
| (0,1) | 1 |
| (1,0) | 1 |
| (1,1) | 0 |
y=1인 점은 (0,1), (1,0) — 대각선 방향
y=0인 점은 (0,0), (1,1) — 반대 대각선 방향
어떤 직선을 그어도 이 두 그룹을 완전히 분리할 수 없다.
이를 선형 분리 불가능(Linearly Inseparable) 이라고 한다.
3. 그래도 학습하면 어떻게 될까?
XOR 데이터셋을 Logistic Regression으로 그냥 학습시키면 어떻게 될까?
실제로 돌려보면 Cost가 줄어들지 않고 수렴도 하지 않는다.
학습이 끝나도 결정 경계는 직선이라서,
어느 방향으로 그어도 정답 분류가 불가능하다.
예측 결과도 모두 0.5 근방에 머무는 걸 확인할 수 있다.
4. Sigmoid 함수
Logistic Regression에서 확률로 변환할 때 사용하는 함수다.
$$
\sigma(z)=\frac{1}{1+e^{-z}}
$$
입력값 $z$의 범위는 음의 무한대에서 양의 무한대이지만,
$$
z \in (-\infty, +\infty)
$$
출력은 항상 0~1 사이다.
$$
\sigma(z) \in (0,1)
$$
$z$=0이면 출력은 0.5, $z$가 클수록 1에 가까워지고, $z$가 작을수록 0에 가까워진다.
코드로는 아래와 같이 구현한다.
def sigmoid(z):
return 1/(1+np.exp(-z))
5. Cost Function
이진 분류에서는 이미 이전글에서 설명한 Binary Cross Entropy를 사용한다.
$$
J(W,b)
=
-\frac{1}{m}
\sum_{i=1}^{m}
\left[
y^{(i)}\log(\hat
y^{(i)})
+
(1-y^{(i)})
\log(1-\hat y^{(i)})
\right]
$$
$y$=1이면 앞 항만 남고, $y$=0이면 뒷 항만 남는다. 😀
즉, 정답 클래스의 확률을 높이도록 학습된다.
수식이 복잡해 보이지만 로그함수 그래프를 그려보면 바로 이해된다.
(붉은색은 $y$=1일때, 녹색은 $y$=0 일때 손실함수, BCE는 둘의 합체)
|
| [desmos에서 그린 로그손실함수] |
코드로 구현하면 아래와 같다.
def compute_cost(X, y, W, b):
m, n = X.shape
z = np.dot(X, W) + b
y_hat = sigmoid(z)
L = -y*np.log(y_hat) - (1-y)*np.log(1-y_hat)
cost = np.sum(L) / m
return cost
6. Gradient Descent,경사하강법
Sigmoid + Cross Entropy 조합은 미분이 깔끔하게 정리된다.
- 오차(error)
$$
err = \hat y - y
$$
- Weight gradient
$$
\frac{\partial J}{\partial W}
=
\frac{1}{m}
X^T err
$$
- Bias gradient
$$
\frac{\partial J}{\partial b}
=
\frac{1}{m}
\sum err
$$
- Gradient Descent Update (Weight)
$$
W
=
W
-
\alpha
\frac{\partial J}{\partial
W}
$$
- Gradient Descent Update (Bias)
$$
b
=
b
-
\alpha
\frac{\partial J}{\partial
b}
$$
코드로는 경사하강, 업데이트 각 두개의 함수로 나누어 구현한다.
def compute_gradient(X, y, W, b):
m, n = X.shape
z = np.dot(X, W) + b
y_hat = sigmoid(z)
err = y_hat - y
dj_dw = np.dot(X.T, err) / m
dj_db = np.sum(err) / m
return dj_dw, dj_db
def gradient_descent(X, y, W, b, epochs, lr):
for i in range(epochs):
dj_dw, dj_db = compute_gradient(X, y, W, b)
W = W - lr * dj_dw
b = b - lr * dj_db
if i%100==0:
cost = compute_cost(X, y, W, b)
print(f'epochs={i}, W={np.round(W, 3)}, b={b:.3f}, cost={cost:.3f}')
return W, b
7. 전체 구현
아래 예제는 ML Lib. 사용을 지양하고 수식과 전개과정 이해를 위해 numpy로 작성.
(개념공부 시 PyTorch, TensorFlow 사용 X 추천)
두 파일을 같은 위치에 두고 main.py를 실행.
main.py
import numpy as np
X = np.array([
[0,0],
[0,1],
[1,0],
[1,1]
])
y = np.array([0, 1, 1, 0])
m,n = X.shape
W = np.random.randn(n)
b = 0
epochs = 5000
lr = 0.01
from logistic_func import gradient_descent, sigmoid
W_final, b_final = gradient_descent(X, y, W, b, epochs, lr)
# predict
def predict(X, W, b):
z = np.dot(X, W) + b
return sigmoid(z)
xx, yy = np.meshgrid( np.arange(-1, 2, 0.1), np.arange(-1, 2, 0.1) ) # (30, 30), (30, 30)
X_test = np.c_[xx.ravel(), yy.ravel()] # (900,) (900,)
print(X_test.shape)
y_pred = predict(X_test, W_final, b_final)
y_grid = y_pred.reshape(xx.shape)
import matplotlib.pyplot as plt
plt.contourf(xx, yy, y_grid, levels=50, alpha=0.6, cmap='RdBu')
plt.contour(xx, yy, y_grid, levels=[0.5])
plt.scatter([0, 1], [1, 0], marker='o', s=100, label='True:1')
plt.scatter([0, 1], [0, 1], marker='x', s=100, label='False:0')
plt.legend()
plt.show()
logistic_func.py
import numpy as np
# X (m, n)
# y (m, )
# W (n, )
# z (m, )
def sigmoid(z):
return 1/(1+np.exp(-z))
def compute_cost(X, y, W, b):
m, n = X.shape
z = np.dot(X, W) + b
y_hat = sigmoid(z)
L = -y*np.log(y_hat) - (1-y)*np.log(1-y_hat)
cost = np.sum(L) / m
return cost
def compute_gradient(X, y, W, b):
m, n = X.shape
z = np.dot(X, W) + b
y_hat = sigmoid(z)
err = y_hat - y
dj_dw = np.dot(X.T, err) / m
dj_db = np.sum(err) / m
return dj_dw, dj_db
def gradient_descent(X, y, W, b, epochs, lr):
for i in range(epochs):
dj_dw, dj_db = compute_gradient(X, y, W, b)
W = W - lr * dj_dw
b = b - lr * dj_db
if i%100==0:
cost = compute_cost(X, y, W, b)
print(f'epochs={i}, W={np.round(W, 3)}, b={b:.3f}, cost={cost:.3f}')
return W, b
8. 실행 결과 분석
학습을 5000번 반복해도 Cost가 0.693 근처에서 수렴하지 못하고 맴돌게 된다.
|
| [학습중 가중치, 편향, 비용변화] |
$-\log(0.5)=\log(2)=\ln(2)\approx0.693$
으로, 이는 완전히 랜덤하게 찍는 것과 같은 수준이다.
즉, 모델이 아무것도 학습하지 못했음을 의미한다.
시각화 결과를 보면 결정 경계(등고선)가 직선 형태로,
O마커(y=1) 와 X마커(y=0) 를 나눌 수가 없다.
9. XOR 해결 방법 — 힌트
XOR 문제를 해결하려면 비선형 결정 경계가 필요하다.
이를 위한 방법이 바로 신경망(Neural Network) 이다.
은닉층(Hidden Layer)을 추가하면 비선형 변환이 가능해지고,
XOR처럼 선형 분리 불가능한 문제도 해결할 수 있다.
Logistic Regression : 은닉층 없음, 선형 결정 경계 ($x$→$y$)
Neural Network (2층) : 은닉층 1개, 비선형 결정 경계 ($x$→$h$→$y$)
다음 글에서 은닉층을 추가한 신경망으로 XOR 문제를 해결해보겠다.
10. 정리
XOR 문제는
- Logistic Regression으로 풀 수 없는 대표적 예시
- 선형 분리 불가능(Linearly Inseparable) 데이터
- Cost가 0.693 에 수렴하면 학습 실패 신호
- 해결책 : 은닉층(Hidden Layer) 추가 → Neural Network
딥러닝의 출발점이 바로 이 XOR 문제의 해결에서 시작되었다.
1969년 Minsky & Papert가 퍼셉트론의 한계로 XOR 불가능을 증명하였고,
이후 다층 퍼셉트론(MLP)의 등장으로 해결되었다.
감사합니다.





댓글
댓글 쓰기