Code › codeit-ai-sprint

손실 함수에서 경사하강법까지

선형회귀의 가설 함수, 손실 함수, 편미분, 경사 벡터가 경사하강법으로 이어지는 흐름

지난번에는 선형회귀와 손실 함수를 정리했다. 집 크기 xx로 집 가격 yy를 예측하는 예제에서, 선형회귀는 데이터를 잘 설명하는 직선을 찾는 문제이고 손실 함수는 그 직선이 얼마나 틀렸는지 숫자로 평가하는 함수이다.

이번에는 경사하강법에 대해서 내가 이해한 바를 나름대로 정리해 보려고 하는데, 지난 내용을 다시 짚어가면서 전체 그림을 이어가면 좋을 것 같아서 다시 언급하면서 정리하려 한다.

손실 함수가 현재 직선을 평가하는 함수라면, 경사하강법(gradient descent)은 그 손실을 줄이기 위해 직선의 모양을 조금씩 바꾸는 방법이다. 그래서 경사하강법만 따로 떼어놓고 보기보다는, 가설 함수에서 손실 함수로 가고, 손실 함수에서 편미분과 경사 벡터로 넘어간 뒤, 마지막에 경사하강법으로 이어지는 순서가 딱 머리에 들어와야 한 번에 그림이 그려진다.


선형회귀에서 가설 함수는 직선이다. 익숙한 형태로 쓰면 y=ax+by = ax + b이고, 머신러닝 표기에서는 보통 이렇게 쓴다.

hθ(x)=θ0+θ1xh_\theta(x) = \theta_0 + \theta_1 x

여기서 xx는 입력 데이터이고, 집값 예측 예제에서는 집 크기다. hθ(x)h_\theta(x)는 현재 직선이 예측한 집 가격이며, θ0\theta_0θ1\theta_1은 직선의 위치와 기울기를 정하는 값이다.

학습 데이터의 xxyy는 이미 주어져 있다. 내가 바꿀 수 있는 것은 데이터가 아니라 직선의 모양이고, 직선의 모양은 θ0\theta_0θ1\theta_1이 정한다. 그래서 선형회귀에서 학습한다는 말은 결국 손실이 작아지도록 θ0\theta_0θ1\theta_1을 조정한다는 뜻으로 이해했다.


현재 θ\theta가 좋은지 나쁜지 판단하려면 점수가 필요하다. 그 점수를 계산하는 함수가 손실 함수(loss function)이고, 선형회귀에서는 평균 제곱 오차를 바탕으로 다음과 같이 쓴다.

J(θ)=12mi=1m(hθ(x(i))y(i))2J(\theta) = \frac{1}{2m}\sum_{i=1}^{m}\left(h_\theta(x^{(i)}) - y^{(i)}\right)^2

위 식은 모든 학습 데이터를 돌면서 현재 가설 함수의 예측값과 실제값의 차이를 구하고, 그 차이를 제곱한 뒤 평균을 낸다. 값이 작으면 현재 직선이 데이터를 잘 설명한 것이고, 값이 크면 예측이 빗나간 것이다.

처음에는 그래프를 집 크기와 집 가격의 2차원 그래프로 그려서 생각했는데, 손실 함수를 볼 때는 다른 그래프를 봐야 한다. 집 크기 xx와 실제 가격 yy는 이미 정해진 데이터이고, 손실 함수에서 움직이는 값은 θ0\theta_0θ1\theta_1이다.

즉, 가설 함수의 그래프는 xx를 넣으면 예측 가격이 나오는 직선이고, 손실 함수의 그래프는 θ0\theta_0θ1\theta_1을 넣으면 손실값이 나오는 그래프다. 지금 줄이고 싶은 값은 예측 가격 자체가 아니라 손실값이므로, 앞으로 미분해야 하는 대상도 가격 그래프가 아니라 손실 함수 J(θ)J(\theta)다.


손실을 줄이려면 현재 위치에서 어느 방향으로 움직이면 손실이 줄어드는지 알아야 한다. 무작정 모든 θ0\theta_0, θ1\theta_1 조합을 넣어볼 수도 있겠지만, 값의 범위가 넓어지면 현실적인 방법이 아니다. 그래서 현재 위치에서 손실 함수가 어느 방향으로 얼마나 변하는지 계산한다.

여기서 편미분이 다시 나온다. 손실 함수 J(θ)J(\theta)θ0\theta_0θ1\theta_1에 따라 값이 달라지기 때문에, 한 번에 둘을 모두 움직이면 어떤 값이 손실에 얼마나 영향을 줬는지 분리하기 어렵다.

