Mathematics for Artificial Intelligence 3강: 경사하강법

2023. 1. 2. 22:00BOOTCAMP/boostcamp AI Tech Pre-Course

미분이 뭔가요?

- 미분(differentiation)은 변수의 움직임에 따른 함수값의 변화를 측정하기 위한 도구로 최적화에서 제일 많이 사용하는 기법입니다.
미분 변화율의 극한(limit)으로 정의한다.
미분을 손으로 계산하려면 일일이 h -> 0 극한을 계산해야한다.
 
f(x) = x2 + 2x + 3
f'(x) = 2x + 2  
 
- 최근엔 미분을 손으로 직접 계산하는 대신 컴퓨터가 계산해줄 수 있다.
import sympy as sym
from sympy.abc import x
sym.diff(sym.poly(x**2 + 2*x + 3), x)
Poly(2*x + 2, x, domain='ZZ')
 
요즘은 sympy.diff를 가지고 미분을 컴퓨터로 계산할 수 있다.
- 미분은 함수 f의 주어진 점 (x,f(x))에서의 접선의 기울기를 구한다.
미분을 계산하려면 함수의 모양이 매끄러워야(연속)한다.
- 한점에서 접선의 기울기를 알면 어느 방향으로 점을 움직여야 함수값이 증가하는지/감소하는지 알 수 있다.
증가시키고 싶다면 미분값을 더하고, 감소시키고 싶으면 미분값을 뺀다.
미분값을 알면 제어할 수 있다.

 

미분값이 양수면 x + f'(x) > x는 오른쪽으로 이동하여 함수값이 증가
미분값이 음수면 x - f'(x) > x는 오른쪽으로 이동하여 함수값이 감소
미분값이 양수라서 x - f'(x) > x는 왼쪽으로 이동하여 함수값이 감소

 

- 미분값을 더하면 경사상승법(Gradient ascent)이라 하며 함수의 극대값의 위치를 구할 때 사용한다.
- 미분값을 빼면 경사하강법(Gradient descent)이라 하며 함수의 극소값의 위치를 구할 때 사용한다.
- 경사상승/경사하강 방법은 극값에 도달하면 움직임을 멈춘다.
- 극값에선 미분값이 0이므로 더이상 업데이트가 안되기에 목적함수 최적화가 자동으로 끝난다.

경사하강법: 알고리즘

Input: gradient, init, lr, eps, output: var
# gradient: 미분을 계산하는 함수
# init: 시작점, lr: 학습률, eps: 알고리즘 종료조건
var = init
grad = gradient(var)
while(abs(grad) > eps) :
      var = var - lr * grad
      grad = gradient(var)
종료조건이 성립하기 전까지 미분값을 계속 업데이트한다.
var = var - lr * grad
grad = gradient(var)

import numpy as np

def func(val):
    fun = sym.poly(x**2 + 2*x +3)
    return fun.subs(x, val), fun

def func_gradient(fun, val):
    _, function = fun(val)
    diff = sym.diff(function, x)
    return diff.subs(x, val), diff

def gradient_descent(fun, init_point, lr_rate=le-2, epsilon=le-5):
    cnt = 0
    val = init_point
    diff, _ = func_gradient(fun, init_point)
    while np.abs(diff) > epsilon:
        val = val - lr_rate*diff
        diff, _ = func_gradient(fun, val)
        cnt+=1
    
    print("함수: {}, 연산횟수: {}, 최소점: ({}, {})".format(fun(val)[1], cnt, val, fun(val)[0]))

gradient_descent(fun=func, init_point=np.random.uniform(-2,2))
변수가 벡터일때
  • 미분(differentiation)은 변수의 움직임에 따른 함수값의 변화를 측정하기 위한 도구로 최적화에서 제일 많이 사용하는 기법이다.
  • 벡터가 입력인 다변수 함수의 경우 편미분(partial differentiation)을 사용한다.
  • 각 변수 별로 편미분을 계산한 그레디언트(gradient) 벡터를 이용하여 경사하강/경사상승법에 사용할 수 있다.

f(x,y) = x2 +2xy + 3 + cos(x+2y)

axf(x,y) = 2x + 2y - sin(x + 2y)

 

단위 벡터를 통해 극한을 계산하면, i번째 변수에서만 변화를 계산할 수 있기에 편미분을 통해 경사하강법을 할 수 있다.

import sympy as sym
from sympy.abc import x, y

sym.diff(sym.poly(x**2 + 2*x*y + 3) + sym.cos(x + 2*y), x)
2x+2y−sin(x+2y)

그레디언트 벡터?

  • 그레디언트 벡터는 각 점에서 가장 빨리 감소하게 되는 방향과 같다.
  • 주어진 함수의 극대, 극소값을 알 수 있음.

경사하강법: 알고리즘

  • gradient: 그레디언트 벡터를 계산하는 함수

while(norm(grad) > eps):

*경사하강법 알고리즘은 그대로 적용된다. 그러나 벡터는 절대갑 대신 노름(norm)을 계산해서 종료조건을 설정한다.

# Multivariate Gradient Descent
def eval_(fun, val):
    val_x, val_y = val
    fun_eval = fun.subs(x, val_x).subs(y, val_y)
    return fun_eval

def func_multi(val):
    x_, y_ = val
    func = sym.poly(x**2 + 2*y**2)
    return eval_(func, [x_, y_]), func
    
def func_gradient(fun, val):
    x_, y_ = val
    _, function = fun(val)
    diff_x = sym.diff(function, x)
    diff_y = sym.diff(function, y)
    grad_vec = np.array([eval_(diff_x, [x_, y_]), eval_(diff_y, [x_, y_])], dtype=float)
    
def gradient_descent(fun, init_point, lrrate=le-2, epsilon=le-5):
    cnt = 0
    val = init_point
    diff, _ = func_gradient(fun, val)
    while np.linalg.norm(diff) > epsilon:
        val = val - lr_rate*diff
        diff, _ = func_gradient(fun, val)
        cnt+=1
    
    print("함수: {}, 연산횟수: {}, 최소점: ({}, {})".format(fun(val)[1], cnt, val, fun(val)[0]))
pt=[np.random.uniform(-2, 2), np.random.uniform(-2, 2)]
gradient_descent(fun=func_multi, init_point=pt)