지난 글에서 수치 미분이 무엇인지 알아보았다.
그런데 이걸 딥러닝에서 어떻게 쓸 수 있을까?
이번 글에서는 딥러닝에서 가장 중요하게 미분이 쓰이는 부분 중 하나인 경사 하강법(Gradient Descent)에 대해 정리해 보았다.
잠깐! 편미분 먼저!
사실 경사하강법이 무엇인지 이해하기 위해 알아야 할 또 하나의 미분 개념이 있다.
바로 편미분(Partial Derivative)이다.
아래 식에는 변수가 2개나 있다. 과연 미분은 이 중 어떤 것을 기준으로 해야 할까?
정답은 '둘 다 가능하다'이다. 이렇게 변수가 여럿인 함수를 각 변수에 대해 미분하는 것을 편미분이라고 한다.
$$f(x_0, x_1) = x_0^2 + x_1^2$$
`f`를 `x_0`에 대해 미분하는 것을 `\frac{\partial f}{\partial x_0}`라고 쓰고, `x_1`에 대해 미분하는 것을 `\frac{\partial f}{\partial x_1}` 라고 표기한다.
하나의 변수를 기준으로 미분할 때에 다른 나머지 변수들은 상수로 취급한다.
만약 `x_0`와 `x_1`에 대한 편미분을 동시에 계산하고 싶다면 어떻게 해야 할까?
각 변수의 편미분을 벡터로 묶으면 된다. 바로 이 것을 기울기 gradient라고 부른다.
그래프를 그려서 기울기를 직접 눈으로 확인해보자.
첫 번째 그래프는 matplotlib 라이브러리를 이용해 `f(x_0, x_1) = x_0^2 + x_1^2` 를 3차원으로 그린 것이다.
두 번째는 동일한 함수의 gradient, 즉 `x_0`와 `x_1`에 대한 편미분을 벡터로 나타낸 것이다.
우리는 이 그래프들로부터 아주 중요한 사실을 직관적으로 이해할 수 있다.
바로 주어진 함수에서 가운데 위치한 부분이 가장 낮고, 기울기 gradient가 작다는 사실이다.
가장 낮은 곳을 찾아서!
이렇게 기울기 gradient를 이용해 함수의 최소값을 찾아가는 것을 바로 경사 하강법 gradient descent이라고 한다.
수식으로 나타내면 다음과 같다.
$$\mathrm{x}_{n+1} = \mathrm{x}_n - \eta \triangledown f(\mathrm{x}_n)$$
딥러닝에서는 학습을 할 때에 이 경사 하강법을 통해 손실 함수(Loss Fuction)가 최소가 될 때의 매개변수(parameter), 즉 가중치(weight)와 편향(bias)을 찾는다.
물론 기울어진 방향을 따라간다고 해서 무조건 최소값을 만날 수 있는 것은 아니다.
예를 들어 아래 그림과 같은 함수를 만난다면, 우연히 찾은 구덩이가 전체 중 가장 깊은 구덩이가 아닐 수도 있기 때문이다.
따라서 여기서 또 하나의 중요한 개념이 등장한다. 바로 학습률 learning rate 이다.
한 번의 학습으로 얼마만큼 학습해야 할지, 즉 보폭을 얼마나 크게 하면서 그래프 안에서 움직여야 우리가 궁극적으로 원하는 가장 깊은 웅덩이의 바닥으로 다가갈 수 있는지를 결정하는 것이다.
기호로는 보통 `\eta`로 표기하며, 1회에 해당하는 갱신하는 양을 `x_0`와 `x_1`에 대한 수식으로 나타내면 다음과 같다.
$$x_0 = x_0 - \eta \frac{\partial f}{\partial x_0}$$
$$x_1 = x_1 - \eta \frac{\partial f}{\partial x_1}$$
방금 '결정한다'는 표현을 쓴 것은 이 값을 미리 사람이 결정해야 하기 때문이다.
이렇게 사람이 직접 설정해주어야 하는 매개변수를 특별히 하이퍼 파라미터 hyper parameter 라고 한다.
이제 지금까지 설명한 경사 하강법을 직접 코드로 구현해보자.
# f는 최적화하려는 함수, init_x는 초깃값, lr은 learning rate, step_num은 반복 횟수를 의미
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x) # 수치미분을 해주는 함수
x -= lr * grad
return x
# f(x) = x[0]**2 + x[1]**2
init_x = np.array([-3.0, 4.0])
gradient_descent(f, init_x=init_x, lr=0.1, step_num=100)
# array([ -6.11110793e-10, 8.14814391e-10])
초기값을 `(-3.0, 4.0)`, 학습률을 `0.1`로 설정하여 경사 하강법에 적용한 결과, 최종 결과는 `(-6.1e^-10, 8.1e^-10)`으로, 거의 `(0, 0)`에 가까운 수를 얻을 수 있었다.
즉 실제 최솟값에 가까운, 거의 정확한 결과를 얻은 것이다.
만약 학습률을 다르게 설정하면 어떻게 될까?
# 학습률이 너무 큰 예: lr=10.0
gradient_descent(f, init_x=init_x, lr=10.0, step_num=100)
# array([ -2.58983747e+13, -1.29524862e+12])
# 학습률이 너무 작은 예: lr=1e-10
gradient_descent(f, init_x=init_x, lr=1e-10, step_num=100)
# array([ -2.99999994, 3.99999992])
위 결과와 같이 학습률이 너무 크면 큰 값으로 발산해버리고, 반대로 너무 작으면 초기값에서 거의 갱신되지 못하는 것을 확인할 수 있다.
아래 내용을 참고했습니다.
- 책 『밑바닥부터 시작하는 딥러닝』
'공부하며 성장하기 > 인공지능 AI' 카테고리의 다른 글
과적합(Overfitting)과 규제(Regularization) (2) | 2021.08.17 |
---|---|
앙상블 학습(Ensemble Learning)이란? (2) | 2021.08.15 |
수치 미분(Numerical differentiation)이란? (0) | 2021.07.27 |
퍼셉트론(perceptron)이란? (0) | 2021.07.21 |
판다스Pandas 가 뭔가요? (+ 와인 리뷰 데이터 탐색 맛보기) (1) | 2021.07.10 |