강좌/MachineLearning

TensorFlow #007 선형 회귀 분석

여름나라겨울이야기 2016. 12. 13. 11:35
728x90

선형 회귀 분석(Linear Regression Analysis)

선형성이라는 기본 가정이 충족된 상태에서 독립변수종속변수의 관계를 설명하거나 예측하는 통계방법이다. 독립변수가 하나인 경우를 단순회귀분석, 여러 개인 경우를 중다회귀분석이라고 한다. 즉 회귀분석에서 독립변수에 따라 종속변수의 값이 일정한 패턴으로 변해 가는데, 이러한 변수간의 관계를 나타내는 회귀선이 직선에 가깝게 나타나는 경우를 선형회귀분석이라고 한다. (교육심리학용어사전, 2000. 1. 10., 학지사)


단순 선형 회귀 분석: y = a * x + b

x: 독립변수

y: 종속변수

a: 직선의 기울기 (가중치: weight)

b: y 절편 (bies)

2 차원에서 점이 하나인 경우 단순 선형 회귀 분석


import matplotlib.pyplot as plt

xs = [1]
ys = [2]

plt.plot(xs, ys, 'ro')
plt.show()

(1, 2) 점 하나를 갖는 경우이다.

쿨럭.. 선을 그어야 하는데 점 하나를 통과하는 선은 무수히 많다. 결국 y = a * x + b 를 만족하는 선은 무수히 많다는 것이다. 이래서는 x 가 1 일때, y 는 2 인 것을 제외하고는 다른 x 에 대한 y 값을 계산(예측)해 볼 수는 없다. 결국 점이 하나인 경우에는 (단순) 선형 회귀 분석의 의미가 없다.


2차원에서 점이 두 개인 경우 단순 선형 회귀 분석 (두뇌 이용 방법)


import matplotlib.pyplot as plt

xs = [1, 2]
ys = [2, 5]

plt.plot(xs, ys, 'ro')
plt.show()

(1, 2), (2, 5) 두 점을 먼저 그려보자.

쿨럭.. 두 점이 그래픽 경계선에.. ㅡㅡ;

import matplotlib.pyplot as plt

xs = [1, 2]
ys = [2, 5]

plt.plot(xs, ys, 'ro')
plt.xlim(0, 3)
plt.ylim(0.1, 6)

plt.show()


두 점이 있으니 선을 그을 수 있다.

 import matplotlib.pyplot as plt

xs = [1, 2]
ys = [2, 5]

# 빨간 점 찍기
plt.plot(xs, ys, 'ro')

# 두 점 사이에 파란 선 긋기
plt.plot(xs, ys, 'b')

plt.xlim(0, 3)
plt.ylim(0.1, 6)
plt.show()


x 의 범위(수학용어: 정의역)에 적절한 제한 있긴 하겠지만 파란선을 연장해 보면 모든 x 의 값에 대해 y 의 값을 계산할 수 있다. x 가 1.5 일때 y 의 값을 찾아보자. 자를 들고 그림에서 선 그어가면서.... 중학교 수학을 적용해 보자. 2차원에서 모든 직선은 다음 수식을 만족한다.

y = a * x + b

우리가 알고 있는 x, y 쌍이 2개 이니 각각 대입해 보면

2 = a * 1 + b

5 = a * 2 + b

를 얻을 수 있다. 두번째 수식에서 첫번째 수식을 빼주면

a = 3

을 얻을 수 있다. 얻어진 a 를 다시 첫번째 수식에 대입하면

2 = 3 * 1 + b

결국 

b = -1

을 얻을 수 있다. 이렇게 얻은 a, b 값을 직선의 식에 대입해 보면

y = 3 * x - 1

을 얻을 수 있다.


2차원에서 점이 두 개인 경우 단순 선형 회귀 분석 (컴퓨터 이용 방법) - 사전 지식: 잔차? 편차? 분산? 표준편차?

바로 앞에서는 두뇌를 이용해서 두 점 (1, 2), (2, 5) 를 지나는 선의 y = a * x + b 형태의 수식을 만족하는 a, b 를 얻어냈다. 이것을 컴퓨터를 이용해 알아내는 것이 머신 러닝이다. 선형 회귀 분석에 사용되는 지식을 먼저 갖추도록 하자. 일단 x 축의 두 위치 1, 2 를 이어주는 임의의 선을 그어 보자.

