옵티마이저

src/content/documents/deep-learning/optimizer-sgd-to-adam.json

SGD: 현재 기울기만 보면 지그재그로 흔들린다

이전 글에서 본 기본 경사하강법(SGD, Stochastic Gradient Descent)은 매 순간 현재 위치의 기울기만 보고 이동한다. 이 방식은 단순하지만, 경사면이 울퉁불퉁하면 잘 흔들리고 완만한 구간에서는 느리게 움직인다. Optimizer는 “지금까지 어느 방향으로 움직였는지”, “그 방향이 얼마나 들쭉날쭉했는지” 같은 정보를 추가로 활용해서 더 빠르고 안정적으로 수렴하도록 경사하강법을 개선한 알고리즘들이다.

모든 절에서 시작점 (x0,y0)=(4,3)(x_0,y_0)=(4,3)과 목적 함수를 같게 두고 이동 경로만 비교한다.

f(x,y)=x2+10y2,f(x,y)=(2x,20y)f(x,y)=x^2+10y^2,\qquad \nabla f(x,y)=(2x,20y)
SGD: 가파른 y축을 오가며 지그재그로 수렴
-4-3-2-101234-3-2-10123x (완만한 축)y (가파른 축)
시작시작(0,0)(0,0)y축의큰기울기때문에양쪽으로진동y축의 큰 기울기 때문에 양쪽으로 진동SGD(η=0.08)SGD (η=0.08)최솟값최솟값

방향을 기억하는 Momentum

Momentum은 현재 기울기만 보지 않고, 이전까지 이동해온 방향의 누적(관성)을 함께 반영한다. 공이 언덕을 굴러 내려가듯, 계속 같은 방향으로 이동해왔다면 그 방향으로 가속이 붙고, 방향이 자주 바뀌는 축에서는 진동이 상쇄되어 줄어든다.

vt=βvt1+(1β)f(θt),θt+1=θtηvtv_t=\beta v_{t-1}+(1-\beta)\nabla f(\theta_t),\qquad \theta_{t+1}=\theta_t-\eta v_t

여기서 v는 지금까지 기울기의 지수이동평균(관성), β는 이전 방향을 얼마나 유지할지 정하는 값(보통 0.9 근처)이다.

SGD vs Momentum: 지그재그와 관성의 차이
-4-3-2-101234-3-2-10123x (완만한 축)y (가파른 축)
SGDSGDMomentumMomentum최솟값최솟값

상황별 보폭을 조절하는 Adagrad·RMSProp

Adagrad와 RMSProp은 반대 아이디어를 쓴다. 자주 크게 흔들린 방향(기울기 변화가 컸던 파라미터)은 보폭을 줄이고, 잘 움직이지 않은 방향은 보폭을 키운다. 파라미터마다 학습률을 다르게 자동 조절하는 셈이다. RMSProp은 Adagrad가 학습이 진행될수록 보폭이 지나치게 작아지는 문제를 지수이동평균으로 보완한 버전이다.

rt=ρrt1+(1ρ)gt2,θt+1=θtηgtrt+ϵr_t=\rho r_{t-1}+(1-\rho)g_t^2,\qquad \theta_{t+1}=\theta_t-\eta\frac{g_t}{\sqrt{r_t}+\epsilon}
SGD vs RMSProp: 축별 보폭 조절
-4-3-2-101234-3-2-10123x (완만한 축)y (가파른 축)
y축의보폭을줄여진동억제y축의 보폭을 줄여 진동 억제SGDSGDRMSPropRMSProp최솟값최솟값

Adam = Momentum + RMSProp

Adam(Adaptive Moment Estimation)은 이름 그대로 두 아이디어, 방향을 기억하는 Momentum과 상황별 보폭을 조절하는 RMSProp/Adagrad 계열을 결합한다. 기울기의 1차 모멘트(평균, 방향)와 2차 모멘트(제곱의 평균, 변동 크기)를 각각 추정해 더 빠르고 흔들림 없이 수렴하도록 설계됐다. 현재 딥러닝에서 가장 널리 쓰이는 기본 Optimizer다.

mt=β1mt1+(1β1)gt,vt=β2vt1+(1β2)gt2m_t=\beta_1m_{t-1}+(1-\beta_1)g_t,\qquad v_t=\beta_2v_{t-1}+(1-\beta_2)g_t^2
θt+1=θtηm^tv^t+ϵ\theta_{t+1}=\theta_t-\eta\frac{\hat m_t}{\sqrt{\hat v_t}+\epsilon}
Momentum·RMSProp·Adam의 이동 경로 비교
-4-3-2-101234-3-2-10123x (완만한 축)y (가파른 축)
Adam:방향과축별보폭을동시에반영Adam: 방향과 축별 보폭을 동시에 반영MomentumMomentumRMSPropRMSPropAdamAdam최솟값최솟값

코드로 같은 이동 경로 재현하기

앞의 그래프와 같은 함수·시작점·학습률을 사용해 SGD와 Adam의 초기 4단계를 재현한다.

import math

def grad(point):
    x, y = point
    return [2 * x, 20 * y]

def sgd(steps, lr=0.08):
    point = [4.0, 3.0]
    path = [point.copy()]
    for _ in range(steps):
        g = grad(point)
        point = [p - lr * dg for p, dg in zip(point, g)]
        path.append(point.copy())
    return path

def adam(steps, lr=0.3, beta1=0.9, beta2=0.999):
    point, m, v = [4.0, 3.0], [0.0, 0.0], [0.0, 0.0]
    path = [point.copy()]
    for t in range(1, steps + 1):
        g = grad(point)
        m = [beta1 * a + (1 - beta1) * b for a, b in zip(m, g)]
        v = [beta2 * a + (1 - beta2) * b**2 for a, b in zip(v, g)]
        m_hat = [value / (1 - beta1**t) for value in m]
        v_hat = [value / (1 - beta2**t) for value in v]
        point = [p - lr * a / (math.sqrt(b) + 1e-8)
                 for p, a, b in zip(point, m_hat, v_hat)]
        path.append(point.copy())
    return path

def rounded(path):
    return [tuple(round(value, 3) for value in point) for point in path]

print("SGD :", rounded(sgd(4)))
print("Adam:", rounded(adam(4)))
SGD : [(4.0, 3.0), (3.36, -1.8), (2.822, 1.08), (2.371, -0.648), (1.991, 0.389)]
Adam: [(4.0, 3.0), (3.7, 2.7), (3.401, 2.401), (3.103, 2.105), (2.808, 1.812)]

SGD의 y좌표는 3 → -1.8 → 1.08처럼 부호가 반복해 바뀌지만, Adam은 초기에 x와 y를 함께 줄이며 대각선에 가까운 경로로 이동한다. 단, 이 한 예시의 몇 단계만으로 어떤 Optimizer가 항상 더 빠르다고 결론 낼 수는 없다. 실제 성능은 학습률·모멘텀 계수·함수 형태에 따라 달라진다.

정리

SGD는 현재 기울기만 보고 움직이는 가장 단순한 Optimizer다. Momentum은 이동 방향을 기억해 가속·진동 완화를 더하고, Adagrad·RMSProp은 파라미터별로 보폭을 다르게 조절한다. Adam은 이 두 아이디어를 결합해 대부분의 상황에서 안정적인 기본 선택지로 자리 잡았다.

참고 자료

댓글 0

댓글을 불러오는 중…