12. TensorFlow로 Logistic Classification의 구현하기 (lab 05)

개인적으로는 코딩이 훨씬 쉽고 개념 정리하는 것도 쉽게 느껴진다. 힘들게 힘들게 Logistic Regression에 대해 이론적인 내용을 정리했으니, 텐서플로우로 결과를 볼 때가 됐다.


앞의 글에서 엄청난 설명을 했던 공식들이다. 복잡하긴 한데, 앞의 글을 읽었다면 그래도 좀 알 것 같은 느낌이 들어야 하지 않을까?


아래는 이번 글에서 사용한 소스코드 전체이다.  04train.txt

import tensorflow as tf
import numpy as np

# 04train.txt
# #x0 x1 x2 y
# 1 2 1 0
# 1 3 2 0
# 1 3 5 0
# 1 5 5 1
# 1 7 5 1
# 1 2 5 1

# 원본 파일은 6행 4열이지만, 열 우선이라서 4행 6열로 가져옴
xy = np.loadtxt('04train.txt', unpack=True, dtype='float32')

# print(xy[0], xy[-1]) # [ 1. 1. 1. 1. 1. 1.] [ 0. 0. 0. 1. 1. 1.]

x_data = xy[:-1] # 3행 6열
y_data = xy[-1] # 1행 6열

X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)

# feature별 가중치를 난수로 초기화. feature는 bias 포함해서 3개. 1행 3열.
W = tf.Variable(tf.random_uniform([1, len(x_data)], -1.0, 1.0))

# 행렬 곱셈. (1x3) * (3x6)
h = tf.matmul(W, X)
hypothesis = tf.div(1., 1. + tf.exp(-h)) # exp(-h) = e ** -h. e는 자연상수

# exp()에는 실수만 전달
# print(tf.exp([1., 2., 3.]).eval()) # [2.71828175 7.38905621 20.08553696]
# print(tf.exp([-1., -2., -3.]).eval()) # [0.36787945 0.13533528 0.04978707]

cost = -tf.reduce_mean(Y * tf.log(hypothesis) + (1 - Y) * tf.log(1 - hypothesis))

rate = tf.Variable(0.1)
optimizer = tf.train.GradientDescentOptimizer(rate)
train = optimizer.minimize(cost)

init = tf.initialize_all_variables()

sess = tf.Session()
sess.run(init)

for step in range(2001):
sess.run(train, feed_dict={X: x_data, Y: y_data})
if step % 20 == 0:
print(step, sess.run(cost, feed_dict={X: x_data, Y: y_data}), sess.run(W))

print('-----------------------------------------')

# 결과가 0 또는 1로 계산되는 것이 아니라 0과 1 사이의 값으로 나오기 때문에 True/False는 직접 판단
print('[1, 2, 2] :', sess.run(hypothesis, feed_dict={X: [[1], [2], [2]]}) > 0.5)
print('[1, 5, 5] :', sess.run(hypothesis, feed_dict={X: [[1], [5], [5]]}) > 0.5)
print('[1, 4, 2] [1, 0, 10] :', end=' ')
print(sess.run(hypothesis, feed_dict={X: [[1, 1], [4, 0], [2, 10]]}) > 0.5)
sess.close()
[출력 결과]
...
1920 0.340956 [[-5.85679626  0.47247875  1.018206  ]]
1940 0.340727 [[-5.87786055  0.47344807  1.02187181]]
1960 0.340503 [[-5.8986907   0.47439972  1.02550077]]
1980 0.340284 [[-5.91929054  0.4753342   1.02909374]]
2000 0.340069 [[-5.93966627  0.47625202  1.03265131]]
-----------------------------------------
[1, 2, 2] : [[False]]
[1, 5, 5] : [[ True]]
[1, 4, 2] [1, 0, 10] : [[False  True]]

파일에 있는 데이터를 읽어서 x_data와 y_data에 치환해서 사용하는 코드다. 음수 인덱스와 슬라이싱은 이전 글에서 설명했으니 여기서는 건너뛴다.


# feature별 가중치를 난수로 초기화. feature는 bias 포함해서 3개. 1행 3열.
W = tf.Variable(tf.random_uniform([1, len(x_data)], -1.0, 1.0))

가중치 배열을 생성하는 코드로 1행 3열의 2차원 배열이다. 


