경사하강법 매운맛(gradient descent)

Date:


경사하강법을 통한 선형 회귀

  • 역행렬과 무어펜로즈(Moore-penrose)역행렬을 통하여 선형회귀가 가능하지만, 선형 모델에 대해서만 적용이 가능함

🔑 경사하강법을 통한 선형회귀

  • 선형모델을 포함하여 다른 모델도 대응이 가능함
  • 역행렬을 통한 회귀법보다 더 powerful한 방법

선형회귀에 대한 이해

  • 많은 수의 data가 있을 경우($벡터의 수≥벡터의 길이$), 그 모든 벡터를 만족하는 해답을 구할 수 없음(일직선에 있는 경우는 제외)
  • 따라서 선형회귀 모델을 이용하여 데이터들을 아우를 수 있는 최적의 식을 찾아야 함

💡 data -> 행렬식으로 표현

image

  • 찾고자 하는 최적의 식을 $X\beta = \hat Y ≈ Y$로 정의하여 $Y-\hat Y$를 최소로 만드는 $\beta$을 구해야 한다.
  • 무어펜로즈 역행렬을 사용하면, 바로 최소가 되는 $\beta$을 구할 수 있지만, 사용범위가 더 넓은 경사하강법으로 트라이 해보자
  • 선형회귀 목적식 $   Y-\hat Y   _2 =   Y-X\beta   _2$가 최소가 되는 $\beta$ 구하기 위해 gradient vector를 구해보자

🔑 gradient 수식 풀이

image

  • 점화식 표현 $\beta^{(t+1)} = \beta^{(t)} - \lambda\nabla_\beta   Y-X\beta^{(t)}   = \beta^{(t)} + \frac{2\lambda}{n}X^T(y-X\beta^{(t)}$

선형회귀 algorithm 구현(경사하강법)

📰code

import numpy as np

#한평면 상 -> [1,2,3]이 정답
X= np.array([[1,1],[1,2],[2,2],[2,3]]) 
y= np.dot(x,np.array([1,2]))+3

# 0,0 만 다른 평면에 있을 경우 -> [0.7333, 1.8666, 0.7333]이 정답
# X= np.array([[1,1],[1,2],[2,2],[2,3],[0,0]]) 
# y= np.array([3,5,6,8,1])
beta_gd=[10.1,15.1,-6.5] 
X_=np.array([np.append(x,[1]) for x in X])

for t in range(10000): 
    error = y - X_@beta_gd
#     error = error/np.linalg.norm(error) #이부분을 안해주면 learning_rate:0.01에 반영됨
    grad = - np.transpose(X_) @ error
    beta_gd = beta_gd - 0.01*grad
    
print(beta_gd)

🔍result

[1. 2. 3.]

경사하강법은 만능일까?

  • 경사하강법은 결국 미분가능이 가능하고, 최소점을 구할 수 있는 볼록(convex)한 함수에 대해서는 수렴이 가능한다
  • 적절한 학습률과 학습횟수를 선택했을 때 수렴이 보장됨
  • 선형회귀의 경우는 목적식($   Y-X\beta   _2$)이 회귀계수$\beta$에 대해 볼록함수이기 때문에 수렴가능성이 보장된다
  • 하지만 극소값이 여러개거나, 미분 불가능한 경우는 사용하지 못한다(무어펜로즈 역행렬보다는 더 많은 모델을 구축할 수 있다)
  • 비선형회귀 문제의 경우 목적식이 볼록하지 않을 수 있으므로 수렴성이 항상 보장되지 않음

image

경사하강법 gradient vector 계산



확률적 경사하강법(Stochastic Gradient Descent)

  • 경사하강법은 non-convex함수에 대해서 최소값이 보장되지 않는다는 문제 있음
  • 그걸 보완하기 위한 방법임
  • 한 개 혹은 일부 데이터를 활용하여 업데이트(기존 경사하강법은 모든 데이터를 이용하여 업데이트)
  • 일부 데이터를 활용하기 때문에 연산자원을 효율적으로 활용 가능함($O(d^2n) -> O(d^2b)$ : 연산량 b/n만큼 감소함) image

  • 볼록이 아닌(non-convex) 목적식은 SGD를 통해 최적화 가능함
  • mini-batch는 10~1000개의 데이터를 가지고 함
  • 딥러닝에서는 SGD가 실증적으로 더 낫다는 평가를 받음

image

💡 mini-batch SGD

image image

경사하강법 vs 미니배치 SGD

image

경사하강법 vs 미니배치 SGD code 비교

📰code

import numpy as np
import sympy as sym
from sympy.abc import x,w,b #추가
from sympy.plotting import plot

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)

