본문 바로가기
ML&DATA/모두를 위한 딥러닝

simple linear regression (단순 선형 회귀)

by sun__ 2020. 7. 8.

데이터를 (x,y)로 표현할 수 있다면 2차원 좌표평면에 점을 찍을 수 있다.

 

그 데이터들을 가장 잘 대변하는 직선의 방정식을 찾아가는 과정을 linear regression이라고 한다.

 

가장 잘 대변하는 직선이란, cost(=loss, error)의 제곱의 평균이 가장 작은 경우를 말한다.

 

 

결과 $y = ax + b$의 a,b를 찾기 위해서 가설함수$H(x)$와 비용함수$cost(W,b)$를 사용해야 한다. (W는 가중치, b는 bias)

$$H(x) = Wx + b$$

$$cost(W,b) = \frac{1}{m}\sum_{i}^{m}(H(x_i)-y_i)^{2}$$

 


 

데이터 셋이 다음과 같이 주어졌을 때 선형회귀를 수행해보자

x y
1 1
2 2
3 3
4 4
5 5

 

import tensorflow as tf

x_data = [1,2,3,4,5]
y_data = [1,2,3,4,5]

#2.9,0.5는 임의의 랜덤값. 편의를 위함
W = tf.Variable(2.9)
b = tf.Variable(0.5)

hypothesis = W*x_data+b

#에러 제곱의 평균
#reduce_mean : 1차원 리스트-> 0차원 value로 차원을 낮춰준다 하여 reduce
cost = tf.reduce_mean(tf.square(hypothesis-y_data))
print(cost)

#minimize cost(W,b)의 W,b를 찾는 알고리즘
learning_rate = 0.01

for i in range(100):
    #gradient descent
    with tf.GradientTape() as tape:
        hypothesis = W*x_data+b
        cost= tf.reduce_mean(tf.square(hypothesis-y_data))
    
    #gradient : 미분
    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%10 ==0:
        print("{:5}|{:10.4f}|{:10.4}|{:10.6f}".format(i,W.numpy(), b.numpy(), cost))

 

여러 번 gradient descent하여 W = 1 , b = 0에 근접해 가는 것을 볼 수 있다.

 

결과

 

 

더보기

tensorflow는 처음이고 python에 익숙치 않아서 이해한대로 한 줄 한 줄 정리해본다.

 

tensor구조는 기능적으로 numpy의 ndarray와 유사하다. 하지만 GPU에 상주할 수 있도록 고안된 데이터 구조라고 한다.

 

x_data = [1,2,3,4,5]
y_data = [1,2,3,4,5]

#2.9,0.5는 임의의 랜덤값. 편의를 위함
W = tf.Variable(2.9)
b = tf.Variable(0.5)

위의 W를 그대로 출력해보면 차원(shape)과 타입, 값에 대한 정보가 모두 나온다.

<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.9>

 

텐서플로우에서 사용하는 tensor라는 데이터구조와 호환시키기 위한 데이터구조라고 생각하면 될 것 같다.

 

학습시키기 전엔 임의의 랜덤값을 넣어주는 것이 일반적이라고 한다.

 

 

hypothesis = W*x_data+b

cost = tf.reduce_mean(tf.square(hypothesis-y_data))

가설함수와 비용함수이다.

 

tf.Variable와 리스트의 곱에 tf.Variable을 hypothesis에 담았다. 

 

hypothesis를 그대로 출력해보면,

tf.Tensor([ 3.4 6.3 9.200001 12.1 15. ], shape=(5,), dtype=float32)

x_data가 가설함수에 입력됐을 때의 함수값들의 리스트와 shape, 타입에 대한 정보가 나온다.

그리고 이를 담는 데이터구조는 tensor이다.

 

cost부분의 hypothesis-y_data를 보면, tensor에서 리스트를 뺀 형태이다. 

hypothesis-y_data를 출력해보면,

tf.Tensor([ 2.4 4.3 6.200001 8.1 10. ], shape=(5,), dtype=float32)

지금까지 봤을 때 tensor 구조는 벡터와 그 벡터에 대한 정보를 담는 구조인 것으로 보인다.

 

