데이터를 (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 |