본문 바로가기

부스트캠프 AI Tech/AI_Math

[03] 경사하강법

미분 Differentiation

  • 미분(differentiation)은 변수의 움직임에 따른 함수값의 변화를 측정하기 위한 도구
  • 최적화에서 제일 많이 사용하는 기법
  • 미분은 변화율의 극한(limit)으로 정의
  • $f'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}$
  • 예시$f'(x) = 2x + 2$미분을 손으로 계산하려면 일일이 $h \rightarrow 0$ 극한을 계산해야 한다
  • $\frac{f(x+h) - f(x)}{h} = 2x + 2 + h$
  • $f(x) = x^2 + 2x + 3$
  • 최근에는, 미분을 손으로 직접 계산하는 대신 컴퓨터가 계산해준다!
    • sympy.diff - symbolic python
import sympy as sym
from sympy.abc import x

# poly - 다항함수
# x로 미분하겠다는 뜻
sym.diff(sym.poly(x**2 + 2*x + 3), x)

# Poly(2𝑥+2,𝑥,𝑑𝑜𝑚𝑎𝑖𝑛=ℤ)

그림으로 이해하는 미분

  • 미분은 함수 $f$의 주어진 점 $(x, f(x))$에서의 접선의 기울기를 구한다
  • 한 점에서 접선의 기울기를 알면 어느 방향으로 점을 움직여야 함수값이 증가하는지 / 감소하는지 알 수 있다
    • 미분값이 음수일 때 미분값을 더하면 증가, 빼면 감소
    • 미분값이 양수일 때 미분값을 더하면 증가, 빼면 감소
    • 즉, 증가시키고 싶으면 미분값을 더하고 감소시키고 싶으면 미분값을 뺀다

미분의 쓰임

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

경사하강법 : 알고리즘

Input: gradient, init, lr, eps, Output: var
# gradient: 미분을 계산하는 함수
# init: 시작점, lr: 학습률, eps: 알고리즘 종료조건

# lr는 update 속도를 조절한다, 조심해서 제어해야한다
# 컴퓨터 계산 때, 미분값이 정확히 0이 되기 불가능하므로
# eps보다 작을 때 종료하는 조건 필요하다

var = init
while(abs(grad) > eps):
    var = var - lr * grad  # 미분값을 주어진 변수에서 빼준다
    grad = gradient(var)
  • var = var - lr * grad 이 부분이 $x - \lambda f'(x)$을 계산하는 부분
  • lr은 학습률로서 미분을 통해 업데이트하는 속도 조절
  • 종료조건 성립하기 전까지 미분값을 계속 업데이트
  • 예시: 함수가 f(x) = x^2 + 2x + 3일 때 경사하강법으로 최소점 찾는 코드
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=1e-2, epsilon=1e-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(f"함수: {fun(val)[1]}, 연산횟수: {cnt}, 최소점; ({val}, {fun(val)[0]})")

gradient_descent(fun=func, init_point=np.random.uniform(-2, 2))

# 함수: Poly(x**2 + 2*x + 3, x, domain='ZZ'), 연산횟수: 640, 최소점; (-0.999995015264646, 2.00000000002485)
# (-1, 2)

편미분 - 변수가 벡터인 경우

  • 미분(differentiation) - 변수의 움직임에 따른 함수값 변화 측정
  • 미분의 입력에서, 벡터가 입력인 다변수 함수의 경우 편미분(partial differentiation)을 사용한다
    • $e_i$는 i번째 값만 1이고 나머지는 0인 단위 벡터
    • 즉, x의 i번째 벡터만 영향, 나머지는 변화하지 않는다
    • 특정 방향 좌표축으로 이동
    • 편미분은 주어진 함수의 변수 개수만큼 가능하다$\partial _{x_i} f(x, y) = 2x + 2y - sin(x + 2y)$
    • $f(x, y) = x^2 + 2xy + 3 + cos(x+ 2y)$
  • $\partial_{x_i}f(x) = \lim_{h \to 0} \frac{f(x+he_i) - f(x)}{h}$
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)
# 2𝑥+2𝑦−sin(𝑥+2𝑦)
  • 각 변수 별로 편미분을 계산한 gradient vector를 이용해 경사하강/경사상승법에 사용할 수 있다
    • $\nabla$ 나블라 : 다변수 함수의 gradient 벡터 표시
    • 이 gradient vector를 사용해 변수 $x = (x_1, ..., x_d)$를 동시에 업데이트 가능
  • $\nabla f = (\partial_{x_1}f, \partial_{x_1}f, \cdot \cdot \cdot, \partial_{x_1}f)$

[참고] 미분 기호 - 델타

  • https://ko.wikipedia.org/wiki/∂
  • $\partial$
    • 델타의 변형으로, 편미분을 나타낸다
    • LaTex에서는 partial
    • del, dee, partial이라고 한다
  • $\delta$
  • 델타
  • $\nabla$
  • 나블라

Gradient Vector

  • 각 점 $(x, y, z)$ 공간에서 $f(x, y)$ 표면을 따라 $- \nabla f$ 벡터를 그리면 아래와 같이 그려진다
  • 극소점으로 향하는 화살표들의 움직임

  • 함수 $f(x, y)$의 등고선(contour) 해석
    • gradient vector $\nabla f(x, y)$는 각 점 $(x, y)$에서 가장 빨리 증가하는 방향으로 흐르게 된다
    • $- \nabla f$는 $\nabla (-f)$와 같고, 이는 각 점에서 가장 빨리 감소하게 되는 방향과 같다

경사하강법 : 알고리즘

Input: gradient, init, lr, eps, Output: var
# gradient: 그레디언트 벡터를 계산하는 함수
# init: 시작점, lr: 학습률, eps: 알고리즘 종료조건

# 벡터는 절대값 대신 norm을 계산해 종료조건 설정

var = init
grad = gradient(var)
while(norm(grad) > eps):
    var = var - lr * grad
    grad = gradient(var)
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)
    return grad_vec, [diff_x, diff_y]

def gradient_descent(fun, init_point, lr_rate=1e-2, epsilon=1e-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(f"함수: {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)

# 함수: Poly(x**2 + 2*y**2, x, y, domain='ZZ'), 연산횟수: 603, 최소점: [-4.92683346e-06 -1.64534091e-11], 2.42736879841898E-11

 

'부스트캠프 AI Tech > AI_Math' 카테고리의 다른 글

[06] 확률론  (0) 2022.01.16
[05] 딥러닝 학습방법 이해하기  (0) 2022.01.16
[04] 확률적 경사하강법 SGD  (0) 2022.01.15
[02] 행렬  (0) 2022.01.15
[01] Vector  (0) 2022.01.14