미분 (differentiation)
미분이란 '한 점에서의 기울기' 이다.
이는 변수의 움직임에 따른 함수값의 변화를 측정하기 위한 도구로, 최적화에서 많이 사용하는 기법이다.
미분은 변화율의 극한으로 정의하는데, 어떤 기울기를 계산할때, f(x+h) - f(x)를 h로 나눈 것, 이 변화율의 극한값이 미분이다.
미분을 구하는 코드는 아래와 같음. sympy 라이브러리를 활용해서, 그 안에 diff 기능을 계산한다 (도함수 구하기)
딥러닝에서도 이렇게 수치적으로 미분 계산 및 최적화 해서 활용할 예정.
import sympy as sp
# 변수 및 함수 정의
x = sp.symbols('x') # 변수 x를 정의합니다.
f = x**2 + 3*x + 2 # 미분할 함수를 정의합니다.
# 함수를 x로 미분합니다.
f_prime = sp.diff(f, x)
# 결과 출력
print("f(x) =", f)
print("f'(x) =", f_prime)
# 결과
f(x) = x**2 + 3*x + 2
f'(x) = 2*x + 3
미분을 이해하기 1) - 그림으로 이해
미분은 함수 f의 주어진 점 (x, f(x))에서의 '접선의 기울기'를 구한다. f'(x)
한 점에서 접선의 기울기를 알면, 어느 방향으로 점을 움직여야 함수값이 증가 혹은 감소하는지를 알 수 있다.
f(x)라는 함수 위에서 x라는 점이 있을 때, 미분값을 알면 어느 방향으로 움직일지 컨트롤이 가능하다.
2차원 좌표상 그림으로는 이를 쉽게 파악할 수 있지만, 고차원의 함수일수록 어느 방향으로 가야 함수가 증가할지 혹은 감소할지 알기 어려움.
이때 미분을 활용하면 이를 파악하기 쉬움.
- 함수값을 증가시키고 싶다면 미분값을 더한다 -> '경사 상승법(gradient ascent)'
- 함수값을 감소시키고 싶다면 미분값을 뺀다 -> '경사 하강법(gradient descent)'
경사상승 / 경하하강 방법은 각각 극단의 값(극대값 maximum, 극소값 minimum)에 도달하면, 움직임을 멈춘다.
== 최적화 알고리즘의 원리
ex. 극값에서는 미분값이 0이므로 더이상 업데이트가 되지 않기 때문에, 목적함수의 최적화가 자동으로 끝나게 된다.
경사하강법
경사하강법의 요소들
- gradient : 미분을 계산하는 함수
- init : 시작점
- lr : 학습률
- eps : 알고리즘 종료 조건
컴퓨터로 계산시에 미분값이 0이되는 시점, 종료되는 시점이 오기 어려움. 따라서 알고리즘을 종료 조건을 설정해줘야만 알고리즘이 중단
경사하강법은 미분값을 경사에서 반복적으로 (종료조건 이전까지) 빼주면서 위치를 이동시킨다.
아래 코드에서 var = var -lr * grad 부분이 미분을 통한 업데이트 부분이며, lr 학습률로 업데이트 속도를 조절한다.
var = init # 변수 초기값
grad = gradient(var) # 기울기/경사
while(abs(grad) > eps) :
var = var - lr * grad # 업데이트
grad = gradient(var) # 업데이트 후 기울기 다시 계산
아래는 함수가 fx = x**2 + 2x + 3일때 경사하강법으로 최소점을 찾는 코드의 예시이다.
import sympy as sym
import numpy as np
# 심볼릭 변수 정의
x = sym.symbols('x')
# 함수 정의: 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"Iteration {cnt}: x = {val}, gradient = {diff}")
return val
# 초기값 설정
init_point = 0.0
# 경사하강법 실행
min_x = gradient_descent(func, init_point)
print(f"The minimum value is found at x = {min_x}")
변수가 벡터인 경우 : 편미분
위에서 설명한 경사하강법은 변수가 1개 일때, 방향이 + 혹은 - 두개일때 미분하여 계산했었다.
그러나 입력되는 변수가 벡터나 행렬일 때, 즉 다변수 함수의 경우에는 편미분 (partial difference) 를 사용한다.
편미분이란 '특정 방향의 좌표축으로 이동하는 형식'의 미분이다.
편미분은 x의 i번째 값에만 변화율을 주고, 나머지는 그대로 두는 방법, 즉 다변수 함수의 특정 변수를 제외한 나머지 변수를 상수로 간주하여 미분하는 방법 이다.
ex. x에 대한 편미분 : y를 상수 취급, x에 대한 미분을 하기 / y에 대한 편미분 : x를 상수 취급, y에 대한 미분을 하기
import sympy as sp
# 변수 정의
x, y = sp.symbols('x y')
# 함수 정의
f = x**2 + 2*x*y + 3 + sp.cos(x + 2*y)
# x에 대한 편미분
partial_f_x = sp.diff(f, x)
# y에 대한 편미분
partial_f_y = sp.diff(f, y)
# 결과 출력
print("∂f/∂x:", partial_f_x)
print("∂f/∂y:", partial_f_y)
# 결과
∂f/∂x: 2*x + 2*y - sin(x + 2*y)
∂f/∂y: 2*x - 2*sin(x + 2*y)
그레디언트 벡터 (gradient)
각 변수별로 편미분을 계산한 그레디언트 벡터 (gradient vector)를 이용해서 경사하강법 / 경사상승법에 사용할 수 있다.
아래 수식은 '일반적인 d차원 공간에서 벡터에 적용되는 경사하강법'을 정의한 것.
f(x) 대신 벡터 ∇f를 사용하여 변수를 동시에 업데이트 하는 것이 가능하다.
$$\nabla f = \left( \frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \ldots, \frac{\partial f}{\partial x_n} \right)$$
그레디언트 벡터 ∇f(x,y)는 각 점(x,y)에서 가장 빨리 증가하는 방향으로 흐르게 된다.
여기에 마이너스를 붙이게되면, -∇f(x,y)는 각 점(x,y)에서 가장 빨리 감소하는 방향으로 흐르게 된다.
# 증가하는 방향 $$f(x, y) = x^2 + 2y^2 \Rightarrow \nabla f(x, y) = \left( 2x, 4y \right)$$ # 감소하는 방향 $$ f(x, y) = x^2 + 2y^2 \Rightarrow -\nabla f(x, y) = -\left( 2x, 4y \right) $$
wiki - gradient
그레디언트 벡터를 계산하는 코드는 아래와 같다.
var = init # 변수 초기값
grad = gradient(var) # 기울기/경사
while(norm(grad) > eps) : #미분 계산때는 abs, 그레디언트 벡터는 norm
var = var - lr * grad # 업데이트
grad = gradient(var) # 업데이트 후 기울기 다시 계산
아래는 함수가 fx = x**2 + 2y**2 일때 경사하강법으로 최소점을 찾는 코드의 예시이다.
import numpy as np
import sympy as sym
x, y = sym.symbols('x y')
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 -= lr_rate * diff
diff, _ = func_gradient(fun, val)
cnt += 1
return val, cnt
pt = [np.random.uniform(-2,2), np.random.uniform(-2,2)]
result, iterations = gradient_descent(func_multi, pt)
print(f'최소값: {result}, 연산횟수: {iterations}')
'ML study' 카테고리의 다른 글
[네이버AI class] 4주차 (1) - 확률론 (0) | 2024.05.21 |
---|---|
[네이버AI class] 3주차 (4) 딥러닝 학습 원리 (0) | 2024.05.20 |
[네이버AI class] 3주차 (2) - 행렬 (0) | 2024.05.17 |
[네이버AI class] 3주차 (1) - 벡터 (0) | 2024.05.16 |
[네이버AI class] 1주차 - 개발 환경 설정, Pandas, Numpy (0) | 2024.05.05 |