tf.square(hypothesis-y_data)를 출력해보면,

tf.Tensor([ 5.76 18.490002 38.44001 65.61001 100. ], shape=(5,), dtype=float32)

텐서 (hypothesis-y_data)의 제곱이 담긴 텐서가 반환된다.

 

cost를 출력하면,

tf.Tensor(45.660004, shape=(), dtype=float32)

차원이 한 차원 줄었다. 이전 단계의 텐서에 들어있던 리스트의 요소들의 평균값이 담겨있는 것을 볼 수 있다.

이렇게 평균을 구하며 차원을 하나 줄여줘서 reduce_mean이라는 이름이 붙여졌다고 한다.

 

learning_rate = 0.01

for i in range(100):
    with tf.GradientTape() as tape:
        hypothesis = W*x_data+b
        cost= tf.reduce_mean(tf.square(hypothesis-y_data))
    
    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%10 ==0:
        print("{:5}|{:10.4f}|{:10.4f}|{:10.6f}".format(i,W.numpy(), b.numpy(), cost))

 

 

tf.GradientTape() : 자동 미분을 위한 클래스(API)이다. 보통 with-as 구문과 같이 사용함.

tf.GradientTape()는 context 안에서 실행된 모든 연산을 테이프(tape)에 기록한다. 그리고 기록된 연산의 gradient를 계산할 수 있다.

(위 예제에서 변수는 W,b (Variable type)에 해당함)

 

이후에 tape.gradient함수에서 미분값을 구함.

 

tape.gradient(cost, [W,b])

cost함수에서 W의 미분값, b의 미분값이 tensor에 담겨서 tuple로 반환됨.

(W_grad의 출력 예 : tf.Tensor(44.800003, shape=(), dtype=float32) )

 

A.assign_sub(B) 는 A-=B의 의미를 갖는다.

learning_rate 와 미분값을 곱한 결과를 W,b에 빼기 대입해준다.

learning_rate는 미분값을 한 라운드에 얼마나 반영할지에 대한 값이다.

 

GradientTape()는 파일입출력에 보통 쓰이는 with와 어떻게 사용됐을까? 다음과같은 함수를 생각해보자. (파일입출력의 with as와는 다른 쓰임인 것 같다.)

def func():
	#initialize something
    try:
    	#do something
    except:
    	#handle exception
    finally:
    	#상관없이 마지막에 항상 수행

func()

파이썬에선 위와같은 코드는 부자연스러운 코드라고 한다. 대신에 아래 코드를 사용할 수 잇다

class func:
	def __enter__():
    	#initialize something
        return thing
        
    #여러 api
    
    def __exit__(parameters):
    	#마지막에 항상 수행
        
with func() as f:
	#context

 

 

 

 

 

 


<단순화한 경사하강법>

 

설명을 위해 가설함수와 비용함수를 단순화하면 다음과 같다.

$$H(x) = Wx$$

$$cost(W,b) = \frac{1}{m}\sum_{i}^{m}(W\cdot x_i-y_i)^{2}$$

 

 

단순화한 비용함수는 W에 대한 아래로 볼록한 2차함수이다.

 

[위의 경우 경사하강법 알고리즘]

(초기) 임의의 점 $(W_i , cost(W_i))$을 잡는다.

(반복) 해당 점에서의 미분값을 구해서 $W_i$에 빼기 대입해준다.

 

미분값은 비용함수를 W에 대해 편미분한 함수에 $W_i$값을 넣어서 계산해준다. 식으로 나타내보면 다음과 같다.

(tf.GradientTape()의 gradient를 사용하면 되므로 편미분을 직접 해서 비용함수를 다시 짤 일은 없다.) 

(비용함수를 m으로 나누지 않고 2m으로 나눈걸로 간주했는데, 이는 알고리즘을 해치지않으면서 계산을 편하게 하기 위함. 그렇다고 비용함수가 2m으로 나뉜건 아님. 그냥 계산을 편하게 하기위해)

 

위 수식을 코드로 나타내면 다음과 같다.

gradient = tf.reduce_mean(tf.multiply(tf.multiply(W,X) - Y, X))
descent = W - tf.multiply(alpha, gradient)
W.assign(descent)

 