θ0\theta_0에 대해 편미분할 때는 θ1\theta_1을 잠깐 고정하고, θ1\theta_1에 대해 편미분할 때는 θ0\theta_0을 고정한다. 이렇게 하면 각 파라미터를 아주 조금 움직였을 때 손실값이 어느 방향으로 얼마나 변하는지 따로 볼 수 있다.

Jθ0,Jθ1\frac{\partial J}{\partial \theta_0}, \quad \frac{\partial J}{\partial \theta_1}

이 값들은 현재 θ0\theta_0, θ1\theta_1 위치에서 손실 함수의 기울기를 각각의 방향으로 나눠서 본 결과다. θ0\theta_0 쪽으로 움직이면 손실이 얼마나 변하는지, θ1\theta_1 쪽으로 움직이면 손실이 얼마나 변하는지를 따로 계산한 셈이다.


각 파라미터에 대한 편미분 값을 모으면 경사 벡터(gradient vector)가 된다.

J(θ)=[Jθ0Jθ1]\nabla J(\theta) = \begin{bmatrix} \dfrac{\partial J}{\partial \theta_0} \\[0.8em] \dfrac{\partial J}{\partial \theta_1} \end{bmatrix}

이 벡터는 현재 위치에서 손실 함수가 가장 빠르게 증가하는 방향을 가리키며, 손실 함수 입장에서는 오르막 방향이다. 우리가 원하는 것은 손실을 키우는 일이 아니라 줄이는 일이므로, 경사 벡터가 가리키는 방향 그대로 가지 않고 반대 방향으로 움직인다. 이때 등장하는 식이 경사하강법이다.

θ0new=θ0oldαJθ0\theta_0^{new} = \theta_0^{old} - \alpha \frac{\partial J}{\partial \theta_0} θ1new=θ1oldαJθ1\theta_1^{new} = \theta_1^{old} - \alpha \frac{\partial J}{\partial \theta_1}

여기서 α\alpha는 학습률(learning rate)이다. 편미분 결과가 어느 방향으로 움직여야 하는지 알려준다면, 학습률은 한 번에 얼마나 움직일지를 정한다. 너무 작으면 손실이 천천히 줄어들고, 너무 크면 최소점을 지나쳐 오히려 불안정해질 수 있다.

위 식은 기존 θ\theta 값에서 편미분 결과에 학습률을 곱한 만큼 빼서, 새로운 θ\theta 값으로 다시 잡는다는 뜻이다. 이렇게 하면 손실이 줄어드는 방향으로 파라미터가 조금 이동한다.

이 갱신을 여러 번 반복하면 θ0\theta_0θ1\theta_1이 조금씩 바뀌고, 그에 따라 가설 함수의 직선도 바뀐다. 직선이 바뀌면 예측값이 바뀌고, 예측값이 바뀌면 손실 함수의 값도 다시 계산된다. 경사하강법은 이 과정을 반복하면서 손실이 작아지는 방향으로 파라미터를 조정하는 방법이다.


이번 정리에서 개인적으로 구분이 필요했던 것은 그래프의 분리였다. 집 크기와 집 가격을 나타내는 가설 함수의 그래프가 있고, θ\theta와 손실값을 나타내는 손실 함수의 그래프가 따로 있다. 경사하강법에서 미분하는 대상은 첫 번째 그래프가 아니라 두 번째 그래프이며, 선형회귀 예제에서는 손실값, θ0\theta_0, θ1\theta_1로 이루어진 3차원 그래프가 된다. 입력변수가 많아지면 조정해야 하는 θ\theta도 늘어나기 때문에 차원도 함께 올라간다.

그래서 흐름을 다시 쓰면 이렇다. 먼저 가설 함수가 예측값을 만들고, 손실 함수가 그 예측이 얼마나 틀렸는지 계산한다. 그 다음 손실 함수를 각 θ\theta에 대해 편미분해서 현재 위치의 변화 방향을 구하고, 그 편미분 값들을 경사 벡터로 모은다. 마지막으로 경사 벡터의 반대 방향으로 θ\theta를 조금씩 움직이면 손실이 줄어드는 쪽으로 직선이 조정된다.

아직 실제 데이터에 대해 손으로 미분식을 전개하거나, 학습률을 어떻게 잡아야 하는지까지는 깊게 다루지 않았다. 지금 단계에서는 경사하강법을 앞의 내용과 엮어 전체 그림으로 보는 것이 중요했고, 결국 손실 함수의 값을 줄이기 위해 파라미터를 반복해서 업데이트하는 절차로 이해했다.