ML05. XOR 문제 해결, 논리게이트 조합과 다층 퍼셉트론(MLP)

개요

이전 글에서 XOR 문제단층 퍼셉트론(Logistic Regression)으로 풀 수 없다는 것을 확인했다.

직선 하나로는 아래 네 점을 절대 두 그룹으로 나눌 수 없기 때문이다.

x1 x2 y (XOR)
0 0 0
0 1 1
1 0 1
1 1 0


이번 글에서는 XOR 문제를 두 가지 방법으로 직접 해결해본다.

우선, 논리게이트(OR, AND)를 직접 조합해 XOR 해결의 실마리를,

그리고 은닉층 (Hidden Layer)이 있는 다층 퍼셉트론(MLP)으로 학습


1. XOR을 논리게이트로 분해하기

먼저 수학적으로 XOR을 이해하자.

XOR은 아래처럼 OR 게이트와 AND 게이트의 조합으로 표현할 수 있다.


$$
\begin{aligned}
\text{XOR}(x_1, x_2)
&= \text{OR}(x_1, x_2) 
&\;\;-\; \text{AND}(x_1, x_2)
\end{aligned}
$$

  • $x1$, $x2$를 $OR$   연산한 결과가 $h1$이고
  • $x1$, $x2$를 $AND$ 연산한 결과는 $h2$라면
  • $XOR$ = $h1-h2$
  • 즉, $h1$ $AND$ ($NOT$ $h2$) 이다

잘 이해되지 않는다면 논리식을 건너뛰어 아래 진리표를 보고 다시 돌아와 이해하자.

다시 풀어쓰면

$$
y = h_1 - h_2
$$


$$
h_1 = \text{OR}(x_1, x_2) = \begin{cases} 1 & x_1 + x_2 > 0.5 \\ 0 & \text{otherwise} \end{cases}
$$


$$
h_2 = \text{AND}(x_1, x_2) = \begin{cases} 1 & x_1 + x_2 > 1.5 \\ 0 & \text{otherwise} \end{cases}
$$

$h_1$(OR)에서 $h_2$(AND)를 빼면 정확히 XOR이 된다.


진리표로 확인해보자.

x1 x2 h1 (OR) h2 (AND) y = h1 - h2
0 0 0 0 0
0 1 1 0 1
1 0 1 0 1
1 1 1 1 0


위 과정을 파이썬 코드로 단순하게 작성해 보았다.

예제 1. 논리게이트 조합으로 XOR구현

import numpy as np

X = np.array([
    [0,0],
    [0,1],
    [1,0],
    [1,1]
])

y = np.array([0, 1, 1, 0])

h1 = (X[:,0] + X[:, 1] > 0.5).astype(int) # OR
h2 = (X[:,0] + X[:, 1] > 1.5).astype(int) # AND

print(f'h1={h1}')
print(f'h2={h2}')

h3 = h1 - h2
print(f'h3={h3} (XOR)')


위 코드의 실행결과는 아래와 같다.

[논리게이트로 구현한 XOR]

출력 $y$(h3)는 정답 XOR와 [0, 1, 1, 0] 와 완전히 일치한다.


그렇다면 위 예제 1의 의미를 신경망(Neural Network) 관점에서 살펴보자.

이 논리게이트 조합은 사실 아래처럼 2층 신경망 구조 와 동일하다.

(일반적으로 신경망에서 입력층은 레이어수에 포함하지 않는다.)



입력층 ($x1$, $x2$)

      ↓

은닉층 (h1=OR, h2=AND)   ← 숨겨진 표현 공간

      ↓

출력층 ($y$ = h1 - h2)



즉, 단층으로 불가능했던 XOR이 은닉층(Hidden Layer) 하나만 추가해도 해결된다는 것이다.

이것이 바로 다층 퍼셉트론(Multi-Layer Perceptron)의 핵심 아이디어다.


위 예제는 단순히 손으로 OR, AND 임계값을 설계한 것이지만,

다음 예제에서는 이 가중치를 기계학습을 통해 자동으로 찾게 하는것을 목표로 한다.


2. 다층 퍼셉트론(MLP) 구조와

이제 가중치를 직접 설계하지 않고, 순전파(Forward) ,역전파(Back Propagation)를 통해 자동으로 학습시켜보자.

은닉층을 추가한 다층 신경망의 구조는 아래와 같다.

(히든 레이어의 뉴런 수는 임의로 8개로 정의, 변경가능)

