Optimizer 역할
옵티마이저(optimizer)는 머신 러닝에서 비용 함수를 최소화 하거나 목적 함수를 최대화 하기 위해 모델의 여러 파라미터들을 업데이트하는 데 사용하는 알고리즘이다.
이때 비용 함수는 손실 함수나 loss function, cost function이라고도 부르는데 모델의 입력에 대한 출력과 실제 타겟 간의 차이를 측정하고, 목적 함수(objection function)는 최대화할 양(accuracy, recall, or precision 등)을 말한다.
옵티마이저는 모델의 파라미터를 조금씩 업데이트하며 작동하는데 이러한 단계는 비용 함수나 목적 함수의 기울기에 의해 결정된다.
다음 예를 보고 옵티마이저를 이해해보자.
당신이 산 위에 있다고 내려가는 길을 찾고 싶어한다고 상상해보자.
가장 빠르게 내려가는 길을 찾고 싶다고 할 때, 당신은 아래 방향으로 가장 가파른 경사를 찾는 것으로 첫 발을 내딛을 수 있다.
이제 그 방향으로 한 걸음을 걸은 다음 그 지점에서 다시 아래 방향으로 가장 가파른 경사를 찾는다.
그 작업을 산을 내려갈 때까지 계속 반복한다. (혹은 계곡에 갇힐 때까지...)
이제 이 이야기를 머신 러닝에 적용시켜보자.
아주 기본적인 옵티마이저에 대한 이야기이기도 한데 우리는 모델의 파라미터로 이루어진 산의 어떤 지점 \(\overrightarrow{w}\)에 있다고 가정해보자.
이제 그 지점의 비용 함수 \(L\)에 대한 모델의 경사(gradient)을 계산한다. 즉, \( {\nabla_\vec{w}} L \)을 계산한다.
가장 가파른 경사(위로 가파른 경사)의 반대 방향으로 한 걸음을 내딛는다.
즉, 한 걸음 내딛는 다는 것은 파라미터를 업데이트 한다는 이야기다.
이 작업을 수렴할 때까지 반복한다.
이것이 사실 Gradient Descent(GD, 경사 하강법)이다.
보통 수천 수백만 개의 데이터 포인트가 있기 때문에 전체 비용을 계산한 후 단지 "한 걸음"만 내딛는 것은 컴퓨팅 파워의 낭비이다.
그래서 위와 같은 방법은 잘 쓰이지 않고 데이터들의 일부인 mini batch에서 기울기를 계산하고 파라미터를 업데이트 하는데 이를 Stochastic Gradient Descent(SGD, 확률적 경사 하강법)이라고 부른다.
옵티마이저는 이 외에도 다양한 종류가 있고 각 옵티마이저마다 모델의 학습에 큰 영향을 준다.
각 옵티마이저마다 장단점이 다르고 특정 유형에 대해 효과적인 옵티마이저가 있다.
예를 들어, SGD는 비교적 간단한 모델과 데이터 세트에서 잘 작동하는 반면 Adam이나 RMSProp와 같은 옵티마이저는 복잡하고 큰 문제에서 효과적일 수 있다.
이제 옵티마이저의 종류와 특징을 알아보자.
이번 글에서는 수학을 다루지 않을 생각이다.
대신 수학적인 내용을 아주 잘 정리해둔 참고할 만한 글의 링크를 첨부한다.
https://www.ruder.io/optimizing-gradient-descent/
Momentum
이름 그대로 Momentum은 SGD에 더해 관성을 주는 것이라고 볼 수 있다.
SGD에 모멘텀이 추가되면 그레디언트의 방향이나 크기가 변하더라고 옵티마이저가 이전 단계의 영향도 받아 마치 SGD와 이전 스텝의 벡터 합처럼 이동한다.
이렇게 하면 옵티마이저가 더 빠르게 수렴하고 도중 로컬 미니마에 빠져도 관성을 통해 빠져나올 수 있다.
보통 이전 스텝이 현재 스텝에 얼마나 많은 모멘텀 즉, 가중치를 주는 지는 코드에서 각자 설정할 수 있는데 보통 0.9에서 0.999사이의 값을 사용한다.
PyTorch 코드로는 SGD에 모멘텀 파라미터를 추가해서 간단하게 쓸 수 있다.
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
NAG
NAG는 Nesterov Accelerated Gradient Descent의 줄임말로 그래디언트 디센트(GD)을 수정한 옵티마이저이다.
NAG는 모멘텀과 미리보기 개념을 도입하여 GD 알고리즘을 개선한다.
GD에서 옵티마이저는 기울기에 따라 가중치를 업데이트 하고, 모멘텀은 과거 그레디언트를 기하급수적으로 감소시키는 이동 평균인데 NAG는 여기서 한 걸음을 더 나아가 미리보기를 한다.
이 말은 그레디언트를 계산하기 전에 먼저 모멘텀으로 움직이고, 그 자리에서 그레디언트를 계산한 다음 스텝을 밟는 것이다.
PyTorch 코드로는 SGD에 nestterov 파라미터를 추가해서 간단하게 쓸 수 있다.
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=momentum, nesterov=True)
Adagrad
Adagrad(Adaptive Gradient)는 과거의 그레디언트를 기반으로 모델의 각 파라미터에 대한 학습률을 조정한다.
어떤 방식으로 조정하냐면 지금까지 많이 업데이트된 파라미터는 스텝 사이즈를 줄여 적게 변화하게 하고, 지금까지 적게 업데이트된 파라미터는 스텝 사이즈를 키워 큰 업데이트를 시키는 것이다.
왜냐하면 많은 업데이트를 겪은 파라미터는 이미 최적의 값에 근접했을 것이고 그렇지 않은 파라미터는 아직 최적의 값과 거리가 있을 수 있기 때문이다.
이 옵티마이저는 고차원의 데이터를 다룰 때 특히 유용하다.
PyTorch로는 다음과 같이 쓸 수 있다.
optimizer = optim.Adagrad(net.parameters(), lr=0.01)
AdaDelta
AdaDelta는 Adagrad의 확장으로 다소 공격적으로 스텝 사이즈를 줄이려는 경향의 Adagrad의 한계를 극복하기 위한 옵티마이저이다.
Gradient를 수정하는 모멘텀이나 NAG와는 달리 Learning rate을 수정하는 Adagrad, AdaDelta, 그리고 곧 살펴볼 RMSProp은 수학적인 내용이 많이 때문에 정확한 컨셉과 설명이 궁금하면 위의 링크를 꼭 찾아보자.
PyTorch 코드는 다음과 같이 쓸 수 있다.
optimizer = optim.Adadelta(model.parameters(), lr=1.0, rho=0.9, eps=1e-6)
여기서 rho는 감쇠율, eps는 수치적인 안정성을 위한 아주 작은 수이다.
RMSProp
RMSProp도 Adagrad 알고리즘을 수정한 것으로 학습 속도를 그레디언트의 제곱의 이동 평균으로 나누는 적응형 lr(learning rate)알고리즘이다.
이러한 방식으로 Adagrad와 비슷하게 각 파라미터에 대해 자주 변한 것은 lr을 줄이고 드물게 변한 파라미터는 lr을 키운다.
PyTorch 코드로는 다음과 같이 쓸 수 있다.
optimizer = optim.RMSprop(model.parameters(), lr=0.001)
Adam
아담 옵티마이저(Adam optimizer)는 기계 학습에 일반적으로 사용되는 최적화 알고리즘의 일종이다.
"Adam"이라는 이름은 "Adaptive Moment Estimation"의 약자로, 이 옵티마이저가 그레디언트의 모멘트 추정치를 기반으로 학습 속도를 조정한다는 사실을 의미한다.
Adam은 SGD의 확장이다. SGD와 마찬가지로 Adam 옵티마이저는 비용 함수를 최소화하기 위해 모델의 가중치를 반복적으로 업데이트한다.
그러나 Adam은 수렴 속도를 높이는 데 도움이 될 수 있는 두 가지 새로운 기능이 추가된다.
첫 번째 새로운 기능은 적응형(adaptive) 학습 속도의 사용이다.
학습 속도는 옵티마이저가 모델의 가중치를 얼마나 빨리 조정하는지를 결정하는 변수이다.
보통 SGD에서 학습률은 훈련 과정 전반에 걸쳐 고정되지만 Adam에서 학습 속도는 그레디언트의 첫 번째와 두 번째 순간의 추정치를 기반으로 조정된다.
두 번째 새로운 특징은 모멘텀의 사용이다.
앞에서 살펴본 것처럼, 운동량은 그래디언트가 방향을 바꾸더라도 옵티마이저가 같은 방향으로 계속 움직이게 하는 관성을 말한다.
이를 통해 로컬 미니마에 갇히는 것을 방지할 수 있다.
두 가지를 종합하면, Adam Optimizer는 적응형 학습 속도와 모멘텀을 사용하여 트레이닝의 수렴 속도를 높이는 알고리즘이다.
여러 장점을 가진 Adam은 머신러닝에서 유행하는 옵티마이저로 뭘 써야할지 모르겠다면 일단 Adam을 써보는 것을 추천한다.
optimizer = optim.Adam(net.parameters(), lr=0.001)
오늘은 그동안 미뤄둔 이야기였던 optimizer에 대한 이야기를 아주 간단하게 했다.
다음에는 활성화 함수(activation function)에 대한 이야기를 해보자.