# 행렬 곱셈. (1x3) * (3x6)
h = tf.matmul(W, X)
hypothesis = tf.div(1., 1. + tf.exp(-h)) # exp(-h) = e ** -h. e는 자연상수

X는 3행 6열의 배열이다. 현재는 placeholder로 되어 있고, 맨 밑의 반복문에서 데이터와 연결된다. matmul 함수는 행렬 곱셈을 지원하는 함수로, W와 X의 행렬 크기가 어울리지 않으면 에러를 발생시킨다. 여기서 사용된 2개의 변수에는 오해가 있을 수 있다. 정확하게 말하면 h가 hypothesis에 해당하고, hypothesis는 sigmoid에 해당한다. 그러나, 처음에 나온 그림에서 첫 번째 공식을 표현할 때 sigmoid를 H(X)로 표현했기 때문에, 김성훈 교수님의 동영상에서는 적절한 변수 이름이라고 할 수 있다. 그러나, sigmoid의 역할이 자연상수 e의 지수를 사용해서 hypothesis의 결과를 0과 1 사이의 값으로 변환하는 것임을 상기하면 수긍 가능할 것이다.


cost = -tf.reduce_mean(Y * tf.log(hypothesis) + (1 - Y) * tf.log(1 - hypothesis))

앞의 그림에서 두 번째 공식을 표현한다. reduce_mean 함수의 파라미터는 정확하게 시그마(∑)의 오른쪽에 있는 공식을 있는 그대로 표현하고 있다. 글자 그대로 log 함수를 호출했고, 약속했던 매개변수를 전달한다. 코드 맨 앞에는 음수 기호(-)도 붙어 있다.


print('[1, 2, 2] :', sess.run(hypothesis, feed_dict={X: [[1], [2], [2]]}) > 0.5)

학습 결과를 사용해서 성공과 실패를 예측하고 있다. 여기서 착각하면 안 되는 것이 hypothesis가 성공 또는 실패를 참과 거짓으로 직접 알려주는 것이 아니라 sigmoid 함수를 통해 0과 1 사이의 값으로 변환한 결과를 알려준다는 사실이다. 그래서, 참과 거짓에 대한 결과는 직접 0.5와 비교해서 사용해야 한다. 60%의 성공 확률이라고 표현할 수도 있기 때문에, 이렇게 반환하는 것이 맞다고 생각한다.

placeholder에 전달되는 X의 값이 조금 어렵다. 그런데, 앞에서 '04train.txt' 파일을 읽어올 때의 x_data는 6개의 데이터를 갖고 있어서 3행 6열이었다. 이번에는 데이터를 1개만 전달하기 때문에 3행 1열이 되어야 하니까, [[1], [2], [2]]와 같은 []가 두 번 중첩되어서 나오는 것이 맞다.


동영상에서 코드가 있는 부분을 캡쳐했다. 위의 그림을 보면, 코드와 어울리는 공식이 어떤 것인지 쉽게 알 수 있다.


96000 0.321994 [[-12.81583881   0.57090575   2.35556102]]
97000 0.321991 [[-12.83300495 0.57092708 2.35898232]]
98000 0.321988 [[-12.85017109 0.570948 2.36240411]]
99000 0.321985 [[-12.86729622 0.57096756 2.36581874]]
100000 0.321983 [[-12.88358688 0.57098198 2.36907005]]

최종적으로 두 번째 열에 있는 cost는 0이 되지 않았다. 앞에서 보여준 간단하면서도 feature가 하나밖에 없는 예제에서는 cost가 0이 될 수도 있겠지만, 실제 상황에서는 0이 될 수 없는 것이 맞다. 10만번을 구동시켜서 확인했는데, 위와 같은 결과가 나왔다. 줄어들기는 하지만, 0이 될 수는 없다.

결국  classification이라고 하는 것은 2차원 좌표상에 흩어진 데이터를 직선을 그어서 구분하겠다는 뜻인데, 그 직선을 계산하는 비용이 0이 될 수는 없다. 어떻게 계산해도 상당한 거리일 수밖에 없다. Logistic Regression을 포함한 모든 예측에서 100% 만족한 결과란 존재하지 않는다. 100%는 신의 영역이다. 대통령 선거와 같은 투표 결과 또한 위아래 5% 정도의 오차를 허용하고 있지 않은가?