본문 바로가기

AI/Machine Learning

[머신러닝] 선형회귀(linear regression) - 컴도리돌이

728x90
728x90

Regression

"Regression toward the mean"

 

전체 평균으로 되돌아간다. 즉 어떤 데이터들이 굉장히 크거나 굉장히 작은 데이터들이 나와도 결과적으로, 전체적으로 봤을 때 이 데이터들은 전체 평균으로 되돌아가려는 특징을 갖고 있다. 

 

 


Linear Regression

종속 변수 y와 한 개 이상의 독립 변수 x와의 선형 상관관계를 모델링하는 회귀 분석 기법을 선형 회귀라고 한다. 선형 예측 함수를 사용해 회귀식을 모델링하며, 알려지지 않은 변수는 데이터로부터 추정한다. 이렇게 만들어진 회귀식을 선형 모델이라고 한다. 선형은 말 그대로 1차이기 때문에 선형 모델은 일차 모델 즉, 대표적인 일차 방정식인 직선의 방정식을 일컫는다. 

그림에서 이 파란색 점들을 데이터를 의미하며,  이 데이터 전체를 가장 잘 대변하도록 직선을 긋는다면 이 직선은 어떠한 모양이 될까?라는 것이 선형 회귀(linear regression)의 핵심이다.

 

https://en.wikipedia.org/wiki/Linear_regression

위에 간단한 데이터에 대해서 예를 들자면, 해당 feet와 price에 대한 테이블 표와 feet와 price에 대해서 차트를 그리면 오른쪽 그림과 같은 모양이 된다. 해당 파란색 x 표시가 데이터를 의미한다.

 

이 데이터를 가장 잘 대변하는 직선의 방정식을 한번 생각해보면 주황색 직선을 가정할 수가 있다. 당연히 직선의 방정식이기 때문에 'H(x) = Wx + b'으로 표현할 수 있을 것이다. 해당 주황색 선이 해당 데이터를 가장 잘 표현하고 있다고 전제하에 가정을 하는 것이다. 이 직선 식은 기울기와 절편으로 표현할 수 있으니 해당 데이터를 가장 잘 표현하는 직선의 기울기와 절편을 구하면 된다.

 

그렇다면 가설로 세운 직선의 방정식의 기울기와 절편은 어떻게 구할 수 있을까?

해당 질문을 알기 위해서는 비용이라는 개념을 생각해봐야 한다. 가설로 세운 주황색 직선과 실제 데이터의 차이를 구하면 'H(x) - y'라는 간단한 식을 구할 수 있게 된다. 즉 직선의 방정식과 해당 데이터의 차이가 작으면 작을수록 해당 데이터를 잘 표현하는 직선의 방정식을 구할 수 있을 것이다. 여기서 우리가 세운 가설과 실제 데이터와의 차이를 cost, loss 또는 error라고 한다. 우리는 여기서 해야 할 중요한 일을 당연히 이 에러를 최소화하는 방법을 찾는 것이다.

 

하지만 여기서 주의할 점은 단순히 가설에서 데이터의 y값을 빼면 안 된다. 왜냐하면 둘의 차이가 음수가 나올 수도 있고 양수가 나올 수도 있기 때문이다. 그래서 해당 에러 값을 제곱해서 사용을 한다.

error function

가설을 기반으로 error 함수를 위에 나오는 식처럼 정의를 하였다. 우리의 가설에서 실제 값의 차이(error)의 제곱을 모두 더해서 데이터의 개수 m으로 나눈 값으로 정의를 한다.(하지만 위에 식에서는 2로 나누어졌다. 그 이유는 뒤에서~) 해당 error 값은 우리가 세운 가설의 W와 b에 해당하는 값이다. 

 

Hypothesis and Error test

import tensorflow as tf

feet = [1,2,3,4,5]
price = [3,6,9,12,15]

w = tf.Variable(2.5) # w 값 임의로 설정
b = tf.Variable(6.5) # b 값 임의로 설정

