최적화: 경사 하강법과 그 변형들

손실을 가장 작게 만드는 최적화 문제를 정리하고, 볼록 함수와 그렇지 않은 지형, 극소점과 말안장점을 구별해요. 학습률, 확률적 경사 하강법과 미니배치, 모멘텀, RMSProp, Adam을 원리부터 이해하고, 길쭉한 골짜기에서 직접 경주시켜 비교해요.

고급강의 6 / 6약 10분

미적분 트랙의 마지막 강의예요. 미분으로 경사를 재고, 그래디언트로 가장 가파른 방향을 찾고, 역전파로 수백만 개의 기울기를 한 번에 구했어요. 이제 남은 질문은 하나예요. 그 기울기를 가지고 어떻게 걸어야 가장 빨리, 안정적으로 바닥에 닿을까? 이것이 최적화예요. 같은 모델과 데이터라도 최적화 방법에 따라 학습이 몇 배 빨라지기도, 아예 실패하기도 해요.

최적화 문제의 모양

머신러닝의 학습은 결국 이 문제를 푸는 거예요.

w∗=arg⁡min⁡wL(w),L(w)=1n∑i=1nℓ(fw(xi),yi)\mathbf{w}^* = \arg\min_{\mathbf{w}} L(\mathbf{w}), \qquad L(\mathbf{w}) = \frac{1}{n}\sum_{i=1}^{n} \ell(f_{\mathbf{w}}(\mathbf{x}_i), y_i)

arg⁡min⁡\arg\min은 "최솟값을 만드는 입력"이에요. 데이터 하나하나의 오차 ℓ\ell을 평균 낸 손실 LL을 가장 작게 만드는 파라미터 w∗\mathbf{w}^*를 찾는 거예요.

볼록한 지형과 울퉁불퉁한 지형

  • 볼록 함수: 그릇 모양. 골짜기가 하나뿐이라 경사를 따라가면 반드시 가장 낮은 곳에 닿아요. 선형 회귀, 로지스틱 회귀가 여기에 속해요.
  • 볼록이 아닌 함수: 골짜기가 여럿이고, 평평한 고원과 말안장 모양도 있어요. 신경망의 손실이 여기에 속해요.

극소점과 말안장점

그래디언트가 0인 임계점에는 세 종류가 있어요.

  • 극소점: 사방이 오르막. 우리가 찾는 곳.
  • 극대점: 사방이 내리막.
  • 말안장점: 어떤 방향으로는 오르막, 어떤 방향으로는 내리막. 말 안장처럼 생겼어요.

예전에는 신경망 학습이 "나쁜 극소점에 갇히는 것"을 가장 걱정했어요. 그런데 차원이 수백만인 지형에서는 모든 방향이 동시에 오르막인 극소점보다, 어느 한 방향이라도 내리막이 남아 있는 말안장점이 훨씬 흔하다는 게 밝혀졌어요. 그리고 큰 신경망의 극소점들은 대부분 손실이 비슷하게 낮아요. 그래서 실제 문제는 "갇히는 것"보다 말안장점 근처의 평평한 곳에서 느려지는 것이에요. 아래의 모멘텀과 Adam이 바로 이 문제를 잘 다뤄요.

학습률: 가장 중요한 손잡이

경사 하강법의 한 걸음은

w←w−η ∇L(w)\mathbf{w} \leftarrow \mathbf{w} - \eta\,\nabla L(\mathbf{w})

학습률 η\eta가 너무 작으면 바닥까지 한없이 오래 걸리고, 너무 크면 골짜기를 넘어 반대편으로 튕기다가 발산해요. 그래디언트 강의에서 봤듯, 가장 가파른 방향이 학습률의 상한을 정해요. 지형의 곡률이 방향마다 크게 다르면(길쭉한 골짜기), 가파른 방향 때문에 학습률을 작게 잡아야 하고, 완만한 방향으로는 기어가듯 움직이게 돼요.

학습률 스케줄

학습 초반에는 크게 움직이고, 바닥 근처에서는 조심스럽게 움직이는 게 좋아요. 그래서 학습률을 시간에 따라 바꿔요.

  • 계단식 감소: 정해진 단계마다 학습률을 10분의 1로.
  • 코사인 감소: 코사인 곡선을 따라 부드럽게 0 근처까지.
  • 워밍업: 처음 몇 단계는 아주 작은 학습률에서 시작해 점점 키워요. 학습 초반 파라미터가 엉망일 때 큰 걸음으로 망가지는 것을 막아요. 트랜스포머 학습에서는 사실상 필수예요.