import matplotlib.pyplot as plt
import numpy as np

xs = [1, 2]
ys = [2, 5]

plt.plot(xs, ys, 'ro')
plt.xlim(0, 3)
plt.ylim(0.1, 6)

# a 예상치
a = 1
# b 예상치
b = 2

y = a * np.array(xs) + b

plt.plot(xs, y, 'b')

plt.show() 

numpy 를 이용해서 y 라는 배열을 xs 배열을 이용해 쉽게 계산해 보았다.

실제의 y 값들은 ys 배열에 있고, 예상한 y 값들은 y 배열에.. ㅡㅡ; 실제값과 예상값들의 차이를 표시해 보자.

x 가 1 일때, y 의 실제값은 2 이고 예측값은 3 이다. 차이는 -1 이다.

x 가 2 일때, y 의 실제값은 5 이고 예측값은 4 이다. 차이는 1 이다.

이러한 각 점에서의 차이를 잔차(Residual)이라고 한다. 하지만 머신 러닝 책들은 편차(Deviation)라고 한다. 사전적인 내용을 봐도 편차라하면 안 되는데... 나는 수학 공부 중이 아니고 머신러닝 공부 중이고, 체제순응형이니 그냥 편차라 적도록 하겠다.

편차 Deviation

수학 및 통계학에서 편차는 자료값 또는 변량과 평균의 차이를 나타내는 수치이다. 편차를 살펴보면 자료들이 평균을 중심으로 얼마나 퍼져 있는지를 알 수 있다.

자료값이 평균보다 크면 편차는 양의 값을, 평균보다 작으면 음의 값을 갖는다. 편차의 크기는 차이의 크기를 나타낸다.

편차의 절댓값은 절대편차, 편차의 제곱은 제곱편차라고 한다.

(수학백과, 2015.5, 대한수학회)


잔차 Residual

회귀분석에서 종속변수와 적합값(예상값)의 차이를 잔차(residual)라고 한다. 즉 잔차는  (종속변수 - 적합값) 으로 정의된다.

(수학백과, 2015.5, 대한수학회)

이러한 편차들을 최소화 하는 a, b 를 찾는 것이 우리의 목표이다. 그런데 편차의 합을 구해서 그것을 최소화 하려고 하니 문제가 생긴다. 위에서 두 점과 예상의 편차의 합은 0 이다. 이래서는 편차의 합을 최소화할 수가... 음의 편차와 양의 편차가 서로 상쇄시키기 때문이다. 그래서 분산(Variance)을 사용하게 된다. 분산은 편차의 제곱이다. 그렇치만 이는 또 너무 값이 커진다나 어쩐다나 그래서 분산의 제곱근인 표준 편차(Standard Deviation)를 사용하게 된다. 아 어렵다. 프로그래밍으로 만들어 보자.

import numpy as np
import math

xs = [1, 2]
ys = [2, 5]

# a 예상치
a = 1
# b 예상치
b = 2
y = a * np.array(xs) + b

# 편차의 합
sumOfDeviation = 0.0

# 분산의 합
sumOfVariance = 0.0


# 표준편차의 합
sumOfStandardDeviation = 0.0


for i, x in enumerate(xs):
    deviation = ys[i] - y[i]
    variance = deviation ** 2
    standardDeviation = math.sqrt(variance)

   
    sumOfDeviation += deviation
    sumOfVariance += variance
    sumOfStandardDeviation += standardDeviation

   
    print "x 가 ", x, "일때 실제값 ", ys[i], ", 예상값 ", y[i]
    print "   편차(잔차): ", deviation
    print "   분산: ", varianc
    print "   표준편차: ", standardDeviation
    print

print "편차의 합: ", sumOfDeviation
print "분산의 합: ", sumOfVariance
print "표준편차의 합: ", sumOfStandardDeviation 

제곱근 계산을 위해 math 라이브러리를 사용했고, 반복문을 위해 enumerate 함수를 사용했다. 결과는 아래와 같다.

x 가  1 일때 실제값  2 , 예상값  3
   편차(잔차):  -1
   분산:  1
   표준편차:  1.0

x 가  2 일때 실제값  5 , 예상값  4
   편차(잔차):  1
   분산:  1
   표준편차:  1.0

