미분의 가장 중요한 용도는 함수를 최적화하는 것이다. 이번에는 특정 함수를 대상으로 최적화를 해볼 것이다.
함수 최적화
1. 로젠브록 함수
로젠브록 함수(Rosenbrock function)의 수식과 형태는 아래와 같다.
로젠브록 함수의 정의는 a, b가 정수일 때, 아래의 식과 같고, 위의 이미지는 a=1, b=100일 때에 해당된다.
목표는 로젠브록 함수의 출력이 최소가 되는 x0과 x1을 찾는 것이다. 실제로 로젠브록 함수의 최솟값은 (x0, x1) = (1, 1)이며 이를 DeZero를 사용하여 구해볼 것이다.
2. 미분 계산하기
import numpy as np
from dezero import Variable
def rosenbrock(x0, x1):
y = 100 * (x1 - x0 ** 2) ** 2 + (1 - x0) ** 2
return y
x0 = Variable(np.array(0.0))
x1 = Variable(np.array(2.0))
y = rosenbrock(x0, x1)
y.backward()
print(x0.grad, x1.grad) #-2.0 400.0
x0과 x1의 미분은 각각 -2.0과 400.0이 나오며 이 미분값을 모은 (-2.0, 400.0) 벡터를 기울기 혹은 기울기 벡라고 한다. 기울기는 각 지점에서 함수의 출력을 가장 크게하는 방향을 가리킨다. 반대로 기울기에 마이너스를 곱한 방향 (2.0, -400.0)은 y값을 가장 작게 줄여주는 방향을 뜻한다.
3. 경사하강법 구현
기울기가 가리키는 방향이 국소적으로는 함수의 출력을 크게하는 방향이지만 그곳에 최댓값이 존재한다는 보장은 없다. 그래서 기울기 방향으로 일정 거리만큼 이동하여 다시 기울기를 구하는 작업을 반복하면 최댓값(혹은 최솟값)에 도달할 가능성이 높다. 이것을 경사하강법(gradient descent)라 한다.
x0 = Variable(np.array(0.0))
x1 = Variable(np.array(2.0))
lr = 0.001 #학습률
iters = 1000 #반복 수
for i in range(iters):
print(x0, x1)
y = rosenbrock(x0, x1)
x0.cleargrad()
x1.cleargrad()
y.backward()
x0.data -= lr * x0.grad
x1.data -= lr * x1.grad
로젠브록 함수 같이 골짜기가 길게 뻗은 함수에는 경사하강법이 잘 대응하지 못한다. 50,000번이나 반복해야 간신히 도착했다. 따라서 이제부터 또 다른 최적화 기법을 볼 것이다.
뉴턴 방법으로 푸는 최적화 (수동 계산)
1. 뉴턴 방법을 활용한 최적화 이론
뉴턴 방법(Newton's method)은 f(x) = 0이 되는 x, 즉 함숫값이 0이되는 근사적인 해를 찾는 방법이다. f'(x) = 0을 이용하면 최솟값 또는 극솟값을 찾을 때도 뉴턴 방법을 사용할 수 있다.
뉴턴 방법은 테일러급수를 이용하여 y = f(x) 라는 함수를 다항식으로 근사하여 계산한다. 여기서는 다음과 같이 2차 함수로 식을 근사할 것이다.
이 2차 근사함수는 점 a에서 f(x)의 테일러 급수이므로 아래와 그림과 같이 a에서 y = f(x)와 접하는 곡선이 된다.
여기서 2차 근사함수 미분값이 0되는 위치를 확인함으로써 최솟값을 구할 수 있다.
2차 근사함수가 x = a - {f'(a) / f''(a)}에 있으므로 a의 위치를 -{f'(a) / f''(a)}만큼 갱신하면 된다.
경사하강법은 alpha를 사람이 설정하고 alpha만큼 기울기 방향으로 x값을 갱신한다. 반면 뉴턴 방법은 2차 미분을 이용하여 경사하강법에서의 alpha를 자동으로 조정한다. 즉 alpha를 1 / f''(x)로 대체한 것이다. 경사하강법은 1차 미분의 정보만을 사용하는 반면 뉴턴 방법은 2차 미분의 정보도 활용하므로 목적지에 더 빨리 도달할 가능성이 높다. 물리 세계로 보면 뉴턴 방법은 속도뿐만 아니라 가속도 정보까지 사용하기 때문이다.
2. 뉴턴 방법을 활용한 최적화 구현
뉴턴 방법을 이용하여 아래의 수식을 최적화 해볼 것이다.
DeZero는 2차 미분을 자동으로 구하지 못하기 때문에 2차 미분은 따로 정의해서 수동으로 구할 것이다.
import numpy as np
from dezero import Variable
def f(x):
y = x ** 4 - 2 * x ** 2
return y
def gx2(x):
return 12 * x ** 2 - 4
x = Variable(np.array(2.0))
iters = 10
for i in range(iters):
print(i, x)
y = f(x)
x.cleargrad()
y.backward()
x.data -= x.grad / gx2(x.data)
#0 variable(2.0)
#1 variable(1.4545454545454546)
#2 variable(1.1510467893775467)
#3 variable(1.0253259289766978)
#4 variable(1.0009084519430513)
#5 variable(1.0000012353089454)
#6 variable(1.0000000000002289)
#7 variable(1.0)
#8 variable(1.0)
#9 variable(1.0)
경사하강법과 뉴턴 방법을 비교했을 때, 경사하강법은 학습률 0.01로 하였을 때 124번 갱신하여 1로 수렴하였고 뉴턴 방법은 7번 갱신하여 1로 수렴하였다. 뉴턴 방법이 경사하강법에 비해 매우 빠르게 도달하는 것을 볼 수 있다.
다음 포스팅에서는 DeZero가 2차 미분, 3차 미분, 4차 미분 등 모든 형태의 고차 미분을 자동으로 계산할 수 있도록 확장할 것이다.
'AI > 딥러닝 프레임워크 개발' 카테고리의 다른 글
33단계) 뉴턴 방법 최적화 자동화 , 2차 미분 자동 계산 (0) | 2021.07.09 |
---|---|
30~32단계) 고차 미분 (0) | 2021.07.09 |
27단계) 테일러 급수 미분 (0) | 2021.06.29 |
25~26단계) 계산 그래프 시각화 , DOT (0) | 2021.06.29 |
23~24단계) 패키지로 정리 , 복잡한 함수의 미분 (0) | 2021.06.29 |
댓글