def l2_norm(x): #추가
    x_norm = x*x
    x_norm = np.sum(x_norm)
    x_norm = np.sqrt(x_norm)
    return x_norm    

for i in range(1000):
    train_y[i] = func(train_x[i])

# initialize
lr_rate = 1e-2
w, b = 0.0, 0.0
n_data = 1000
errors_gd = []

# 경사하강법
expand_x = np.array([np.append(x, [1]) for x in train_x])

for i in range(100):
    ## Todo
    # 예측값 y
    _y = w*train_x+b
    
    # gradient
    gradient_w = - np.transpose(expand_x)[0]@(train_y-_y)/n_data #l2_norm(train_y-_y) #값이 작어지며 learning rate를 크게 듦
    gradient_b = - np.transpose(expand_x)[1]@(train_y-_y)/n_data #l2_norm(train_y-_y) 

    # w, b update with gradient and learning rate
    w -= lr_rate * gradient_w
    b -= lr_rate * gradient_b
    
    # L2 norm과 np_sum 함수 활용해서 error 정의
    error_gd = l2_norm(train_y-_y)
    # Error graph 출력하기 위한 부분
    errors_gd.append(error_gd)

print("경사하강법 w : {} / b : {} / error : {}".format(w, b, error_gd))

# initialize
w, b = 0.0, 0.0
n_data = 10
minibatch_size = 10
errors_sgd = []

# 확률적 경사하강법
for i in range(100):
    ## Todo
    #minibatch 10
    minibatch_idx = np.random.choice(train_y.shape[0], minibatch_size, replace = True)
    minibatch_x = train_x[minibatch_idx]
    minibatch_y = train_y[minibatch_idx]
    
    # 예측값 y
    _y = w*minibatch_x +b
    
    # gradient
    gradient_w = -np.transpose(minibatch_x)@(minibatch_y-_y)/n_data    #l2_norm(minibatch_y-_y) #값이 작어지며 learning rate를 크게 듦
    gradient_b = -np.ones(10)@(minibatch_y-_y)/n_data                  #l2_norm(minibatch_y-_y)

    # w, b update with gradient and learning rate
    w = w - lr_rate*gradient_w
    b = b - lr_rate*gradient_b
    
    # L2 norm과 np_sum 함수 활용해서 error 정의
    error_sgd = l2_norm(minibatch_y-_y)
    # Error graph 출력하기 위한 부분
    errors_sgd.append(error_sgd)

print("확률적 경사하강법 w : {} / b : {} / error : {}".format(w, b, error_sgd))

🔍result 정답은 w : 7, b : 2

경사하강법 w : 7.000429738930361 / b : 1.2742577531192194 / error : 23.181636293256783
확률적 경사하강법 w : 7.039753269083189 / b : 1.2571391750926415 / error : 2.2038064311604137

GD vs SGD 그래프

📰code

from IPython.display import clear_output
import matplotlib.pyplot as plt
%matplotlib inline

def plot(errors_gd,errors_sgd):
    clear_output(True)
    plt.figure(figsize=(20,5))
    plt.subplot(211)
    plt.ylabel('errors_gd')
    plt.xlabel('time step')
    plt.plot(errors_gd)
    plt.show()
    
    plt.figure(figsize=(20,5))
    plt.subplot(212)
    plt.ylabel('errors_sgd')
    plt.xlabel('time step')
    plt.plot(errors_sgd)
    plt.show()
plot(errors_gd,errors_sgd)

🔍result image

📌reference

  • boostcourse AI tech
  • https://www.kakaobrain.com/blog/113


💡 수정 필요한 내용은 댓글이나 메일로 알려주시면 감사하겠습니다!💡 

댓글