편차의 합:  0.0
분산의 합:  2.0 
표준편차의 합:  2.0 

값들을 기가막히게 선택했더니 분산과 표준편차가... ㅡㅡ;

우리는 표준편차의 합을 최소화 하는 a, b 를 컴퓨터가 찾아내도록 하는 것이 목표다. 표준편차의 합을 0 에 최대한 가깝게 만드는 a, b 를 찾도록 하는 것이다. 물론 내가 로직을 만들어도(꺄아악.. 싫어요) 되겠지만 텐서플로우를 이용해 보자.


2차원에서 점이 두 개인 경우 단순 선형 회귀 분석 (컴퓨터 이용 방법) - 텐서플로우 이용

일단 바로 위 소스를 텐서플로우를 이용하는 버전으로 바꾸어 보자. 텐서플로우는 텐서를 이용하고 텐서플로우가 제공하는 함수들도 사용해 보자. 소스가 많이 바꾸었다. 새 술은 새 부대에... 로마에 가면 로마법을... 차근 차근 살펴보는 것은 역시 읽는 자의 몫으로...

numpy 가 다차원 배열을 쉽게 연산하는 것처럼, tensorflow 도 다차원 배열(텐서)를 쉽게 연산한다는 것을 기억하면서...

import tensorflow as tf

# 아래에서 사용하는 tf 의 sqrt 함수는 int 를 다루지 않음
# https://www.tensorflow.org/versions/r0.11/api_docs/python/math_ops.html#sqrt
xs = [1.0, 2.0]
ys = [2.0, 5.0]

# a 예상치: 텐서의 곱셈은 int 를 다루지 않음
a = tf.Variable(1.0)
# b 예상치
b = tf.Variable(2.0)
y = a * xs + b

deviations = tf.sub(ys, y)
# print deviations

variances = tf.pow(deviations, 2)
# print variances

standardDeviations = tf.sqrt(variances)
# print standardDeviations

# 편차의 합
sumOfDeviation = tf.reduce_mean(deviations)

# 분산의 합
sumOfVariance = tf.reduce_mean(variances)

# 표준편차의 합
sumOfStandardDeviation = tf.reduce_mean(standardDeviations)

sess = tf.Session()

sess.run(tf.initialize_all_variables())

print "y: ", sess.run(y)
print "편차 리스트: ", sess.run(deviations)
print "분산 리스트: ", sess.run(variances)
print "표준편차 리스트: ", sess.run(standardDeviations)
print "편차의 합: ", sess.run(sumOfDeviation)
print "분산의 합: ", sess.run(sumOfVariance)
print "표준편차의 합: ", sess.run(sumOfStandardDeviation)

결과

y:  [ 3.  4.]
편차 리스트:  [-1.  1.]
분산 리스트:  [ 1.  1.]
표준편차 리스트:  [ 1.  1.]
편차의 합:  0.0
분산의 합:  1.0
표준편차의 합:  1.0

a 는 직선의 기울기이다. 선형 회귀 분석에서는 이를 가중치 W, 즉 weight 라고 한다. b 는 y 절편으로 bias 라고 부른다. 수식을 다시 정리해 보자.

y = W * x + b

이를 가설함수(hypothesis function)이라고 한다.

우리에겐 주어진 x 집합이 있고, 그에 상응하는 y 집합이 있다. 그리고 가상의 선을 그어서 표준 편차의 합을 최소로 하는 W, b 값을 이리 저리 바꾸어 가면서 찾을 것이기에 둘을 변수 텐서로 정의했다. 그리고 최소화할 함수를 표준 편차의 합으로 잡았는데, 이를 비용함수(cost function, loss function) 이라고 한다.

이제 완성해 보자.

import tensorflow as tf

xs = [1.0, 2.0]
ys = [2.0, 5.0]

# Weight(가중치) 예상치
# 2.5 ~ 3.5 사이의 랜덤 값으로 제한
W = tf.Variable(tf.random_uniform([1], 2.5, 3.5))

# bias 예상치
# -1.5 ~ -0.5 사이의 랜덤 값으로 제한
b = tf.Variable(tf.random_uniform([1], -1.5, -0.5))

# 가설 함수
hypothesis = W * xs + b

