4-3-1 XOR 문제의 Polynomial Hypothesis 접근법과 Support Vector Machine 기법
수상돌기의 시냅스 영역에서 신경전달물질(neurotransmitter) 분비를 통해 입력 신호 전달과정에서 웨이팅이 일어나며 아울러 여러 개의 수상돌기가 연결된 뉴론에서 시그마(∑)에 의한 가감 합산이 일어난다. 합산된 전위 값은 Threshold 조건 만족 여부에 따라 fire 즉 신경 충격이 일어나게 되며 뉴론에서 외부로 연결된 축색 돌기를 통해 신경 정보 전달이 이루어진다.
뉴럴 네트워크는 이와 같은 기본 구조를 갖추도록 컴퓨터 모델링을 통해 구성되며 그중에서 가장 단순한 모델이 퍼셉트론일 것이다.
뉴론 모델의 출발점에 해당하는 단일 퍼셉트론의 논리적 특성 파악을 위한 AND 및 OR 논리 머신 러닝에서 입력 신호 처리, 웨이팅 그리고 시그마(∑)에 의한 가감 합산처리, Threshold 판단에 따른 firing 과정에 이르기까지 별문제 없이 처리가 가능하였다. 하지만 XOR 논리에 이르러서는 신호가 입력되어도 웨이팅 값이 0 이 되고 바이아스 값이 상수인 0.5로 결정되어 firing 할 것인지 말 것인지 결정이 불가능해진다. (W0 = 0.5, W1 = 0.0, W2 = 0.0)
이런 현상이 발생하는 이유는 무엇일까? 단일 퍼셉트론이라는 조건에 유의해 보자. 선형 회귀 문제뿐만 아니라 AND 와 OR 로직문제를 잘 처리할 수 있다는 사실은 결국 AND 와 OR 문제도 선형 특성이 있다는 점으로 이해할 수 있디. 반면에 XOR 로직에 대한 단일 퍼셉트론에 접근법이 실패한 결과를 낳는 이유는 XOR 로직 특성이 선형적이지 않다는 점일 것이다.
간단히 말하면 XOR 로직을 처리하는데 필요한 웨이트 벡터의 수가 충분치 못해 일어나는 현상으로 볼 수도 있다. 그렇다면 충분한 수의 웨이트 벡터를 사용하면 처리가 가능할 수도 있다는 점이다. 물론 실제 뉴론에서는 생체의 전기화학적인 메카니즘 자체가 전혀 변경될 수는 없으므로 뉴럴 네트워크(NN) 구성에서는 뉴론을 모방한 퍼셉트론 구조를 수정함이 없이 단지 다층화 및 교차연결에 의해서 웨이트 벡터들이 증가된 뉴럴 네트워크를 구성하여 문제 해결을 시도해 왔다.
하지만 수치적으로 데이터를 처리하는 뉴럴 네트워크에서는 반드시 생체내의 전기화학적인 과정을 모방한 퍼셉트론 알고리듬을 반드시 그대로 지켜야 할 이유는 없을 것이다. 즉 뉴럴 네트워크에서는 컴퓨팅 과정에서 웨이트 값을 찾기 위해 랜덤 넘버가 사용되기 때문에 때에 따라서는 생체 뉴론에서는 그럴 일이 없지만 floating point error 현상이 자주 발생하기도 한다. 비록 뉴럴 네트워크가 생체 뉴론을 잘 반영한 모델이긴 하지만 실제 생체 뉴론과 동일하지는 않다고 볼 수 있는 측면이기도 하다. 따라서 단일 퍼셉트론 모델의 XOR 처리 문제에 대해서도 다층화 교차연결 모델만 고려할 것이 아니라 퍼셉트론에서 웨이팅된 신호들의 선형이 아닌 다양한 연산 모델에도 관심을 기울여야 할 필요가 있다.
선형 회귀 문제에서는 입력 값 X 벡터에 대한 적절한 웨이트 벡터 W 와 바이아스 벡터 b 를 결정하는 문제로 귀착된다. 따라서 다음과 같이 선형 hypothesis 함수 f(X)와 벡터를 표현한다.
하지만 이러한 선형 hypothesis로 XOR 로직문제를 처리할 수 없다면 좀 더 웨이트 벡터나 바이아스 벡터를 증가시킨 2차 또는 3차 다항식 hypothesis 모델로 일반화를 시도해 보도록 해보자. 다음의 두 모델은 웨이트 벡터의 수는 그대로지만 바이아스 항이 추가된 상태에서 2차와 3차 다항식 형태를 취하고 있다.
웨이트 벡터들의 값들이 경사하강법에 의한 학습을 통해 결정이 되면 함수 G(X)와 H(X)는 입력 벡터 X 에 대해 각각 2차, 3차식의 형태를 가지게 된다. 즉 선형 문제와 비교하여 차원이 증가된 상태로 볼 수 있다.
한편 다음과 같이 웨이트 벡터와 바이아스를 추가한 다항식 모델도 고려해 보자.
TensorFlow 학습을 위한 데이터 구조는 아무런 변동 없으며 단지 hypothesis 만 바꾼 상태에서 learning_rate 값은 0.05 로 두고 학습 횟수를 500회로 설정하여 최소제곱법 cost 함수를 적용하는quadratic_regression_XOR_bias_01.py 코드를 실행하자.
결과를 관찰해 보면 100회 학습을 통해서도 약 0.2% 정도의 오차로 이미 충분히 수렴된 결과를 보여준다. 하지만 때때로 hypothesis 값들이 0.5가 되는 즉 불완전한 hypothesis 설정에 따라 머신 러닝이 실패하는 경우도 자주 일어난다.
한편 웨이트 벡터와 바이아스를 증가시킨 hypothesis E(X)도 성공적인 계산 결과를 낳는다.
하지만 이 경우의 Hypothesis 곡면은 다음과 같이 위로 볼록한 곡면을 나타내게 된다.
입력 벡터 X에 대한 3차식 형태 Hypothesis인 H(X) 와 2차식 형태 Hypothesis인 E(X)의 3차원 그래픽 처리 결과를 보면 support vector 라고 불리는 (0,0)과 (1,1) 점 데이타들은 (1,0)과 (0,1) 점 데이터와 초록색 평면에 의해서 완전히 분리된 상태가 된다. 즉 평면에서는 분리(classification)가 불가능했던 즉 “not linearly separable” 한 XOR 로직 데이터들이 인위적으로 한 차원 또는 이차원 만큼 높은 형태의 Hypothesis를 사용함에 의해서 높이가 0.5인 threshold 즉 초록색 평면에 의해 정확히 분리가 가능한 상태로 바뀐다. 이 때 사용된 Hypothesis는 예를 들면 E(X)는 두 개의 벡터간의 내적 (inner product) 계산에 해당함을 알 수 있다. 머신 러닝에서 사용되는 이러한 기법을 Support Vector Machine 줄여서 SVM 이라고 한다.
하지만 F(X) 의 경우는 벡터간의 내적 계산 범위를 넘어서므로 SVM 이라 정의하기는 어려우므로 다 뭉뚱그려서 다항식 접근법(Polynomial Approch)으로 표현하도록 하자. 이와 같이 Polynomial 형태를 사용하는 방법 이외에도 XOR 문제를 성공적으로 처리할 수 있는 연산 방법은 다양하게 있을 수 있어 보인다.
머신 러닝에서는 결정해야 할 웨이트와 바이아스의 수가 많아도 학습(trainning)에 따른 컴퓨팅 타임 소모 외에는 별다른 문제가 되지 않는다. 물론 문제의 특수성에 따라서 지나치게 많을 필요는 없지만 충분한 정도의 웨이트와 바이아스를 사용하면 되는 문제이다.
1990년대에 학술적으로 연구되었던 SVM 기법에 관해서는 이미 널리 알려져 있으나 사실 이러한 기법의 명칭이 SVM 인지는 오늘 알게 되었다. 하지만 문헌을 살펴 보면 코드를 사용한 실제의 문제 풀이가 아닌 수학 문제로 접근을 하고 있어 웬만한 수학적 배경이 없으면 거의 이해하가 불가능하다는 생각이 들기도 한다. 한편 이 SVM 문제는 TensorFlow 사용자 측에서는 거의 다루어지지 않는 실정이며 오히려 scikit-learn 라이브러리를 사용하는 통계쪽에서 흔히 언급되고 있다.
#quadratic_regression_XOR_bias_01.py
import tensorflow as tf
tf.set_random_seed(777)
x1_data = [0., 1., 0., 1.]
x2_data = [0.,0.,1.,1.]
y_data = [0., 1., 1., 0.]
#placeholders for a tensor that will be always fed.
x1 = tf.placeholder(tf.float32)
x2 = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)
#w1 = tf.Variable(tf.random_normal([1]), name='weight1')
#w2 = tf.Variable(tf.random_normal([1]), name='weight2')
#b1 = tf.Variable(tf.random_normal([1]), name='bias1')
#b2 = tf.Variable(tf.random_normal([1]), name='bias2')
w1 = tf.Variable(tf.random_uniform([1]), name='weight1')
w2 = tf.Variable(tf.random_uniform([1]), name='weight2')
b0 = tf.Variable(tf.random_uniform([1]), name='bias0')
b1 = tf.Variable(tf.random_uniform([1]), name='bias1')
b2 = tf.Variable(tf.random_uniform([1]), name='bias2')
hypothesis = (x1 * w1 + x2 * w2 + b1)(x1 * w1 + x2 * w2 + b2)
#hypothesis =(x1 * w1 + x2 * w2) (x1 * w1 + x2 * w2 + b1)
#cost/loss function
cost = tf.reduce_mean(tf.square(hypothesis - Y))
#Minimize. Need a very small learning rate for this data set
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.05)
train = optimizer.minimize(cost)
#Launch the graph in a session.
sess = tf.Session()
#Initializes global variables in the graph.
sess.run(tf.global_variables_initializer())
for step in range(5001):
cost_val, hy_val, _ = sess.run([cost, hypothesis, train],
feed_dict={x1: x1_data,x2: x2_data, Y: y_data})
if step % 100 == 0:
print(step, cost_val, hy_val,sess.run(w2),sess.run(b2),sess.run(w1),sess.run(b1))