Adventure Time - Finn 3
본문 바로가기
AI/ML

XOR 문제 Neural Network로 구현하기 (TensorFlow)

by hyun9_9 2026. 3. 21.

단순한 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와 역전파로 완벽하게 해결했습니다. 🚀