(참고: https://ko.wikipedia.org/wiki/%EB%B0%94%EC%9D%B4%EC%96%B4%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%8A%A4_%ED%95%A8%EC%88%98)


어쩌다가 우연히 이 함수를 알게 되었는데, 그냥 개인 취향으로 맘에 들어서 언젠간 한 번 그려보고 싶었던 놈이였습니다. 오늘 새벽에 멍 때리다가 갑자기 생각나서 python numpy를 활용해서 그려봤네요. 거의 수학 공식 그대로를 코드로 바로 옮겨서 쓸 수 있다는 게 맘에 들었습니다. 실제로 이걸 C/C++, Java로 계산하자면(까마득...) 확실히 python이 편하긴 하네요.


코드는 다음과 같습니다.


import numpy as np import matplotlib.pyplot as plt a = 0.5 # 0 < a < 1 b = 5 # ab > 1 + 3/2 * pi x = np.arange(-5, 5, 0.0001) y = np.zeros(2 * 5 * 10000, dtype="float128") for n in xrange(1000): y += np.power(a, n) * np.cos(np.power(b ,n) * np.pi * x) if n % 100 == 0: print "Now: ", n plt.grid(True) plt.xlim(-5, 5) plt.ylim(-3, 3) plt.plot(x, y, "black", linewidth=0.1) # linewidth 조정 가능 plt.savefig("Weierstrass Function.png", dpi=1200) # 그냥 보시려면 plt.show()


이미지로 그려봤습니다. 나름 적절히 linewidth 타협을 봐서 세세하게 잘 보이면서 너무 얇지도 않게(?) 그렸습니다. 너무 얇다고 생각하시는 분은 linewidth를 0.1보다 크게 세팅하시면 됩니다.

* 그리다보면 warning으로 overflow가 뜰 수 있습니다. np.power의 특성 덕분인지는 모르겠으나 그래프 그리는데에는 큰 지장은 주지 않아서 그대로 사용했습니다. 원래는 a 초기값에서 step마다 a 자신을 초기값과 곱해서 늘려가게 짜보았으나 이건 overflow가 나버리면 아예 작동에 심각한 지장을 줘서 일단은 np.power로 했습니다. 따로 제대로된 해결책을 찾아봐야 할 것 같습니다.

Linear Regression에 이어서 두 번째로는 Logistic Regression을 해보려고 합니다. 원래는 여러 개의 x가 존재하는 Linear Regression도 해보려고 했으나, Logistic (Regression) Classification까지 한 이후에 여러 개의 x가 존재하는 Linear Regression과 Logistic (Regression) Classification을 동시에 하는게 더 좋을 거 같네요. 조금 응용해서 확장하는 수준이라서 큰 어려움은 없을 거 같아서 말이죠.


간단하게 Logistic Regression이 왜 나왔는지부터 시작하는게 좋을 듯 합니다. 예를 들어서 스팸 메일과 햄 메일(정상 메일)을 분류한다고 합시다. 스팸 메일일 경우에는 1, 햄 메일일 때는 0을 출력하게 머신 러닝을 구현하고 싶다고 하고, 이것을 Linear Regression을 통해서 해결해보려고 합시다. 일단 데이터가 다음과 같은 경우일 때를 고려해봅시다.


 메일 고유값

1 

3 

9 

4 

100 

스팸 여부 

0 

0

1 


이때의 Linear Regresssion을 구하면 다음과 같은 그래프가 나오게 됩니다. y = 0.5를 중심으로 이보다 크면 스팸이고 아래이면 햄이라고 간주됩니다.


이때 x = 4일 경우를 보면 Linear Regression으로 구한 그래프에서 스팸(1)이 아니라 햄(0)이라고 인식되게 점의 y좌표가 0.5보다 아래에 있습니다. 이러한 문제를 해결하기 위해서 이러한 Binary Classification에서는 Linear Regression보다는 훨씬 더 정확하게 나오는 Logistic Regression을 사용합니다.


Logistic Regression의 기본 모델은 다음과 같습니다. sigmoid 함수라고도 하죠.

이를 그래프로 그려보면 다음과 같습니다. (a = 1, b = 0)

그래프의 y절편은 0.5이며, y = 0과 y = 1을 점근선으로 가집니다. 이를 이용해서 머신러닝을 시켜보면 꽤 정확한 결과가 나온다고 합니다.


Linear Regression 때와 같이 이 함수도 최적화를 거쳐서 제대로된 함수로 만드는 과정이 필요합니다. 따라서 cost값을 계산을 해야합니다. 기존의 Linear Regression에서는 다음과 같은 cost 함수를 사용했었습니다. 

함수의 형태가 달라졌으니, 한 번 시험삼아 위의 값을 입력해 cost 함수의 그래프를 그려봅시다.

?! 그려보니 우리가 예상하던 U자 형태의 도형이 아닌 괴이한 형태의 도형이 나왔습니다. TensorFlow에서 사용하는 GradientDescentOptimizer, 일명 경사내려가기최적화기이라고 할 수 있는 이 알고리즘은 말 그대로 경사를 타고 내려가면서 최솟값을 찾는 방법이라고 합니다. 따라서 이 cost 함수를 그대로 적용해버리면 최적화된 함수가 나오지 않을 수도 있죠. 그래서 우리는 새로운 cost 함수를 정의해야 합니다.


먼저 우리는 학습할 데이터에서 한 데이터가 스팸(1)인 경우, 그 데이터를 함수에 넣었을 때 스팸이라고 나올 때는 cost를 가장 적게, 햄에 가깝게 나올 때에는 cost를 매우 많이 줘야합니다. 반대로 한 데이터가 햄(0)인 경우, 그 데이터를 함수에 넣었을 때 햄이라고 나올 때는 cost를 가장 적게 , 스팸에 가깝게 나올 때에는 cost를 매우 많이 줘야합니다. 이러한 특성을 생각하면 다음의 cost 함수를 생각해볼 수 있습니다.

이러한 cost 함수가 나올 수 있는 까닭은 log(x)는 점근선으로 x = 0을 가지기 때문에, 원래 데이터와 함수의 계산된 값이 정반대로 나올 수록 cost를 무지무지 많이 줄 수 있기 때문입니다. 이 함수를 정리하면 다음과 같은 수식을 얻을 수 있습니다.

한 번 어떻게 나오는 지 그래프를 그려봅시다.

오오, 생각했던 대로 그릇 모양이 나와서 GradientDescentOptimizer가 최적화된 함수를 잘 구할 수 있는 모양이 나왔네요! 이제 TensorFlow에서 Logistic (Regression) Classification을 해봅시다. 코드는 다음과 같습니다.

여기서 y 함수가 바로 sigmoid 함수를 나타낸 것인데, TensorFlow에 이미 자체적으로 내장되어 있는 tf.sigmoid 함수를 써도 됩니다.  


* 보통 Logistic Regression 하면 단순히 위에 설명한 함수를 그대로 넣어서 training을 하는데, 저 같은 경우는 위와 같이 돌리게 되면 log의 특성에 따라서 nan이 나오는 경우가 발생해서(...) tf.clip_by_value(num, min, max)를 통해 값을 잘라냈습니다. 잘라내지 않으면 정상적으로 training이 되지 않고요. (이 문제 땜에 은근 골치 아파서 + 귀차니즘 발생으로 2달간 글을 안 썻던 겁니다 읍읍)

import tensorflow as tf import matplotlib.pyplot as plt import numpy as np x_data = [1, 3, 9, 4, 100] y_data = [0, 0, 1, 1, 1] a = tf.Variable(tf.random_uniform([1], 1.0, 2.0)) b = tf.Variable(tf.random_uniform([1], 1.0, 2.0)) X = tf.placeholder(tf.float32) Y = tf.placeholder(tf.float32) y = tf.div(1., 1. + tf.exp(-a * X + b)) loss = -tf.reduce_mean(Y * tf.log(tf.clip_by_value(y, 1e-8, 1.)) + (1 - Y) * tf.log(tf.clip_by_value(1 - y, 1e-8, 1.))) optimizer = tf.train.GradientDescentOptimizer(0.5) train = optimizer.minimize(loss) init = tf.initialize_all_variables() sess = tf.Session() sess.run(init) for step in xrange(2500): sess.run(train, feed_dict={X: x_data, Y: y_data}) if step % 100 == 0: print step, sess.run(loss, feed_dict={X: x_data, Y: y_data}), sess.run(a), sess.run(b) # Show result t = np.arange(0., 101., 0.01) plt.grid(True) plt.xlim(0, 101) plt.ylim(-0.5, 1.5) plt.plot(t, sess.run(y, feed_dict={X: t}), "g") # Train datas plt.plot(x_data, y_data, "ro") # Learing result plt.plot(x_data, sess.run(y, feed_dict={X: x_data}), "go") plt.show()


위 예제를 돌려서 그래프를 그려보면 다음과 같이 이쁘게 잘 나옵니다.

빨간색 점: 데이터셋

초록색 그래프: 학습된 그래프

초록색 점: 데이터셋에서 x를 넣었을 때의 값

이게 바로 Logistic Regression이고, 여기서 Classification을 하시려면 단순히 0.5를 기점으로 1에 가까운 지, 0에 가까운 지 분류하시면 됩니다.


이렇게 간단하게 Logistic Regression을 다뤄봤습니다. 다음 시간에는 다 변수의 Linear Regression, Logistic Regression을 다뤄볼 생각입니다. 다 변수인 만큼 차원도 많아져서 변수 두 개까지만 간단히 다뤄서 3D 그래프 그려보는게 이해에도 좋고 편할 거 같네요. 




이전글(1. Tensorflow와 놀자! : Linear Regression)에서 말했던 것처럼 이번에는 최소제곱법에서 TensorFlow의 알고리즘을 쓰지 않고 직접 편미분을 해보고 그 방식으로 Linear Regression을 해보겠습니다.


이전글에 있던 cost값 함수는 다음과 같습니다.


이 그래프에서 cost 값을 가장 낮추려면, 편미분을 통해 cost 함수의 극소점을 찾아야합니다. 과정은 다음과 같습니다.


1. a, b에 대해서 각각 편미분을 한다.

두 독립변수에 대해 편미분을 하면 다음과 같은 결과가 나오게 됩니다. 이때 우리는 3차원 그래프에서 극소점을 찾아야하기 때문에 두 편도함수의 값은 당연히 0이 되어야 합니다. c = cost(a, b)로 뒀습니다.



2. 연립방정식을 푼다.

간단하게 중2 때 배운 일차연립방정식 풀이처럼 하면 되겠지만, 저는 귀찮으므로(계수 크기가 ㅂㄷㅂㄷ) 공학용 계산기를 사용해서 계산했습니다.



결론적으로 Linear Regression에서 최소제곱법으로 구한 함수는 다음과 같네요.


혹시 몰라서 TensorFlow의 GradientDescentOptimizer로 구한 a, b값을 cost 함수에 넣어봤더니 0.536026232가 나왔고, 직접 편미분해서 구한 값을 cost 함수에 넣었을 땐 0.5360230548이 나왔습니다. 호오, 둘 다 cost값이 비슷하네요.


아래 그림은 TensorFlow에서 구한 Linear Regression과 직접 편미분 계산해서 구한 Linear Regression의 선입니다. TesorFlow것이 파란색, 제가 직접 구한게 빨간색입니다. 데이터들은 초록색 점이고요. 당연히 cost값이 소수점 다섯째자리까지 일치하는 만큼 그래프도 거의 일치하는 걸 볼 수 있습니다.

흠, 나중에는 미적분 알고리즘도 공부해봐서 직접 미적분을 구현해서 Linear Regression을 구현해봐도 재미있을 거 같네요 허헣

+ Recent posts