# 비용 함수
cost = tf.reduce_mean(tf.sqrt(tf.pow(tf.sub(ys, hypothesis), 2)))

# 최적화 객체
# 학습률을 0.0007 로 지정했다.
optimizer = tf.train.GradientDescentOptimizer(0.0007)

# 비용 함수(cost)를 최소화(minimize) 하기 위한 W, b 를 구하도록 최적화 객체에게 지시한다.
train = optimizer.minimize(cost)

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

print "{0:5} {1:20} {2:20} {3:20}".format("step", "cost", "Weight", "bias")

# 10000 번을 반복하면서 최적의 W, b 를 구한다.
for step in xrange(1001):
    sess.run(train)
    if step % 100 == 0:
        print "{0:5} {1:<20} {2:20} {3:20}".format(step, sess.run(cost), sess.run(W), sess.run(b)) 

결과

실행할 때 마다 결과는 조금씩 다를 수 있다.

비용함수는 표준편차의 합이 아닌 평균을 이용했다. 책들이 주로 그걸 쓰더라는... 그리고 최적화 객체, 최소화 트레이닝 등등... 차마 다 설명하기는... 자 가시죠. 홍콩과기대 김성훈 교수님의 강좌로...

김성훈 교수님 강좌: https://www.inflearn.com/

위 코드에서 W 의 범위, b 의 범위, 학습률, 반복 횟수 등은 지식의 산물로 정해지기는 강아지뿔... 누가 말 했다. "머신 러닝은 99% 노가다와 1% 영감의 산물이다". 숫자를 바꾸어 가면서 코드를 실행해 보는 것은 누군가의 몫으로 남기고....

비용함수에 대해 자세히 생각해 보자.

cost = tf.reduce_mean(tf.sqrt(tf.pow(tf.sub(ys, hypothesis), 2))) 

이걸 수학식으로 풀어 내면 다음과 같다.

이걸 다시 W 와 b 의 함수로 풀어내면... 타이핑이.. 수학식이... ㅡㅡ; 됐고...

W, b, cost 를 3차원 좌표계에 표현해 보면 대충 다음과 같을 것이다.

출처: http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html

여기서 경사하강법은 한 지점에서의 기울기를 보면서 cost 가 0 인 지점을 찾아가는 것이다. 이걸 다시 그림으로 표현해 보면...

출처: www.holehouse.org/mlclass/

여기서 텐서플로우의 경사하강법이 지나간 경로를 추적해 보자. 아래 코드는 굳이 이해하려고 하지 않기를...

import tensorflow as tf

xs = [1.0, 2.0]
ys = [2.0, 5.0]

# Weight(가중치) 예상치
# 2.5 ~ 3.5 사이의 랜덤 값으로 제한
W = tf.Variable(tf.random_uniform([1], 2.5, 3.5))

# bias 예상치
# -1.5 ~ -0.5 사이의 랜덤 값으로 제한
b = tf.Variable(tf.random_uniform([1], -1.5, -0.5))


# 가설 함수
hypothesis = W * xs + b

# 비용 함수
cost = tf.reduce_mean(tf.sqrt(tf.pow(tf.sub(ys, hypothesis), 2)))

# 최적화 객체
# 학습률을 0.0007 로 지정했다.
optimizer = tf.train.GradientDescentOptimizer(0.0007)


# 비용 함수(cost)를 최소화(minimize) 하기 위한 W, b 를 구하도록 최적화 객체에게 지시한다.
train = optimizer.minimize(cost)

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

print "{0:5} {1:20} {2:20} {3:20}".format("step", "cost", "Weight", "bias")

weightList = []
biasList = []
costList = []

# 10000 번을 반복하면서 최적의 W, b 를 구한다.
for step in xrange(10001):
    sess.run(train)

    weightList.append(sess.run(W)[0])
    biasList.append(sess.run(b)[0])
    costList.append(sess.run(cost))

    if step % 1000 == 0:
        print "{0:5} {1:<20} {2:20} {3:20}".format(step, sess.run(cost), sess.run(W), sess.run(b)) 

# 각각 맨 앞의 3개, 맨 뒤의 3개씩 출력해 봄
print
print weightList[:3], weightList[-4:]
print biasList[:3], biasList[-4:]
print costList[:3], costList[-4:]        

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(weightList, biasList, costList, rstride=1, cstride=1)
plt.show() 