단순화한 경사하강법의 코드

import numpy as np
import tensorflow as tf

tf.random.set_seed(0) #다음에 이 코드를 수행해도 같은 결과가 나옴

X = np.array([1,2,3])
Y = np.array([1,2,3])

#정규분포를 따르는 [-100,100] 구간 안의 랜덤 숫자를 1개 선택(리스트형)
W = tf.Variable(tf.random.normal([1], -100, 100)) 

for step in range(300):
    hypothesis = W * X
    cost  = tf.reduce_mean(tf.square(hypothesis - Y))
    
    alpha = 0.01
    gradient = tf.reduce_mean(tf.multiply(tf.multiply(W,X) - Y, X)) #cost의 W에 대한 편미분함수
    descent = W - tf.multiply(alpha, gradient)
    W.assign(descent)
    
    if step%10 == 0:
        print("{:5} | {:10.4f} | {:10.6f}".format(step, cost.numpy(), W.numpy()[0]))
        

 

위와 같은 경우엔 삼분탐색이 더 효과적일 것 같다. 이제 일반적인 경우의 경사 하강법을 보자.

 


 

<다변수 경사 하강법>

 

다변수인 경우 오른쪽 그림처럼 local minimum == global minimum인 경우만 경사하강법을 사용할 수 있다. 이런 함수를 convex function이라고 한다.

 

가설함수는 다음과 같다. 보통 매트릭스로 표현한다.

$$H(X) = XW + b$$

 

 

위 데이터 셋을 예로 코드를 짜보자.

 

행렬을 사용하지 않은 경우

import numpy as np
import tensorflow as tf

x1 = [73., 93., 89., 96., 73.]
x2 = [80., 88., 91., 98., 66.]
x3 = [75., 93., 90., 100., 70.]
Y = [152., 185., 180., 196., 142.]

tf.random.set_seed(0)
w1 = tf.Variable(tf.random.normal([1]))
w2 = tf.Variable(tf.random.normal([1]))
w3 = tf.Variable(tf.random.normal([1]))
b = tf.Variable(tf.random.normal([1]))

learning_rate = 0.000001

for i in range(1000+1):
    with tf.GradientTape() as tape:
        hypothesis = w1*x1 + w2*x2 + w3*x3 + b
        cost = tf.reduce_mean(tf.square(hypothesis-Y))
        
    w1_grad, w2_grad, w3_grad, b_grad = tape.gradient(cost, [w1,w2,w3,b])
    
    w1.assign_sub(learning_rate*w1_grad)
    w2.assign_sub(learning_rate*w2_grad)
    w3.assign_sub(learning_rate*w3_grad)
    b.assign_sub(learning_rate*b_grad)
    
    if i%50 ==0 :
        print("{:5} | {:12.4f}".format(i,cost.numpy()))

 

행렬을 사용한 경우

import numpy as np
import tensorflow as tf


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]]

tf.random.set_seed(0)
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

n_epochs = 2000
for i in range(n_epochs+1):
    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()))

 

 

행렬을 사용한 경우가 더 간단한 것을 볼 수 있다.

 

X가 5 x 3이므로 W는 3 x 1이다.

 

더보기
X = data[:,:-1]
y = data[:, [-1]]

 

X는 data의 모든 row, 맨 오른쪽  col 제외한 column사용

y는 data의 모든 row, 맨 오른쪽 column사용


<결론>

데이터를 가장 잘 대변할 거라고 생각되는 가중치 벡터를 유추해나가는 방법.

 

 

(초기) 가중치 벡터를 랜덤으로 잡기

(반복) 그 점에 해당하는 cost함수에서의 미분값을 모든 벡터에 빼주기

->최소 cost로 수렴해가는 적절한 가중치 벡터를 얻음

->어떤 입력에 대한 결과를 예측할 수 있음

'ML&DATA > 모두를 위한 딥러닝' 카테고리의 다른 글

실습  (0) 2020.07.13
application & tips  (0) 2020.07.13
multinomial classification  (0) 2020.07.12
binary classification  (0) 2020.07.10
용어/개념  (0) 2020.07.08