단순한 Logistic Regression으로는 XOR 문제를 풀 수 없다는 것이 수학적으로 증명되었습니다.
Logistic Regression은 하나의 직선(Decision Boundary)으로 데이터를 나누는데, XOR 데이터는 어떤 직선 하나로도 분리할 수 없기 때문입니다.
x2
↑
1 | o x
|
0 | x o
+--------→ x1
0 1
이 문제를 여러 개의 유닛을 쌓은 Neural Network 로 해결합니다.
전체 코드
import numpy as np
import tensorflow as tf
x_data = [[0, 0], [0, 1], [1, 0], [1, 1]]
y_data = [[0], [1], [1], [0]]
dataset = tf.data.Dataset.from_tensor_slices((x_data, y_data)).batch(len(x_data))
def preprocess_data(features, labels):
features = tf.cast(features, tf.float32)
labels = tf.cast(labels, tf.float32)
return features, labels
W1 = tf.Variable(tf.random.normal([2, 1]), name='weight1')
b1 = tf.Variable(tf.random.normal([1]), name='bias1')
W2 = tf.Variable(tf.random.normal([2, 1]), name='weight2')
b2 = tf.Variable(tf.random.normal([1]), name='bias2')
W3 = tf.Variable(tf.random.normal([2, 1]), name='weight3')
b3 = tf.Variable(tf.random.normal([1]), name='bias3')
def neural_net(features):
layer1 = tf.sigmoid(tf.matmul(features, W1) + b1)
layer2 = tf.sigmoid(tf.matmul(features, W2) + b2)
X = tf.concat([layer1, layer2], -1)
X = tf.reshape(X, shape=[-1, 2])
hypothesis = tf.sigmoid(tf.matmul(X, W3) + b3)
return hypothesis
def loss_fn(hypothesis, labels):
cost = -tf.reduce_mean(labels * tf.math.log(hypothesis) + (1 - labels) * tf.math.log(1 - hypothesis))
return cost
def accuracy_fn(hypothesis, labels):
predicted = tf.cast(hypothesis > 0.5, dtype=tf.float32)
accuracy = tf.reduce_mean(tf.cast(tf.equal(predicted, labels), dtype=tf.float32))
return accuracy
def grad(features, labels):
with tf.GradientTape() as tape:
loss_value = loss_fn(neural_net(features), labels)
return tape.gradient(loss_value, [W1, W2, W3, b1, b2, b3])
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
EPOCHS = 50000
for step in range(EPOCHS):
for features, labels in dataset:
features, labels = preprocess_data(features, labels)
grads = grad(features, labels)
optimizer.apply_gradients(grads_and_vars=zip(grads, [W1, W2, W3, b1, b2, b3]))
if step % 5000 == 0:
print("Iter: {}, Loss: {:.4f}".format(step, loss_fn(neural_net(features), labels)))
x_data, y_data = preprocess_data(features, labels)
test_acc = accuracy_fn(neural_net(x_data), y_data)
print("Testset Accuracy: {:.4f}".format(test_acc))
1. 데이터 준비
x_data = [[0, 0], [0, 1], [1, 0], [1, 1]]
y_data = [[0], [1], [1], [0]]
XOR 진리표입니다. 두 입력이 다를 때만 1, 같을 때는 0이 나옵니다.
x1 x2 XOR
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
2. 데이터 전처리
def preprocess_data(features, labels):
features = tf.cast(features, tf.float32)
labels = tf.cast(labels, tf.float32)
return features, labels
tf.cast 로 정수형 데이터를 float32 로 변환합니다.
TensorFlow 연산은 float32 타입을 기본으로 사용하기 때문에 타입을 맞춰주는 과정입니다.
3. W, b 초기화 : 3개의 유닛
XOR을 풀기 위해 두 개의 유닛으로 구성된 Hidden Layer와 하나의 출력 유닛이 필요합니다.
W1 = tf.Variable(tf.random.normal([2, 1]), name='weight1') # Layer 1 - Unit 1
b1 = tf.Variable(tf.random.normal([1]), name='bias1')
W2 = tf.Variable(tf.random.normal([2, 1]), name='weight2') # Layer 1 - Unit 2
b2 = tf.Variable(tf.random.normal([1]), name='bias2')
W3 = tf.Variable(tf.random.normal([2, 1]), name='weight3') # Layer 2 - 출력
b3 = tf.Variable(tf.random.normal([1]), name='bias3')
입력(x1, x2) → [Unit1: W1,b1] → layer1 ─┐
→ [Unit2: W2,b2] → layer2 ─┤→ [Unit3: W3,b3] → 최종 출력
4. Neural Network 구조
def neural_net(features):
layer1 = tf.sigmoid(tf.matmul(features, W1) + b1) # K1(X)
layer2 = tf.sigmoid(tf.matmul(features, W2) + b2) # K2(X)
X = tf.concat([layer1, layer2], -1) # 두 출력 합치기
X = tf.reshape(X, shape=[-1, 2]) # shape 정리
hypothesis = tf.sigmoid(tf.matmul(X, W3) + b3) # H(X)
return hypothesis
Neural Network의 수식은 다음과 같습니다:
K1(X) = sigmoid(XW1 + b1) ← Hidden Layer Unit 1
K2(X) = sigmoid(XW2 + b2) ← Hidden Layer Unit 2
H(X) = sigmoid([K1, K2] * W3 + b3) ← 출력층
x → Linear 연산 → Sigmoid 를 레이어마다 반복하는 구조입니다.
tf.concat([layer1, layer2], -1) 는 두 유닛의 출력을 하나로 합치는 부분입니다.
layer1 = [[0.2], [0.8], [0.7], [0.3]] → shape: [4, 1]
layer2 = [[0.9], [0.1], [0.2], [0.8]] → shape: [4, 1]
tf.concat → [[0.2, 0.9],
[0.8, 0.1],
[0.7, 0.2],
[0.3, 0.8]] → shape: [4, 2]
이렇게 합쳐진 값이 W3의 입력이 됩니다.
tf.reshape(X, shape=[-1, 2]) 는 shape를 [4, 2] 로 명확하게 맞춰주는 것입니다.
-1 은 "데이터 수는 자동으로 계산해줘" 라는 의미입니다.
5. Cost 함수 : Cross-Entropy
def loss_fn(hypothesis, labels):
cost = -tf.reduce_mean(labels * tf.math.log(hypothesis) + (1 - labels) * tf.math.log(1 - hypothesis))
return cost
가설과 실제값의 차이를 측정하는 비용 함수입니다.
cost = -y * log(H(x)) - (1-y) * log(1 - H(x))
단순히 제곱을 쓰면 구불구불한 그래프가 나오기 때문에 log 함수를 사용해 Convex(볼록)한 구조로 만들어줍니다.
예측이 맞으면 cost → 0, 틀리면 cost → ∞ 가 되어 틀린 예측에 큰 패널티를 줍니다.
6. 역전파 : GradientTape
def grad(features, labels):
with tf.GradientTape() as tape:
loss_value = loss_fn(neural_net(features), labels)
return tape.gradient(loss_value, [W1, W2, W3, b1, b2, b3])
뉴럴 네트워크가 복잡해지면서 최종 결과에 대한 미분값(기울기)을 구하기가 어려워졌는데, 역전파(Backpropagation) 알고리즘이 이 문제를 해결했습니다.
출력값의 오류를 역방향으로 전파하며 Chain Rule을 적용해 미분값을 계산합니다.
출력(loss)
↓ 역전파 시작
W3, b3 미분값 계산
↓ Chain Rule
W1, W2, b1, b2 미분값 계산
- GradientTape 가 forward 연산을 기록합니다
- tape.gradient 가 역방향으로 Chain Rule을 적용해 W1, W2, W3, b1, b2, b3 모두의 미분값을 한번에 계산합니다
W, b가 6개로 늘어났지만 tape.gradient 하나로 모두 처리됩니다.
7. 정확도 계산
def accuracy_fn(hypothesis, labels):
predicted = tf.cast(hypothesis > 0.5, dtype=tf.float32)
accuracy = tf.reduce_mean(tf.cast(tf.equal(predicted, labels), dtype=tf.float32))
return accuracy
sigmoid 출력값을 0.5 기준(Decision Boundary)으로 0 또는 1로 변환합니다.
출력값 >= 0.5 → 1
출력값 < 0.5 → 0
그 후 실제 정답과 비교해 정확도를 계산합니다.
학습 결과
Iter: 0, Loss: 0.7345
Iter: 5000, Loss: 0.6899
Iter: 10000, Loss: 0.6827
Iter: 15000, Loss: 0.6608
Iter: 20000, Loss: 0.6107
Iter: 25000, Loss: 0.5522
Iter: 30000, Loss: 0.5005
Iter: 35000, Loss: 0.4406
Iter: 40000, Loss: 0.2840
Iter: 45000, Loss: 0.1394
Testset Accuracy: 1.0000
50000번 학습 후 정확도 100% 로 XOR 문제를 완벽하게 풀었습니다.
단일 Logistic Regression으로는 불가능했던 문제를 Neural Network로 해결한 것입니다.
전체 흐름 정리
XOR 데이터 입력 (x1, x2)
↓
float32로 타입 변환 (preprocess_data)
↓
Layer 1
layer1 = sigmoid(XW1 + b1)
layer2 = sigmoid(XW2 + b2)
↓
tf.concat([layer1, layer2]) → shape [4, 2]
↓
Layer 2
hypothesis = sigmoid(XW3 + b3)
↓
Cross-Entropy Cost 계산
↓
GradientTape → 역전파로 W1,W2,W3,b1,b2,b3 미분값 한번에 계산
↓
optimizer.apply_gradients → W, b 업데이트
↓
50000번 반복
↓
Accuracy: 100% ✅
정리
개념 설명 코드
| preprocess_data | 데이터 타입을 float32로 변환 | tf.cast(features, tf.float32) |
| tf.concat | 두 레이어 출력을 하나로 합치기 | tf.concat([layer1, layer2], -1) |
| tf.reshape | shape를 원하는 형태로 변환 | tf.reshape(X, [-1, 2]) |
| Cross-Entropy | 맞으면 cost=0, 틀리면 cost=∞ | -y*log(H(x)) - (1-y)*log(1-H(x)) |
| 역전파 | Chain Rule로 모든 W,b 미분값 계산 | tape.gradient(loss, [W1,W2,W3,...]) |
| Decision Boundary | 0.5 기준으로 0 또는 1로 분류 | hypothesis > 0.5 |
단일 Logistic Regression으로는 불가능하던 XOR 문제를 Neural Network와 역전파로 완벽하게 해결했습니다. 🚀
'AI > ML' 카테고리의 다른 글
| ReLU로 MNIST 손글씨 분류하기 (TensorFlow) (0) | 2026.03.23 |
|---|---|
| ReLU : Vanishing Gradient 문제와 해결책 (0) | 2026.03.22 |
| Backpropagation : 역전파 알고리즘 (0) | 2026.03.21 |
| XOR 문제와 Neural Network : 딥러닝의 시작 (0) | 2026.03.19 |
| Overfitting : 과적합 이해하고 해결하기 (0) | 2026.03.13 |