텐서플로우를 이용한 로지스틱(Logistic) 회귀

로지스틱 회귀에 들어오면서 시그모이드 함수에 난데없이 등장한 자연상수 e에 대한 정체를 밝히느라 무려 2주를
보냈다. 물론 그 과정이 쓸모없는 일은 아니었지만 꽤나 고민스럽고 답답한 시간들이었다. 이제 어느정도 그 정체를
밝혔으니 본격적으로 로지스틱 회귀 실습을 진행해보도록 하자.


샘플 데이터는 http://www.dodomira.com/2016/02/12/logistic-regression-in-r/ 블로그를 참조하였고
자료 원본 경로는 http://www.ats.ucla.edu/stat/data/binary.csv 이다.


자료 불러오기


주제에서 조금 벗어나는 이야기지만 현재 나는 Docker for MAC에서 TensorFlow 이미지를 설치한 상태이고
따라서 Docker를 통해 Jupyter 환경에서 작업을 진행 중이다. 이런 환경에서 작업의 불편을 조금이라도 줄이기
위해 Docker 컨테이너에서 MAC OSX의 디렉토리에 접근하여 데이터 파일을 읽어들이도록 사전 작업이 필요하였다. 


Docker for MAC은 기본적으로 File Sharing이라는 기능을 제공하는데 작업표시줄에 떠있는 아이콘을 클릭하면
표시되는 메뉴에서 'Preferences...'를 선택하면 설정 창이 표시되고 설정 창의 두 번째 탭에 'File Sharing'이 있다.
기본적으로 /Users,/Volumes, /private, /tmp가 등록이 되어있다. 추가로 등록도 가능하지만 기본적으로 등록된
경로의 하위 경로는 등록하지 못한다.


이렇게 등록한 후 docker run 명령어를 통해 컨테이너를 실행할 때 '-v [MAC 경로]:[docker 경로]' 형식으로
옵션을 추가하면 된다.


예)
docker run -it -v /Volumes/Data1/Tesorflow:/data -p 8888:8888 -p 6006:6006 \
gcr.io/tensorflow/tensorflow


위과 같이 한 경우 Docker 컨테이너 쉘로 진입하면 cd /data로 MAC 디렉토리에 접근이 가능하다. 이번 예제에서는
이와 같이 데이터 파일을 /data로부터 읽어 사용할 것이다.


파일을 읽어 출력한 결과는 아래와 같다.

import tensorflow as tf
import numpy as np

xy = np.loadtxt('/data/sample.txt', unpack=True, dtype='float32')

x_data = xy[0:-1]
y_data = xy[-1]

print (x_data)
print (y_data)

출력 결과 ---------------------------------------------------------------

[[ 1.          1.          1.         ...,  1.          1.          1.        ]
 [ 3.79999995  6.5999999   8.         ...,  4.5999999   7.          6.        ]
 [ 3.6099999   3.67000008  4.         ...,  2.63000011  3.6500001
   3.8900001 ]
 [ 3.          3.          1.         ...,  2.          2.          3.        ]]
[ 0.  1.  1.  1.  0.  1.  1.  0.  1.  0.  0.  0.  1.  0.  1.  0.  0.  0.
  0.  1.  0.  1.  0.  0.  1.  1.  1.  1.  1.  0.  0.  0.  0.  1.  0.  0.
  0.  0.  1.  1.  0.  1.  1.  0.  0.  1.  1.  0.  0.  0.  0.  0.  0.  1.
  0.  1.  0.  0.  0.  0.  1.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.
  0.  0.  0.  0.  0.  1.  0.  1.  0.  0.  0.  0.  1.  0.  0.  0.  0.  1.
  0.  1.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  1.  1.  0.
  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  1.  0.  0.  0.  0.
  1.  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  0.
  0.  0.  0.  0.  1.  0.  1.  0.  1.  0.  0.  1.  0.  1.  0.  0.  0.  0.
  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  0.  0.
  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  1.  0.  0.  0.  1.  0.  0.  1.
  0.  0.  0.  1.  1.  0.  1.  1.  0.  1.  0.  0.  0.  0.  0.  0.  1.  1.
  0.  1.  0.  1.  0.  0.  1.  0.  0.  1.  0.  0.  0.  1.  0.  0.  0.  0.
  1.  0.  1.  0.  0.  0.  0.  1.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.
  1.  1.  1.  0.  1.  1.  0.  0.  0.  0.  1.  1.  1.  0.  0.  1.  1.  0.
  1.  0.  1.  0.  0.  1.  0.  1.  1.  1.  0.  0.  0.  0.  1.  0.  1.  1.
  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  1.  1.  0.  0.
  1.  0.  0.  0.  0.  0.  0.  1.  0.  1.  1.  1.  1.  0.  0.  0.  0.  0.
  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  1.  1.  0.  0.  0.  1.  0.  1.
  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  1.  0.  0.  1.  0.
  1.  1.  0.  0.  1.  0.  0.  0.  0.  0.  1.  1.  1.  1.  0.  0.  0.  1.
  0.  0.  0.  1.  0.  0.  1.  0.  1.  0.  0.  0.  1.  1.  1.  1.  1.  0.
  0.  0.  0.  0.]