확률적 경사 하강법과 미니배치

손실은 데이터 전체의 평균이라, 정확한 그래디언트를 구하려면 데이터 전부를 봐야 해요. 데이터가 100만 개면 한 걸음 걷는 데 100만 개를 계산해야 하죠.

확률적 경사 하강법(SGD)은 데이터 일부만 보고 그래디언트를 추정해서 걸어요.

  • 데이터 1개로 추정: 아주 빠르지만 방향이 들쑥날쑥해요.
  • 데이터 32~512개 묶음(미니배치)으로 추정: 실무의 표준이에요. GPU가 묶음을 한 번에 병렬 계산하니 효율도 좋아요.

추정한 그래디언트에는 잡음이 섞이는데, 이게 꼭 나쁘지만은 않아요. 잡음이 말안장점이나 얕은 골짜기에서 빠져나오도록 흔들어 주고, 결과적으로 새로운 데이터에도 잘 맞는(일반화가 잘 되는) 지점으로 이끄는 경향이 있다고 알려져 있어요.

데이터 전체를 한 번 다 훑는 것을 에포크라고 해요. 보통 데이터를 섞은 뒤 미니배치로 나눠 한 에포크를 돌고, 이를 여러 번 반복해요.

for epoch in range(epochs):
    rng.shuffle(indices)                        # 매 에포크마다 순서 섞기
    for start in range(0, n, batch_size):
        batch = indices[start:start + batch_size]
        grad = compute_grad(w, X[batch], y[batch])
        w = w - lr * grad

모멘텀: 관성을 붙이기

공이 언덕을 굴러 내려가는 모습을 떠올려 보세요. 공은 지금 발밑의 경사만 보는 게 아니라 지금까지의 속도를 가지고 있어요. 이 관성을 흉내 낸 것이 모멘텀이에요.

v←β v+∇L(w),w←w−η v\mathbf{v} \leftarrow \beta\,\mathbf{v} + \nabla L(\mathbf{w}), \qquad \mathbf{w} \leftarrow \mathbf{w} - \eta\,\mathbf{v}

v\mathbf{v}는 지금까지 그래디언트의 누적(속도)이고, β\beta(보통 0.9)는 이전 속도를 얼마나 유지할지예요.

  • 길쭉한 골짜기에서 가파른 방향의 그래디언트는 좌우로 부호가 계속 바뀌어서, 누적하면 서로 상쇄돼요. 지그재그가 줄어요.
  • 완만한 방향의 그래디언트는 계속 같은 쪽을 가리켜서, 누적하면 점점 빨라져요.

평평한 고원이나 말안장점도 관성으로 지나갈 수 있어요.

학습률을 파라미터마다 따로: RMSProp

모멘텀이 방향을 다듬는다면, RMSProp은 걸음 크기를 다듬어요. 최근 그래디언트 크기의 이동 평균으로 나눠서, 가파른 방향(그래디언트가 큰 방향)은 걸음을 줄이고 완만한 방향은 걸음을 키워요.

s←β2 s+(1−β2) g2,w←w−η gs+ϵ\mathbf{s} \leftarrow \beta_2\,\mathbf{s} + (1 - \beta_2)\,\mathbf{g}^2, \qquad \mathbf{w} \leftarrow \mathbf{w} - \eta\,\frac{\mathbf{g}}{\sqrt{\mathbf{s}} + \epsilon}

제곱과 나눗셈은 모두 자리끼리 계산해요. 결과적으로 파라미터마다 자기만의 학습률을 갖게 돼요. 길쭉한 골짜기를 둥근 그릇처럼 보이게 만드는 효과예요. ϵ\epsilon은 0으로 나누는 걸 막는 아주 작은 수예요.

Adam: 둘을 합친 표준

Adam은 모멘텀(방향의 이동 평균)과 RMSProp(크기의 이동 평균)을 합친 방법이에요. 지금 딥러닝에서 가장 널리 쓰여요.