[클로드로 작성한 신경망구조]

아래 () 내부 숫자는 데이터의 차원을 의미한다.

즉 입력 $X$ (4,2) 는 차원이 4행 2열의 행렬(Matrix)로 구성되었음을 뜻한다.


입력층                은닉층                        출력층

$X\;(4,2)
\rightarrow
Z_1
\rightarrow
A_1\;(4,8)
\rightarrow
Z_2
\rightarrow
\hat{y}\;(4,1)$

  • 입력층($X$) : 2개 노드 (x1, x2)
  • 은닉층(선형결합($Z1$), 활성화함수($A1$)) : 8개 노드 (뉴런 수는 조절 가능)
  • 출력층(선형결합($Z2$), 활성화함수($\hat{y}$) : 1개 노드 (0~1 확률)
  • 활성화함수 : 은닉층, 출력층 모두 Sigmoid


이제 위 과정(ML, 기계학습)을 하나씩 살펴보자.


3. 순전파(Forward Propagation) 과정

입력→ 은닉→ 출력으로 이어지는 과정을 순전파라고 하며 결과는 $\hat{y}$ 이다.


3.1 입력 → 은닉층으로

먼저 입력($X$)과 가중치($W_1$)를 곱하고 편향($b_1$)을 더함 (선형결합)

$$
Z_1 = X \cdot W_1 + b_1
$$

선형결합의 결과($Z_1$)를 시그모이드함수에 넣어 확률로 변환 (활성화)

$$
A_1 = \sigma(Z_1)
$$

∴$X$의 차원은 $(4, 2)$, $W_1$은 $(2, 8)$이므로 $Z_1$, $A_1$은 $(4, 8)$이 된다.

(4행2열의 행렬과 2행8열의 형렬 내적(Dot)결과는 4행8열)


3.2 은닉 → 출력층으로

위 과정과 동일하나 입력이 $X$가 아닌 은닉층의 출력인 $A_1$ (선형결합)

$$
Z_2 = A_1 \cdot W_2 + b_2
$$

선형결합의 결과($Z_2$)를 시그모이드함수에 넣어 확률로 변환 (활성화)

$$
\hat{y} = \sigma(Z_2)
$$

∴$A_1$은 $(4, 8)$, $W_2$는 $(8, 1)$이므로 $\hat{y}$는 $(4, 1)$이 된다.

여기까지($\hat{y}$ 얻기)의 과정을 머신러닝에서 순전파라고 한다.


4. 시그모이드(활성화) 함수

선형결합의 결과($z$)는 양의 무한대~음의 무한대 값을 가진다.

$z \in (-\infty, \infty)$

이 값을 0~1사이 확률분포로 바꾸어주는 활성화 함수

$$
\sigma(z) = \frac{1}{1 + e^{-z}}
$$

이전 글에서 자세히 설명되어 있으므로 자세한 내용은 생략.


5. 손실(Loss), 비용함수(Cost Function)

이진 분류에는 Binary Cross Entropy를 사용한다.

  • 손실함수(Loss)

$$L(y, \hat{y}) = -[y \log(\hat{y}) + (1 - y) \log(1 - \hat{y})]$$

(여기서 $y$는 실제 라벨, $\hat{y}$는 모델의 예측값 $H(\mathbf{x})$)


  • 비용함수(Cost, J)
$$\begin{aligned} J(\mathbf{w}, b) = -\frac{1}{m} \sum_{i=1}^{m} \big[ &y^{(i)} \log(\hat{y}^{(i)}) \\ & + (1 - y^{(i)}) \log(1 - \hat{y}^{(i)}) \big] \end{aligned}$$


$y=1$이면 앞 항만, $y=0$이면 뒷 항만 살아남는다. 정답 클래스의 확률을 높이도록 유도한다.


6. 역전파(Back Propagation) 과정

순전파 과정이 현재의 가중치($W$)와 편향($b$)으로 예측값($\hat{y}$)을 얻는 과정이라면,

이제 예측값($\hat{y}$)과 실제값($y$)의 차이인 오차를 계산해야 한다.


그리고 이 오차가 어떤 경로를 거쳐 발생했는지 추적하기 위해

활성화 함수의 미분값을 이용하여 출력층에서 입력층 방향으로 거꾸로 전파한다.

이렇게 각 가중치($W$)와 편향($b$)가 오차에 얼마나 기여했는지 계산할 수 있으며, 이를 이용해 모델을 학습시킨다.


모델이 학습된다는 말은 가중치($W$)와 편향($b$)이 업데이트되는 것이다.

위 과정이 바로 역전파이다.


6.1 출력층 오차계산

예측값과 실제값의 차이를 계산한다.

$$
err = \hat{y} - y
$$


시그모이드 활성화 함수와 Binary Cross Entropy를 함께 사용하면, 출력층의 오차항은 다음과 같이 간단한 형태로 정리된다.

$$
\delta_2 = \frac{err}{m}
$$

여기서 m은 학습 데이터의 개수이다.


6.2 출력층 가중치 업데이트

출력층의 오차를 이용하여 가중치와 편향의 기울기를 계산한다.

$$
W_2 = W_2 - \alpha \cdot A_1^T \cdot \delta_2
$$


$$
b_2 = b_2 - \alpha \cdot \sum \delta_2
$$

여기서 α는 학습률(Learning Rate)이다.


6.3 은닉층으로 오차 전파

출력층의 오차를 은닉층으로 전달한다.

$$
\delta_1 = (\delta_2 \cdot W_2^T) \odot A_1 (1 - A_1)
$$

여기서 $\odot$는 원소별 곱(element-wise product)을 의미한다.

은닉층의 활성화 함수로 시그모이드(Sigmoid)를 사용하므로, 역전파 과정에서 시그모이드의 미분값이 필요하다.

$$\sigma'(z)=\sigma(z)(1-\sigma(z))$$

또한 순전파에서 

$$
A_{1} = \sigma(Z_{1})
$$

이므로

$$\sigma'(Z_1)=A_1(1-A_1)$$

를 사용할 수 있다.


6.4 은닉층 가중치 업데이트

계산된 δ1을 이용하여 첫 번째 층의 가중치와 편향을 업데이트한다.

$$
W_1 = W_1 - \alpha \cdot X^T \cdot \delta_1
$$
$$
b_1 = b_1 - \alpha \cdot \sum \delta_1
$$

이제 업데이트된 가중치, 편향을 이용 다시 순전파를 진행한다.

이것의 머신러닝의 원리이다.


위에서 설명한 다층퍼셉트론 신경망을 순수파이썬으로 구현한 예제이다.

예제 2 MLP로 XOR 학습

import numpy as np

X = np.array([
    [0,0],
    [0,1],
    [1,0],
    [1,1]
])

y = np.array([0, 1, 1, 0]).reshape(-1, 1)

m,n = X.shape
neuron = 8

W1 = np.random.randn(n, neuron)
b1 = np.zeros(neuron)

W2 = np.random.randn(neuron, 1)
b2 = np.zeros(1)

epochs = 10000
lr = 0.1

def sigmoid(z):
    return 1/(1+np.exp(-z))

for i in range(epochs):
    # forward
    z1 = np.dot(X, W1) + b1 # X:(4,2)•(2,4) = (4, 4)
    a1 = sigmoid(z1) # (4, 4)

    z2 = np.dot(a1, W2) + b2
    y_hat = sigmoid(z2)

    # cost
    L = -y*np.log(y_hat) - (1-y)*np.log(1-y_hat)
    cost = np.sum(L) / m

    # backward : cost -> L -> y_hat -> z2
    err = y_hat - y
    d2  = err / m

    # z2 = a1•W2+b2
    W2 = W2 - lr * np.dot(a1.T, d2)
    b2 = b2 - lr * np.sum(d2, axis=0)

    # z2 -> a1 -> z1
    d1 = np.dot(d2, W2.T) * a1*(1-a1)
    # z1 = X·W1+b1
    W1 = W1 - lr * np.dot(X.T, d1)
    b1 = b1 - lr * np.sum(d1, axis=0)

    if i%100==0:
        print(f'epoch={i:4d}, cost={cost:.3f} ')

# 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)



hidden = predict(X_test, W1, b1)
out    = predict(hidden, W2, b2)

y_grid = out.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()

코드의 학습이 진행되며 순전파, 역전파가 일어나며,

비용이 줄고 학습이 이루어짐을 확인할 수 있다.

[다층퍼셉트론으로 구현한 XOR]

XOR를 다층신경망으로 학습한 결과의 시각화는 아래와 같다.

[다층신경망으로 구현한 XOR 시각화]






댓글

이 블로그의 인기 게시물

Qt Designer 설치하기

파이썬을 활용한 PID 제어기 GUI 구현

Android 15 앱 UI 겹침이슈 해결방법 및 원인분석