[네이버 AI 부스트캠프 5기] 경사하강법
[네이버 AI 부스트캠프 5기] 경사하강법
수업을 듣다 보니 경사하강법이라는 주제를 꽤 중요하게 다루는 것 같다. 그래서 경사하강법에 대해 이해한 내용을 정리해보려고 한다.
경사하강법을 다루기 전에, 미분에 대해 먼저 짚고 넘어가야 한다.
미분을 통해 우리는 함수 f에 대해 주어진 점 (x,f(x))에서 접선의 기울기를 구할 수 있다. 그리고 접선의 기울기를 알면, 어느 방향으로 움직일 때 함수값이 증가하는지, 혹은 감소하는지 알 수 있다. 이러한 원리를 이용하여 경사상승법은 미분값을 더해가며 극대값의 위치를 구하는 방법이고, 경사하강법은 미분값을 빼가며 극소값의 위치를 구하는 방법이다.
예를 들어, 위의 그래프에서 x의 미분계수가 양수이므로, x의 값을 증가시키면 극대값에 가까워질 것이다. 반대로 x의 값을 감소시키면 극소값에 가까워질 것이다. 그림은 극소값을 향해 가까워지고 있으므로 경사하강법의 예시라고 볼 수 있다. 만약 경사하강법을 사용한 결과로 미분계수가 0인 극소값에 도달하게 된다면, 업데이트가 종료된다. 하지만 극소값이 최소값과 일치하지 않는 경우도 많으므로, 주의해야 한다.
조금 더 자세한 원리는 다음과 같다.
init : 시작점
gradient() : 미분 계산 함수
epsilon : 함수 종료 조건
lr_rate : 학습률
var = init
grad = gradient(var)
while (abs(grad) > epsilon) :
var = var - lr_rate * grad
grad = gradient(var)
컴퓨터로 계산한 미분값이 정확히 0이 되는 것은 불가능하다. 아마 소수점 표현 방식의 한계와 관련이 있는 것 같다. 따라서 epsilon이라는 아주 작은 값을 만들어 미분값이 거의 0이 되는 상황에서 while 문을 종료시킨다. 현재의 위치에서 학습률과 미분계수를 곱한 값을 빼는 방식으로 경사하강법을 구현하였고, 현재 위치를 바탕으로 미분 계수를 다시 업데이트하는 과정을 반복한다.
지금까지는 변수가 하나인 상황을 설명하였는데, 만일 변수가 벡터인 다변수함수가 등장한다면 어떻게 해야할까? 편미분을 사용하면 된다.
편미분이란, 특정 변수만을 변수로 취급하고, 다른 변수는 상수로 취급하여 미분하는 것을 의미한다. 처음에 편미분이라는 말을 보고 이게 뭔 소리인가 했는데 사실 고등학교때 이미 많이 해본 미분이었다.
다음과 같은 식이 있다고 가정해보자.
이 식을 x에 대하여 미분한 결과는 y를 상수로 취급하므로 2x일 것이고, y에 대하여 미분한 결과는 4y일 것이다. 이렇게 각 변수별로 편미분을 계산하여 만든 그레디언트 벡터를 이용한다. 그레디언트 벡터의 모습은 다음과 같다.
이상한 기호가 많아 당혹스러웠지만 세세히 뜯어보면 사실 별 거 없다. 앞의 역삼각형은 '나블라'라고 읽고 벡터의 미분을 표기하는 방법이라고 한다. 괄호 안의 친구들은 f를 x1에 대해 미분, x2에 대해 미분, 결국 편미분을 하라는 의미다. f(x,y)가 주어진 상황에서 그레디언트 벡터를 구하면,
이런 모습이다. 경사 하강법을 사용하는 상황에서는 극소값을 구해야 하니까,
이렇게 마이너스를 붙여서 사용할 수 있겠다.
그레디언트 벡터의 의미를 시각화하여 나타내면, 오른쪽의 화살표들을 뜻한다고 볼 수 있다. 극소값으로 향하는 길을 알려주는 녀석들. 코드로 살펴보면 다음과 같다.
init : 시작점
gradient() : 미분 계산 함수
epsilon : 함수 종료 조건
lr_rate : 학습률
var = init
grad = gradient(var)
while (norm(grad) > epsilon) :
var = var - lr_rate * grad
grad = gradient(var)
위에서 나왔던 코드와 거의 동일하지만 while문의 종료 조건이 abs에서 norm으로 바뀌었다. 여기서 말하는 norm은 유클리드 거리, 다시 말해 L2 norm이다. 당연히 L1 norm일 리가 없을테니.
경사하강법을 통해 선형회귀의 계수를 구할 수도 있다. 선형회귀식의 오차를 최소화하는 과정에서 사용된다고 이해하면 될 것 같다. 우리가 최소화시킬 선형회귀의 목적식은,
이렇게 생겼다. (물론 저 식의 제곱을 최소화하여도 같은 결론에 도달할 수 있다.) 저 식을 미분하게 되면,
이렇게 된다. 저 중 k번째 항을 실제로 미분해보면...
이런 결과가 나오고, 결과적으로 다음과 같은 생각보다 깔끔한(?) 결과를 얻을 수 있다.
그렇다면, 이제 목적식을 최소화하는 베타를 구하는 경사하강법의 식을 알아낼 수 있다. 원래 아래처럼 생겼던 식의 norm을 위에서 구한 식으로 바꿔주면, 답이 나온다! 기호가 많아서 복잡해졌지만, 처음의 식과 맥락은 같다. (람다는 학습률을 의미하고 뒤의 나블라는 gradient(var)의 역할과 동일하니까.) 람다 앞의 마이너스(-) 부호가 플러스(+)로 바뀐 것에 주의하자.
위에서 norm의 제곱을 최소화하여도 결론은 같다고 하였는데, 식의 결과는 조금 더 간단해져서 다음과 같다.
코드로 보면 아래와 같고, 종료 조건이 일정 횟수로 변한 것을 제외하면 같은 메커니즘이다. (@는 numpy에서 합성곱을 의미한다)
for t in range(T) :
error = y - X @ beta
grad = -Transpose(X) @ error
beta = beta - lr_rate * grad
수학은 이제 끝났다. 경사하강법은 미분가능하고, 볼록함수인 상황에서는 (convex하다고 표현) 적절한 학습률, 학습횟수를 선택했을 때 수렴이 보장되어 있다. 경사하강법은 미분을 사용하니까 당연히 미분 가능한 함수에 적용해야 하고, 볼록해야 극소값을 가질테니 당연하다. 선형회귀의 경우에도 목적식이 회귀계수에 대해 볼록함수의 형태를 가지기에(회귀계수에 따라 오차를 함수로 그리면 볼록한 함수의 형태를 띈다) 역시 수렴이 보장된다. 하지만 비선형회귀의 경우에는 목적식이 볼록하지 않을 수 있으므로 항상 수렴을 보장하지는 않는다.
볼록이 아닌(non-convex한) 함수의 경우에는 확률적 경사하강법(SGD, Stochastic Gradient Descent)라는 방법을 이용한다. 모든 데이터를 사용하는 경사하강법과 달리, 데이터를 일부만 활용하여 업데이트하는 방식이다. 당연히 모든 단점을 해결할 수는 없지만, 딥러닝에서는 SGD가 경사하강법보다 낫다고 한다. 또한 일부 데이터만 활용하기에 연산이 용이하다. 메모리를 덜 잡아먹는다거나 그런 방면으로 장점이 있고. 데이터 뭉치를 뽑아서 그레디언트를 근사하여 계산하고, 다음 데이터 뭉치를 뽑아서 다음 계산으로 업데이트,,,,, 반복이다.