hp = w * feet + b

# learning_rate
learning_rate = 0.01

# Gradient descent
for i in range(1001):
    with tf.GradientTape() as tape :
        hp = w * feet + b
        cost = tf.reduce_mean(tf.square(hp - price))
    w_grad, b_grad = tape.gradient(cost,[w,b])
    w.assign_sub(learning_rate * w_grad)
    b.assign_sub(learning_rate * b_grad)
    if i % 100 == 0:
  	    print("{:5}|{:10.4f}|{:10.4}|{:10.6}".format(i,w.numpy(),b.numpy(),cost))

 

tensorflow import를 하여 feet와 price에 대한 데이터를 표시했다. (위에 데이터로 학습시키려면 많은 데이터가 필요하기에 적절하게 3배 차이 나게끔 설정해두었다.) w와 b 변수 두 개를 임의로 설정을 하여 직선의 방정식(가설)을 hp로 선언해주었다. 여기서 중요한 부분은 gradient descent라는 알고리즘을 사용하여 w와 b 값을 지속적으로 2100번 갱신하여 300번에 한 번씩 출력하게끔 설정하였다.

 

    0|    2.2200|       6.4|      25.5
  300|    2.3838|     2.225|  0.906887
  600|    2.7769|    0.8054|  0.118867
  900|    2.9192|    0.2916| 0.0155801
 1200|    2.9708|    0.1056| 0.0020421
 1500|    2.9894|   0.03822|0.00026766
 1800|    2.9962|   0.01384|3.50807e-05
 2100|    2.9986|   0.00501|4.60001e-06
w = 2.9986
b = 0.00501
feet1 = 7

print(w * feet1 + b) # 20.99521

 

w와 b의 초기 값을 잘못 선정하여 초기 error의 값이 매우 높지만 반복적인 w와 b값을 경신함으로써 error의 값이 줄어드는 것을 확인할 수 있다. 1000번을 갱신한 결과 w의 값은 2.9986(3에 가까운), b의 값은 0.00501(0에 가까운)의 값이 나왔다. 새로운 값 7에 대해 결과를 확인해보니 20.99521(21에 가까운) 값이 나온 것을 확인할 수 있다.

 

Gradient descent algorithm

 

경사를 따라 내려가면서 최저점을 찾도록 설계된 알고리즘. 에러가 최소화가 되는 w와 b를 찾게 하는데 해당 알고리즘을 사용을 한다. gradient descent는 변수가 하나나 두 개일 때뿐만 아니라 변수가 여러 개일 때도 사용할 수 있는 알고리즘이다.

error function

여기서 다시 위에 있는 에러 함수를 가져왔다. 가설에서 y를 빼고 제곱하여 더해준 것까지도 이해하겠는데 왜 전체 데이터로 나누지 않고 2로 나누었냐? 그 이유는 해당 값은 전체 데이터를 나누던 5로 나누던 7로 나누던 에러 함숫값에는 큰 영향을 주지 않는다. 근데 왜 2로 나누냐? 바로 아래에서 미분을 할 건데 해당 에러 함수를 미분할 때 지수 값의 2가 계수로 나갈 건데 식을 간략하게 하기 위해서 일부로 2로 설정한 거다.

 

해당 에러 함수를 미분하면 지수 항의 2와 2로 나눈 값과 약분이 되고 해당 에러 함수의 속 미분하여 xj값이 나온 것을 확인할 수 있다.

 

결과 적으로 에러 함수는 업데이트될 때마다 에러 함수에서 미분한 에러 함수를 계속 더하게 된다. 여기서 알파 값은 learning rate(학습률)이라 하며, 학습률의 값을 크게 설정하면 에러 함수의 값이 크게 변하게 될 것이고 값을 작게 설정하면 에러 함수의 값이 조금씩 변하게 될 것이다. 해당 알파 값은 사용처에 따라서 유동적으로 설정해줘야 한다.

 

