[4/18] 경사하강법
해당 시리즈는 프로그래밍 언어 중 하나인 줄리아(Julia)로 딥러닝(Deep learning)을 구현하면서 원리를 설명합니다.
이전 글에서 우리는 손실 함수에 대해서 살펴보았다. 손실 함수는 신경망을 평가하는 판단지표로서 사용되며, 손실 함수의 값을 줄이는 방향으로 신경망을 학습시킨다고 하였다. 다른 말로, 좋은 신경망 모델은 손실 함수 값이 작아야 한다. 그렇다면 이쯤에서 "어떻게 손실 함수의 값을 줄일 수 있을까?"라는 의문이 들 것이다. 이것이 바로 최적화의 문제이다. 이번 글에서는 손실 함수를 최소화하는 경사하강법에 대해서 살펴볼 것이다.
경사하강법이란
경사하강법은 신경망 학습에서 손실함수의 값을 최소화하는 가중치와 편향을 찾는 최적화 알고리즘이다. 신경망 모델의 학습 과정을 순서대로 나열하면 다음과 같다.
- 임의의 값을 가진 가중치와 편향을 생성한다.
- 신경망 계산을 한다.
- 손실 함수로 출력층의 결과인 예측값과 정답을 비교한다.
- 예측값과 정답의 격차를 줄이기 위해 가중치와 편향을 조정한다.
- 2~4의 과정을 반복하여 손실 함수의 값을 줄인다.
위의 순서 중에서 경사하강법은 4, 5번 과정을 진행하는 알고리즘이다. 그렇다면 경사하강법의 작동 원리는 무엇인가? 바로 미분이다. 경사하강법은 손실 함수의 미분값(기울기)을 도출하여 가중치와 편향에서 빼주는 방식으로 적절한 가중치와 편향을 찾는다.
미분
최적화된 가중치와 편향을 찾는 방법은 바로 손실 함수 수식의 "기울기"이다. 손실 함수 그래프에서 가장 아래에 있는 점(최솟점)을 찾기 위해서는 임의의 점에서 기울기를 구해 이동할 방향을 잡아야 한다. 그렇다면 기울기를 구하는 방법은 무엇인가? 바로 미분이다.
미분은 순간변화량을 나타내며, 수식은 다음과 같다.
\[f'(x)=\frac{df(x)}{dx}=\lim_{h\to0} \frac{f(x+h)-f(x)}{h}\]
위 수식은 \(f(x)\)에서 \(x\)가 아주 작은 값인 \(h\)만큼 이동했을 때의 변화량을 구하는 식이며, 이는 \(x\)의 이동이 \(f(x)\)를 얼마나 변화시키는지를 나타낸다.
하지만 위의 식을 컴퓨터에서 구현하는 것은 반올림오차 문제때문에 기술적으로 어렵다.
1 |
|
수학적 원리로는 아주 미세한 값의 차이가 나지만, 컴퓨터는 이를 0으로 처리해버린다. 이런 문제로 보통 \(h\)의 값을 \(10^{-4}\)로 사용한다. 또한 위의 코드는 \((x+h)\)와 \(x\)사이의 기울기이기에 약간의 오차가 발생한다. 이런 오차를 줄이기 위해서 사용하는 방식이 중앙 차분이다.
\[f'(x)=\frac{dy}{dx}=\lim_{h\to0} \frac{f(x+h)-f(x-h)}{2h}\]
중앙 차분이란 \(f(x+h)\)에서 \(f(x-h)\)의 값을 뺀 후 \(2h\)로 나누는데, 이는 \((x+h)\)와 \((x-h)\) 사이의 기울기를 구함으로써 보다 정확한 값을 얻을 수 있다. 아래의 코드는 기존의 미분 공식과 중앙차분을 적용한 값의 비교이다.
1 |
|
\(f(x)=x^2\)이기 때문에 이를 미분하면 \(f'(x)=2x\)이다. 즉, \(x\)가 3일 때 \(f'(x)\)는 6인 것이다. 이를 바탕으로 위의 코드를 보면 약간의 차이로 중앙 차분이 적용된 미분 공식이 6에 가까운 것을 확인할 수 있다.
위의 미분 공식으로 코드로 구현하면 다음과 같다.
1 |
|
편미분
미분 파트에서는 \(f(x)=x^2\)과 같은 변수가 1개인 경우만을 살펴보았다. 하지만 대부분의 손실 함수들은 신경망 디자인에 따라서 변수의 개수가 변동하는 다변수 함수이다. 따라서 실제 사용되는 손실 함수들을 미분해야 하며, 그 방법을 편미분이라고 한다.
편미분이란 변수가 2개 이상인 수식에서 각각의 변수들을 기준으로 미분하는 것을 말한다. 다음 식을 편미분해보자.
\[f(x,y)=x^2+2xy+y^2\]
위 식에서 편미분 \(\frac{\partial f}{\partial x}\) 와 \(\frac{\partial f}{\partial y}\)를 수식으로 나타내면 아래와 같다.
\(\frac{\partial f}{\partial x} = 2x+2y\)
\(\frac{\partial f}{\partial y} = 2y+2x\)
편미분은 각각의 변수를 기준으로 하여 나머지 변수는 상수화하여 미분을 진행하면 된다. 위에서 편미분 \(\frac{\partial f}{\partial x}\)은 \(x\)만을 변수로 하고 \(y\)를 상수화 하였으며, 편미분 \(\frac{\partial f}{\partial y}\)은 \(y\)만을 변수로 하여 \(x\)을 상수화하였다.
위의 수식들을 코드로 구현해보자.
1 |
|
먼저 필요한 변수들을 임의로 정의한다. 편미분 \(\frac{\partial f}{\partial x}\)부터 비교하면 다음과 같다.
1 |
|
코드 결과에서 두 개의 미분 값이 거의 유사함을 확인할 수 있다. 다음으로 편향을 기준으로 하는 편미분 \(\frac{\partial f}{\partial y}\)도 확인해보자.
1 |
|
기울기
지금까지 우리는 경사하강법에서 사용되는 미분에 대해 알아보았다. 손실 함수는 보통 변수가 2개 이상이기 때문에 편미분을 통해 각각의 변수 값들을 구하며, 이전 파트에서 변수 하나씩의 값을 구하였다. 하지만 실제 신경망에서 작동하는 기울기(미분값)은 한번에 도출되어야 한다. 따라서 다변수 함수를 한번에 미분해주는 코드를 구현하고자 한다.
1 |
|
이제 위의 예시를 다시 사용하여 값이 잘 도출되는지 확인해보자.
1 |
|
편미분 파트의 예시와는 다르게 두 개의 변수를 한번에 계산하기 위해 배열화하였다. 또한 함수 수식에서 각 변수들의 기호를 배열 g
의 인덱스로 변경하였다.
1 |
|
각자 편미분한 값과 같은 결과가 출력된다.
경사하강법
이제 기울기를 통해 변수들을 최적화하는 알고리즘인 경사하강법을 살펴볼 것이다. '경사하강법(gradient_descent)'은 함수의 미분을 통해 얻은 기울기를 사용해 기존 변수 값을 갱신하여 오차를 점점 줄여나가는 알고리즘이다. 먼저 경사하강법의 수식을 살펴보자.
\[W:=W-\eta \frac{\partial f}{\partial W}\] \[B:=B-\eta \frac{\partial f}{\partial B}\]
위 식에서 기호 \(\eta\)는 학습률을 나타낸다. 학습률(learning rate)이란 기울기를 어느 정도 반영할 것인가를 나타낸다. 보통 학습률은 미리 정하며, \(0.01\)이나 \(0.001\) 등을 사용한다. 위의 수식을 한 문장으로 정리한다면 다음과 같다.
"이전 변수에서 학습률을 곱한 기울기(편미분)를 뺀 결과가 새로운 변수이며, 이를 여러 번 반복하여 오차를 0으로 만드는 적절한 변수를 반한한다."
경사하강법의 코드는 다음과 같다.
1 |
|
위 코드는 매개 변수로 함수 공식인 f
, 변수의 초기 값인 x
, 학습률인 lr
,반복 횟수인 step_num
을 받는다. 이제 위에서 사용했던 예시를 가지고 경사하강법을 실행해보자. 신경망 학습에서 경사하강법 결과에 대한 이해는 필수적이기에 이번 예시는 신경망에서의 경사하강법이라고 전제하고 설명할 것이다.
1 |
|
위의 식을 바탕으로 오차를 계산하면 다음과 같다.
1 |
|
오차가 169
로 도출되었다. 손실 함수의 값이 매우 큰 상태이다. 즉, 아주 좋지 못한 신경망이다. 이제 경사하강법으로 최적화된 가중치와 편향을 찾아보자.
1 |
|
경사하강법의 결과가 나왔다. 확실히 \(5\)과 \(8\)보다는 매우 작은 값이 출력되었다. 손실 함수 수식에 해당 결과를 대입함으로써 정말 최적화된 값인지 확인해보고자 한다.
1 |
|
오차값이 거의 0에 도달하였다. 최적화에 성공한 것이다. 신경망은 이런 경사하강법을 통해서 적절한 가중치와 편향을 찾는다. 이제는 신경망을 구현하기 위한 모든 재료들을 살펴보았다. 다음 포스트에서는 지금까지 만들었던 함수들을 바탕으로 신경망을 직접 구현해보자.