로지스틱 회귀 시작하기

우선 기본적인 흐름은 김성훈 교수님의 동영상 강좌를 기준으로 따라가도록 하겠다.
위에서 이미 데이터를 준비해 놓았으므로 이후 진행을 살펴보자.


먼저 학습 이후에 테스트를 위한 임의의 값을 넣을 수 있도록 x와 y에 대한 placeholder를 생성한다.

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


그리고 각 독립변수 (X)에 대한 기울기 변수 W를 만들자. 위 출력 결과를 보면 알 수 있듯이 x_data은 1행 3열의
벡터이다. 즉 len(x_data)의 값은 3이 될 것이다.

W = tf.Variable(tf.random_uniform([1, len(x_data)], -1.0, 1.0))


다음으로 선형 회귀에서 사용했던 가설함수를 만들고...

h = tf.matmul(W, X)


이 식은 선형 회귀에 대한 가설 함수이므로 이 것을 이용하여 이전 포스팅에서 열심히 공부한 로지스틱 회귀에 적합한
가설 함수를 새롭게 만든다.

hypothesis = tf.div(1., 1. + tf.exp(-h))


로지스틱 회귀의 가설 함수 공식은 이미 알아본 바가 있으므로 tensorflow의 API만 간단히 살펴보자.


  1. tf.div : 굳이 설명할 것도 없이 첫 번째 인자를 두 번째 인자로 나누는 함수이다.
  2. tf.exp : exp라는 함수는 tensorflow 뿐만 아니라 수학 계산을 위해 만들어진 대부분의 프로그램 및 언어에서 대체로 동일한 이름으로 사용되며 자연상수 e의 거듭제곱을 의미한다. 괄호 안의 파라미터만큼 거듭제곱을 하는 것이다. 따라서 이 식은 e^(-W * X)로 우리가 알고있는 로지스틱 회귀 함수의 분모에 보여지는 식이 된다.


이렇게 가설 함수를 만들었으니 이번에는 cost 함수를 만들 차례이다. 로지스틱 회귀에서의 cost 함수에 대해서는
별도로 정리한 적은 없으나 다른 사이트의 훌륭한 자료들을 참고로 식만 살펴보자(이 블로그에서는 다음에 자세히
다뤄보도록 하겠다).

with tf.name_scope("cost") as scope:
cost = -tf.reduce_mean(Y*tf.log(hypothesis) + (1 - Y)*tf.log(1 - hypothesis))
cost_summ = tf.scalar_summary("cost", cost)
cost_summ = tf.summary.scalar("cost", cost)


앞서 말했듯이 자세한 내용은 뒤로 미루고 간단하게 몇가지만 짚고 넘어가자.


우리는 로지스틱 회귀가 결과로써 0 또는 1 사이의 값을 갖도록 한다고 했다. 그 기준에 따라 Y가 0일 경우와
Y가 1일 경우 각각 다른 cost 함수가 적용되는데 이 것을 두 개의 식으로 사용하기 복잡하므로 위와 같은 식을
만들었다. Y가 1이라면 tf.reduce_mean의 파라미터로 들어가있는 덧셈식의 오른쪽 항이 0이되고 Y가 0이라면
반대로 왼쪽항이 사라져 둘 중 한 식만 적용이 되는 것이다.


그밖에 제곱 평균(tf.reduce_mean)을 하는 것은 선형 회귀와 동일하다.
그리고 아래와 같이 학습 계획을 세우는 부분 역시 이전의 선형 회귀외 동일하다.

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


그리고 이미 알고 있겠지만 tensorflow의 Variable들은 초기화를 하기 전까지는 아무것도 아니다~
변수를 초기화 하고 Session을 생성하고 학습을 진행하는 것까지 한번에 가보자!


학습 과정에서는 결과에 대해 프린트도 하고 TesorBoard용 로그도 찍도록 내용을 추가하였다.
참고로 TensorBoard를 이용하기 위한 API가 변경되었다. 관련 내용은 글 말미에 간단하게 정리하겠다.

init = tf.global_variables_initializer()

with tf.Session() as sess:
merged = tf.merge_all_summaries()
merged = tf.summary.merge_all()
writer = tf.train.SummaryWriter("/logs", sess.graph_def)
writer = tf.summary.FileWriter("/logs", graph=sess.graph)