m←β1m+(1−β1)g,v←β2v+(1−β2)g2\mathbf{m} \leftarrow \beta_1 \mathbf{m} + (1 - \beta_1)\mathbf{g}, \qquad \mathbf{v} \leftarrow \beta_2 \mathbf{v} + (1 - \beta_2)\mathbf{g}^2
m^=m1−β1t,v^=v1−β2t,w←w−η m^v^+ϵ\hat{\mathbf{m}} = \frac{\mathbf{m}}{1 - \beta_1^t}, \qquad \hat{\mathbf{v}} = \frac{\mathbf{v}}{1 - \beta_2^t}, \qquad \mathbf{w} \leftarrow \mathbf{w} - \eta\,\frac{\hat{\mathbf{m}}}{\sqrt{\hat{\mathbf{v}}} + \epsilon}

m^\hat{\mathbf{m}}, v^\hat{\mathbf{v}}의 나눗셈은 편향 보정이에요. m\mathbf{m}과 v\mathbf{v}가 0에서 시작해서 초반에 너무 작게 추정되는 것을 바로잡아요. 기본값 β1=0.9\beta_1 = 0.9, β2=0.999\beta_2 = 0.999로 대부분 잘 동작해서, "일단 Adam"이 실무의 출발점이에요. 큰 언어 모델은 가중치 감쇠를 개선한 AdamW를 주로 써요.

직접 경주시키기

한 방향이 100배 가파른 길쭉한 골짜기 f(x,y)=x2+100y2f(x, y) = x^2 + 100y^2에서, (10,1)(10, 1)에서 출발해 손실이 10−610^{-6} 아래로 떨어질 때까지 몇 걸음 걸리는지 재 볼게요. 각 방법마다 잘 맞는 학습률을 찾아서 비교했어요.

import numpy as np

K = 100
f = lambda p: p[0] ** 2 + K * p[1] ** 2
grad = lambda p: np.array([2 * p[0], 2 * K * p[1]])

def run(update, steps=3000):
    p, s = np.array([10.0, 1.0]), {}
    for t in range(1, steps + 1):
        p = update(p, grad(p), s, t)
        if f(p) < 1e-6:
            return t
    return None    # 수렴 못함

def gd(lr):
    return lambda p, g, s, t: p - lr * g

def momentum(lr, beta=0.9):
    def update(p, g, s, t):
        s["v"] = beta * s.get("v", 0) + g
        return p - lr * s["v"]
    return update

def adam(lr, b1=0.9, b2=0.999, eps=1e-8):
    def update(p, g, s, t):
        s["m"] = b1 * s.get("m", 0) + (1 - b1) * g
        s["v"] = b2 * s.get("v", 0) + (1 - b2) * g ** 2
        m_hat = s["m"] / (1 - b1 ** t)
        v_hat = s["v"] / (1 - b2 ** t)
        return p - lr * m_hat / (np.sqrt(v_hat) + eps)
    return update

print(run(gd(0.0099)), run(gd(0.0101)), run(momentum(0.002)), run(adam(0.5)))
방법학습률걸음 수
경사 하강법0.0099476
경사 하강법0.0101발산
모멘텀 (β=0.9\beta = 0.9)0.002150
Adam0.5152

경사 하강법은 학습률을 0.0099에서 0.0101로 겨우 2% 올렸을 뿐인데 발산해요. 가파른 y 방향의 한계(2/200=0.012 / 200 = 0.01)를 넘었기 때문이에요. 한계 바로 아래의 최선을 골라도 476걸음이 걸려요. 모멘텀과 Adam은 3분의 1 정도면 돼요. 실제 신경망의 지형은 이보다 훨씬 길쭉하고 울퉁불퉁해서 차이가 더 커요.

실무 감각 이 표의 숫자보다 중요한 건 "학습률은 방법마다, 문제마다 다시 찾아야 한다"는 점이에요. Adam의 0.5는 이 장난감 문제에서 맞는 값일 뿐이고, 신경망에서는 보통 0.001(1e-3)에서 시작해 손실 곡선을 보며 조정해요.

그 밖에 알아 둘 것

이계 미분을 쓰는 방법