minimize cost test

import numpy as np

x = np.array([1,2,3,4,5])
y = np.array([3,6,9,12,15])

def error(w,x,y):
	c = 0
    for i in range(len(x)):
    	c += (w * x[i] - y[i]) ** 2
    return c/len(x)
for f in np.linspace(0,5,num=30):
	cur_error = error(f,x,y)
    print("{:6.3f} | {:10.5f}".format(f,cur_cost))

 

error 함수는 수식 그대로 반영한 것이다. x에 w라는 기울기를 곱하여 실제 데이터의 y값을 빼서 제곱한 값들을 다 합하여 전체 길이로 나눈 값을 반환해준다. 해당 함수를 테스트하기 위해서 0에서 5 사이를 30개의 구간으로 나누어 에러 값을 출력되게 만들었다.

 

 0.000|  99.00000
 0.172|  87.94768
 0.345|  77.54935
 0.517|  67.80499
 0.690|  58.71463
 0.862|  50.27824
 1.034|  42.49584
 1.207|  35.36742
 1.379|  28.89298
 1.552|  23.07253
 1.724|  17.90606
 1.897|  13.39358
 2.069|   9.53508
 2.241|   6.33056
 2.414|   3.78002
 2.586|   1.88347
 2.759|   0.64090
 2.931|   0.05232
 3.103|   0.11772
 3.276|   0.83710
 3.448|   2.21046
 3.621|   4.23781
 3.793|   6.91914
 3.966|  10.25446
 4.138|  14.24376
 4.310|  18.88704
 4.483|  24.18430
 4.655|  30.13555
 4.828|  36.74078
 5.000|  44.00000

 

출력된 결과를 확인해보니 w값이 2.931일 때 제일 작은 오차가 출력되었다.


다중 선형 회귀(Multi-variable Linear Regression)

 

선형 회귀의 가장 단순한 예제는 한 개의 스칼라 독립 변수 x와 한 개의 스칼라 의존 변수 y의 관계일 때이다. 여기에서 독립 변수를 여러 개로 확장한 것이 다중 선형 회귀이며, 실제로 거의 대부분의 문제는 여러 개의 독립 변수를 포함하며, 선형 회귀라 함은 보통 다중 선형 회귀를 일컫는다.

 

Multi-variable Linear Regression test

import tensorflow as tf
import numpy as np

data = np.array([
 [ 73., 80., 75., 152. ],
 [ 93., 88., 93., 185. ],
 [ 89., 91., 90., 180. ],
 [ 96., 98., 100., 196. ],
 [ 73., 66., 70., 142. ]
], dtype=np.float32)

X = data[:, :-1]
y = data[:, [-1]]

W = tf.Variable(tf.random.normal([3, 1]))
b = tf.Variable(tf.random.normal([1]))

learning_rate = 0.000001

def predict(X):
    return tf.matmul(X, W) + b

for i in range(2001):
    with tf.GradientTape() as tape:
        cost = tf.reduce_mean((tf.square(predict(X) - y)))
        W_grad, b_grad = tape.gradient(cost, [W, b])
        W.assign_sub(learning_rate * W_grad)
        b.assign_sub(learning_rate * b_grad)
        if i % 100 == 0:
            print("{:5} | {:10.4f}".format(i, cost.numpy()))
    0 | 90629.3438
  100 |    14.1731
  200 |     3.0052
  300 |     2.9928
  400 |     2.9818
  500 |     2.9708
  600 |     2.9599
  700 |     2.9490
  800 |     2.9382
  900 |     2.9275
 1000 |     2.9168
 1100 |     2.9062
 1200 |     2.8956
 1300 |     2.8851
 1400 |     2.8747
 1500 |     2.8643
 1600 |     2.8539
 1700 |     2.8436
 1800 |     2.8334
 1900 |     2.8232
 2000 |     2.8131
728x90
728x90