결과


마지막 소스로 1000 번째 반복마다 그래프를 그려서 확인해 보자.

import tensorflow as tf
import matplotlib.pyplot as plt

xs = [1.0, 2.0]
ys = [2.0, 5.0]

# Weight(가중치) 예상치
# 2.5 ~ 3.5 사이의 랜덤 값으로 제한
W = tf.Variable(tf.random_uniform([1], 2.5, 3.5))

# bias 예상치
# -1.5 ~ -0.5 사이의 랜덤 값으로 제한
b = tf.Variable(tf.random_uniform([1], -1.5, -0.5))

# 가설 함수
hypothesis = W * xs + b

# 비용 함수
cost = tf.reduce_mean(tf.sqrt(tf.pow(tf.sub(ys, hypothesis), 2)))

# 최적화 객체
# 학습률을 0.0007 로 지정했다.
optimizer = tf.train.GradientDescentOptimizer(0.0007)

# 비용 함수(cost)를 최소화(minimize) 하기 위한 W, b 를 구하도록 최적화 객체에게 지시한다.
train = optimizer.minimize(cost)

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

# 10000 번을 반복하면서 최적의 W, b 를 구한다.
for step in xrange(10001):
    sess.run(train)

    if step % 1000 == 0:
        plt.plot(xs, ys, 'ro')
        plt.plot(xs, sess.run(hypothesis), 'b')
        plt.xlim(0.0, 3.0)
        plt.ylim(0.1, 6.0)
        plt.title("step: {} / cost: {}\nW: {} / b: {}".format(step, sess.run(cost), sess.run(W), sess.run(b)))
        plt.show() 


다수의 점에 대한 선형 회귀 분석

이전 소스에서 xs, ys 리스트에 값을 추가해 주면 되겠다. 이 값은 직접 소스 상에 입력해 줄 수도, 외부 파일이나 네트워크로 부터 읽어오도록 하면 될 것이다. 소스에서 어디까지가 학습을 위한 테이터 준비인지, 선형 회귀 분석을 위한 준비 작업인지, 실제 트레이닝이 일어나는 위치는 어디인지 등을 구분하는 것은 과제로....

numpy 의 random 객체의 메소드들, tensorflow 의 random_xxxx 함수들은 스스로 찾아서 학습을..

각종 숫자 상수들을 이리 저리 바꾸어 가면서 감각을 키워보는 것도 스스로 학습을...

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np

xs = []
ys = []

for i in xrange(100):
    # np.random.normal: https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.normal.html
    x = np.random.normal(1.5, 0.3)
    # np.random.normal(0.0, 0.3) 를 통해 약간의 노이즈 추가
    y = 3 * x - 1 + np.random.normal(0.0, 0.3)

    xs.append(x)
    ys.append(y)

# Weight(가중치) 예상치
# 2 ~ 4 사이의 랜덤 값으로 제한
W = tf.Variable(tf.random_uniform([1], 2, 4))
# W = tf.Variable(4.0)

# bias 예상치
# -2 ~ -2 사이의 랜덤 값으로 제한
b = tf.Variable(tf.random_uniform([1], -2, 2))
# b = tf.Variable(2.0)

# 가설 함수
hypothesis = W * xs + b

# 비용 함수
cost = tf.reduce_mean(tf.sqrt(tf.pow(tf.sub(ys, hypothesis), 2)))

# 최적화 객체
# 학습률을 0.1 로 지정했다.
optimizer = tf.train.GradientDescentOptimizer(0.1)

# 비용 함수(cost)를 최소화(minimize) 하기 위한 W, b 를 구하도록 최적화 객체에게 지시한다.
train = optimizer.minimize(cost)

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

# 10000 번을 반복하면서 최적의 W, b 를 구한다.
for step in xrange(101):
    sess.run(train)

    if step % 10 == 0:
        plt.plot(xs, ys, 'ro')
        plt.plot(xs, sess.run(hypothesis), 'b')
        plt.xlim(0.0, 3.0)
        plt.ylim(0.1, 6.0)
        plt.title("step: {} / cost: {}\nW: {} / b: {}".format(step, sess.run(cost), sess.run(W), sess.run(b)))
        plt.show() 


반응형