곡률(헤세 행렬)까지 쓰는 뉴턴 방법은 골짜기 모양을 알고 걷기 때문에, 바닥 근처에서 엄청나게 빨리 수렴해요. 하지만 파라미터가 nn개면 헤세 행렬이 n×nn \times n이라, 수백만 파라미터의 신경망에는 쓸 수 없어요. Adam이 그래디언트 제곱의 평균으로 "대각선 곡률"을 흉내 낸다고 볼 수도 있어요.

제약이 있는 최적화

"예산 안에서 효용을 최대로"처럼 조건이 붙은 문제는 라그랑주 승수법으로 풀어요. 최적점에서는 목표 함수의 그래디언트와 제약 조건의 그래디언트가 같은 방향이라는 성질을 이용해요. 서포트 벡터 머신 같은 고전 머신러닝 모델의 이론에 나와요.

학습이 잘 되는지 보는 법

  • 손실 곡선을 항상 그려 보세요. 내려가다 평평해지는 게 정상이에요.
  • 손실이 NaN이 되면 학습률이 너무 크거나 계산이 터진 거예요.
  • 손실이 전혀 줄지 않으면 학습률이 너무 작거나, 기울기 계산에 버그가 있어요(기울기 검사!).
  • 학습 손실은 주는데 검증 손실이 오르면 과적합이에요. 머신러닝 트랙의 평가 강의에서 다뤄요.

실습

  1. 위 경주 코드에서 K를 10과 1000으로 바꿔 보세요. 경사 하강법의 걸음 수는 어떻게 변하나요? 모멘텀과 Adam은요?
  2. RMSProp을 직접 구현해 경주에 넣어 보세요.
  3. 그래디언트에 일부러 잡음(grad(p) + rng.normal(0, 5, 2))을 섞어 확률적 경사 하강법을 흉내 내고, 학습률을 조금씩 줄여 가면(스케줄) 결과가 나아지는지 확인해 보세요.

자주 하는 실수

  • 학습률 하나로 모든 문제를 풀려고 하기. 문제마다, 방법마다 다시 찾아야 해요.
  • 데이터를 섞지 않고 미니배치 만들기. 정렬된 데이터를 그대로 나누면 묶음마다 편향된 그래디언트가 나와요.
  • 손실 곡선을 안 보기. 숫자 하나만 보면 발산이나 과적합을 놓쳐요.
  • Adam이 항상 최고라고 믿기. 이미지 분류 등 일부 문제에서는 잘 조정한 SGD와 모멘텀이 더 좋은 일반화를 보이기도 해요.

점검 문제

  1. 볼록 함수의 최적화가 쉬운 이유는?
  2. 고차원 신경망에서 극소점보다 흔하게 학습을 느리게 만드는 것은?
  3. 미니배치 경사 하강법이 데이터 전체를 쓰는 경사 하강법보다 실무에서 선호되는 이유 두 가지는?
  4. 모멘텀이 길쭉한 골짜기에서 지그재그를 줄이는 원리는?
  5. Adam의 편향 보정은 왜 필요한가요?

정답

  1. 골짜기가 하나뿐이라 극솟값이 곧 최솟값이고, 경사를 따라가면 반드시 그곳에 닿기 때문이에요.
  2. 말안장점과 그 근처의 평평한 영역이에요.
  3. 한 걸음에 드는 계산이 훨씬 적어 빠르고, GPU 병렬 계산에 맞으며, 잡음이 말안장점 탈출과 일반화에 도움이 되기 때문이에요.
  4. 가파른 방향의 그래디언트는 부호가 번갈아 바뀌어 누적하면 상쇄되고, 완만한 방향은 같은 부호가 쌓여 빨라지기 때문이에요.
  5. 이동 평균이 0에서 시작해 초반에 실제보다 작게 추정되는 것을 바로잡기 위해서예요.

미적분과 최적화를 마치며

미분으로 변화를 재고, 그래디언트로 방향을 찾고, 연쇄 법칙과 역전파로 수백만 개의 기울기를 구하고, 최적화로 실제로 걸어 내려가는 방법까지 익혔어요. 이제 딥러닝 코드의 loss.backward()와 optimizer.step() 두 줄이 정확히 무엇을 하는지 설명할 수 있어요.

AI 집중 코스의 다음은 확률과 통계예요. 모델이 "얼마나 확신하는지", 결과가 "우연인지 진짜인지"를 판단하는 언어예요.