1. Gradient Descent (1)
SymPy library를 사용하여 수학방정식을 사용하였다.
Import
import numpy as np
import sympy as sym
from sympy.abc import x
from sympy.plotting import plot
func
sympy의 poly는 함수식을 정의해주고 subs 함수는 다른 변수로 치환거란 값을 대입해준다. 구현한 func 함수는 val 값을 받았을 때의 결과값과 함수식을 반환해준다.
def func(val):
fun = sym.poly(x**2 + 2*x + 3)
return fun.subs(x, val), fun
func_gradient
func_gradient는 미분한 값과 미분한 식을 반환하는 것을 목적으로 한다. sympy의 diff 함수를 사용해 마분식을 구할 수 있다.
def func_gradient(fun, val):
_, function = fun(val) #함수식을 받아온다
diff= sym.diff(function, x) #미분식 계산
return diff.subs(x, val), diff #미분값, 미분식 반환
gradient_descent
gradient_descent는 경사하강법을 시행하는 함수이다.
def gradient_descent(fun, init_point, lr_rate=1e-2, epsilon=1e-5):
cnt = 0
val = init_point #시작 위치(x)
diff, _= func_gradient(fun, val) #시작위치에서의 첫 gradient
while np.abs(diff) > epsilon: # 최소 점이라고 생각할 수 있는 epsilon보다 큰 동안 계속 반복문을 돌린다.
val -= lr_rate * diff # leaning_rate와 gradient(미분값)을 곱한 값을 빼주면서 update
diff, _ = func_gradient(fun, val) # 다시 gradient 값을 계산
cnt += 1 # 수행한 횟수 count
print("함수: {}\n연산횟수: {}\n최소점: ({}, {})".format(fun(val)[1], cnt, val, fun(val)[0]))
2. Gradient Descent (2)
이번에는 sympy를 사용하지않고 구현하는 문제이다.
func
함수식 구현은 똑같이 하지만 함수식을 반환하지 않는다
def func(val):
fun = sym.poly(x**2 + 2*x + 3)
return fun.subs(x, val)
difference_quotient
미분 공식을 그래로 똑같이 구현한 후 비분 값만 반환해준다.
def difference_quotient(f, x, h=1e-9):
result = (f(x + h) - f(x)) / h
return result
gradient_descent
위에서 했던대로 경사하강법을 시행하는 코드를 구현하면 된다.
def gradient_descent(func, init_point, lr_rate=1e-2, epsilon=1e-5):
cnt = 0
val = init_point
diff = difference_quotient(func, val) # 첫 번째 gradient를 받는다
while np.abs(diff) > epsilon:
val -= lr_rate * diff # update
diff = difference_quotient(func, val) # gradient 다시 계산
cnt += 1
print("연산횟수: {}\n최소점: ({}, {})".format(cnt, val, func(val)))
3. Linear Regression
1차원 데이터 train_x와 그 결과값 train_y를 가지고 Linear Regression 수행하는 코드를 구현하는 문제이다.
train_x, train_y
train_x에는 난수 값을 집어넣고 train_y에는 train_x 길이만큼 0으로 채운다. 함수식은 7x+2이며 sympy의 poly 함수로 정의한 후 train_y에 각 함수의 결과값을 넣어준다.
train_x = (np.random.rand(1000) - 0.5) * 10
train_y = np.zeros_like(train_x)
def func(val):
fun = sym.poly(7*x + 2)
return fun.subs(x, val)
for i in range(1000):
train_y[i] = func(train_x[i])
initialize
초기값들을 지정해준다.
w, b = 0.0, 0.0
lr_rate = 1e-2
n_data = len(train_x)
errors = []
main
정답과 다르게 구현했지만 결과적으로 별차이는 없었다. 하지만 내가 구현한 방법은 틀렸다고 할 수 있다. 왜냐하면 for문의 변수 i를 보고 i를 만든 이유가 있을 것이라고 생각해서 train_x의 i번째 값들을 통해 예측값을 계산했지만 정답에서 i는 사용하지 않았다...
for i in range(100):
# 예측값 y 계산
_y = w * train_x[i] + b
# gradient
gradient_w = - (train_y[i] - _y) * train_x[i] # 제곱오차식을 w에 대한 미분한 식에 각각 대입
gradient_b = - (train_y[i] - _y) # 이 또한 b에 대한 미분한 식에 각각 대입
# w, b update with gradient and learning rate
w -= lr_rate * gradient_w
b -= gradient_b
# L2 norm과 np_sum 함수 활용해서 error 정의
error = np.linalg.norm(train_y[i] - _y) / n_data # RMSE를 사용하여 error를 정의
# Error graph 출력하기 위한 부분
errors.append(error)
print("w : {} / b : {} / error : {}".format(w, b, error))
제곱오차 $$(y - \hat{y})^2$$ 에서 w에 대해서 미분하면 $$-2(y - \hat{y})x$$ 가 되지만 앞에 상수값인 2를 없애주었는데 이는 손실 함수에 상소를 곱하거나 나누어도 최종 모델의 가중치나 절편에 영향을 주지 않기 때문이다.
정답 코드에서는 다음과 같이 $$(\hat{y} - y)^2$$를 제곱오차로 사용하여 train_x의 모든 값을 가지고 계산하여 100번 반복하였다. 그리고 loss function으로는 MSE를 사용하였다.
for i in range(100):
# 예측값 y 계산
_y = w * train_x + b
# gradient
gradient_w = np.sum((_y - train_y) ** 2) / n_data
gradient_b = np.sum((_y - train_y)) / n_data
# w, b update with gradient and learning rate
w -= lr_rate * gradient_w
b -= lr_rate * gradient_b
# L2 norm과 np_sum 함수 활용해서 error 정의
error = np.sum((_y - train_y) ** 2) / n_data # MSE를 사용하여 error를 정의
# Error graph 출력하기 위한 부분
errors.append(error)
print("w : {} / b : {} / error : {}".format(w, b, error))
error 시각화
from IPython.display import clear_output
import matplotlib.pyplot as plt
%matplotlib inline
def plot(errors):
clear_output(True)
plt.figure(figsize=(20,5))
plt.ylabel('error')
plt.xlabel('time step')
plt.plot(errors)
plt.show()
plot(errors)
다음과 같이 time step에 따라 error가 줄어드는 것을 확인할 수 있다.
More complicated function
좀 더 복잡한 선형식 $$y = w_0 x_0 + w_1 x_1 + w_2 x_2 + b$$ 를 가지고 regression을 진행하는 것을 구현하는 문제이다. train_x는 matrix로 정의되고 train_y와 beta_gd를 vector로 정의하여 계산된다. 무어-펜로즈 유사역행렬을 사용해도된다.
train_x = np.array([[1,1,1], [1,1,2], [1,2,2], [2,2,3], [2,3,3], [1,2,3]])
train_y = np.dot(train_x, np.array([1,3,5])) + 7
# random initialize
beta_gd = [9.4, 10.6, -3.7, -1.2]
# for constant element
expand_x = np.array([np.append(x, [1]) for x in train_x]) # beta_gd와 shape을 맞추기위해 마지막 열에 1을 추가
for t in range(5000):
error = train_y - expand_x @ beta_gd
grad = - np.transpose(expand_x) @ error
beta_gd -= 0.01 * grad
print("After gradient descent, beta_gd : {}".format(beta_gd))
수식에 대한 내용은 이전에 작성한 경사하강법에서 확인하자.
4. Stochastic Gradient Descent
SGD는 미니 배치를 구성해서 Gradient Descent하는 기법이다. 미니 배치를 구성할 때 np.random.choice를 활용하여 구현하였다. 위에서 구현한 Linear Regression에서 미니 배치로 mini_x와 mini_y를 추가 구현한 것 뿐이다.
for i in range(100):
idx = np.random.choice(1000, 10)
mini_x = train_x[idx]
mini_y = train_y[idx]
_y = mini_x * w + b
error = np.sum((_y - mini_y) ** 2) / n_data
gradient_w = np.sum((_y - mini_y) * mini_x) / n_data
gradient_b = np.sum((_y - mini_y)) / n_data
w -= lr_rate * gradient_w
b -= lr_rate * gradient_b
# Error graph 출력하기 위한 부분
errors.append(error)
print("w : {} / b : {} / error : {}".format(w, b, error))
error 시각화
결과는 GD보다 더 빠르고 효율적으로 수행되는 것을 확인 할 수 있다.
'Boostcamp AI Tech' 카테고리의 다른 글
[Boostcamp 선택 과제 - 3] Maximum Likelihood Estimation (MLE) (0) | 2021.08.08 |
---|---|
[Boostcamp 선택 과제 - 2] RNN Backpropagation 구현 (0) | 2021.08.08 |
[Boostcamp Day-5] Python - Pandas (0) | 2021.08.06 |
[Boostcamp Day-5] Python - Data Structure (0) | 2021.08.06 |
[Boostcamp Day-5] Python - Numpy (0) | 2021.08.06 |