sess.run(init)
for step in range(20000):
sess.run(train, feed_dict = {X:x_data, Y:y_data})
if step % 200 == 0 :
summary = sess.run(merged, feed_dict = {X:x_data, Y:y_data})
writer.add_summary(summary, step)
print(step, sess.run(cost, feed_dict = {X:x_data, Y:y_data}), sess.run(W))

correct_prediction = tf.equal(tf.floor(hypothesis + 0.5), Y)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print("Accuracy: ", accuracy.eval({X:x_data, Y:y_data}))


(0, 3.9219272, array([[ 0.56839818,  0.54351538,  0.33454522,  0.35521972]], dtype=float32))
(200, 0.58839369, array([[ 0.26840821,  0.14631633, -0.08747143, -0.65875155]], dtype=float32))
(400, 0.58729297, array([[ 0.12495378,  0.15614627, -0.05624871, -0.66897833]], dtype=float32))
(600, 0.58630282, array([[-0.01207304,  0.15915234, -0.02467063, -0.66446435]], dtype=float32))
(800, 0.58538932, array([[-0.14362514,  0.16177623,  0.00590925, -0.65985072]], dtype=float32))
(1000, 0.58454657, array([[-0.26996911,  0.16426219,  0.03536379, -0.65547818]], dtype=float32))
...
중간 생략
...
(17400, 0.57432276, array([[-3.30464649,  0.22630024,  0.74326813, -0.56382245]], dtype=float32))
(17600, 0.57432127, array([[-3.30992198,  0.22641255,  0.74449652, -0.56368262]], dtype=float32))
(17800, 0.57432002, array([[-3.31500411,  0.2265207 ,  0.74568129, -0.56354982]], dtype=float32))
(18000, 0.57431865, array([[-3.31990051,  0.22662495,  0.74682134, -0.56342   ]], dtype=float32))
(18200, 0.57431746, array([[-3.32461858,  0.22672606,  0.74791992, -0.56329668]], dtype=float32))
(18400, 0.57431638, array([[-3.32916546,  0.22682308,  0.74897873, -0.56317711]], dtype=float32))
(18600, 0.57431531, array([[-3.33354449,  0.22691627,  0.74999893, -0.56306177]], dtype=float32))
(18800, 0.57431442, array([[-3.33776498,  0.22700654,  0.75098211, -0.56295192]], dtype=float32))
(19000, 0.57431358, array([[-3.34183216,  0.22709353,  0.75192851, -0.5628444 ]], dtype=float32))
(19200, 0.57431275, array([[-3.34575057,  0.22717719,  0.75284076, -0.56274116]], dtype=float32))
(19400, 0.57431203, array([[-3.34952545,  0.22725755,  0.753721  , -0.56264317]], dtype=float32))
(19600, 0.57431132, array([[-3.35316253,  0.22733469,  0.75456882, -0.5625475 ]], dtype=float32))
(19800, 0.57431066, array([[-3.356668  ,  0.22740975,  0.75538445, -0.56245506]], dtype=float32))
('Accuracy: ', 0.70749998)


유튜브 강좌에 있던 짧은 샘플로는 강좌에서와 거의 유사한 결과를 얻었다. 그런데 이렇게 별도로 가져온 데이터를
사용하나 cost가 영 줄어들지를 않는다...ㅠ.ㅠ 게다가 정확도도 0.7 수준...
참고로 TensorBoard로도 확인 가능하도록 했으니 확인이나 한 번 해보자. 출력한 cost 값의 챠트는 다음과 같다.


그리고 그래프는 아래와 같다.


아직 나의 수준에서는 뭔가 제대로 된 결과가 나오기는 힘든가보다. 조금 더 열심히 공부를 해봐야겠다...ㅠ.ㅠ

앞서 말했듯이 전체적인 진행을 김성훈 교수님의 유튜브 강의를 기반으로 이번 실습을 진행을 했다. 그런데 tensorflow를 1.0으로
업그레이드한 탓인지 TensorBoard 설정을 위한 API 일부가 모두 바뀌었다. 일단 코드 상에 강의에 등장하는 이전 버전의 API는
모두 주석 처리하여 남겨두었다. 간단하게 정리하면 변화는 다음과 같다. 

  • tf.scalar_summary("cost", cost) -> tf.summary.scalar("cost", cost)
  • tf.merge_all_summaries() -> tf.summary.merge_all()
  • tf.train.SummaryWriter("/logs", sess.graph_def) -> tf.summary.FileWriter("/logs", graph=sess.graph)



마지막으로 위 실습을 진행한 Jupyter 노트북과 샘플 데이터 파일을 첨부한다.


sample.txt

chapter2_logistic_regression.py.ipynb


블로그 이미지

마즈다

이미 마흔을 넘어섰지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^

티스토리 툴바