AdamW·Nesterov·L-BFGS 최적화 알고리즘 선택과 튜닝

AdamW, Momentum·Nesterov, L-BFGS의 업데이트 방식과 선택 기준, 하이퍼파라미터 튜닝 및 PyTorch 적용 방법을 정리한다.

2026-08-14 · 최초 발행 2024-04-29

학습 곡면에 따라 달라지는 업데이트 방식

최적화 알고리즘은 같은 기울기를 받아도 파라미터를 이동시키는 방식이 다르다. Momentum과 Nesterov는 이전 기울기의 흐름을 누적해 이동 방향을 다듬고, AdamW는 좌표별 모멘트 추정으로 스텝 크기를 조절한다. L-BFGS는 제한된 메모리 안에서 곡률을 근사해 다른 방식으로 다음 지점을 찾는다.

SGD with Momentum은 경사 업데이트에 1차 모멘텀을 더해 진동을 줄이고 수렴을 가속한다. 이때 모멘텀 계수 β와 학습률 η가 핵심 변수다. Nesterov Accelerated Gradient(NAG)는 모멘텀으로 예상한 위치에서 기울기를 평가하므로 오버슈팅을 줄이고 곡면에 대한 방향 적응성을 높인다.

AdamW는 Adam의 1차·2차 모멘트 추정과 가중치 감쇠를 분리한 방식이다. 편향 보정과 ε에 의한 수치 안정화가 구성에 포함되며, 업데이트와 정규화를 분리해 최적점 왜곡을 줄이고 일반화 성능을 개선한다.

L-BFGS는 s, y 쌍을 이용해 역헤시안 (H_k)를 제한 메모리로 근사하는 준뉴턴 방식이다. 큰 스텝과 빠른 후반 수렴을 목표로 하지만, 선탐색(line search)의 조건이 수렴 안정성과 속도를 크게 좌우한다.

상태값이 업데이트에 개입하는 지점

미니배치 경사는 계산량과 메모리 비용을 낮추는 확률적 추정이다. 이 과정의 노이즈는 국소 최솟값에서 벗어나는 데 기여할 수 있다. Momentum 계열은 1차 모멘텀 (m)을 누적해 관성을 만들며, Nesterov는 예측 단계의 기울기로 이를 보정한다.

AdamW에서는 1차·2차 모멘트 (m, v)와 편향 보정값 (m̂, v̂)이 좌표별 스케일 변화에 대한 적응력을 제공한다. ε는 수치적 안정성을 위한 값이다. 반면 L-BFGS는 과거의 변화량을 보존해 곡률을 추정하고, 선탐색으로 실제 스텝 크기를 결정한다.

SGD+MomentumAdamWL-BFGSNaN/Inf정상입력: 파라미터 θ, 기울기 ∇L,상태(state: m, v, H)옵티마이저 선택모멘텀 갱신: m = β·m + ∇LNesterov: θ' = θ - η·β·m,∇L(θ') 재평가Δθ = -η·mm, v 갱신 편향보정Decoupled weight decay: θ θ - η·λ·θΔθ = -η·m̂/(sqrt(v̂)+ε)과거 (s, y)로 H_k 근사선탐색으로 η_k 결정Δθ = -η_k·H_k·∇L안정화 단계Grad clip, 손실 스케일조정(AMP), lr 감소/재시도출력: θ θ + Δθ, 상태업데이트

모델 조건에 맞춰 고르는 기준

대규모 사전학습이나 미세조정에서는 AdamW를 먼저 고려할 수 있다. 초기 수렴이 빠르고 하이퍼파라미터의 안정 범위가 넓지만, η와 ε를 과도하게 설정하면 과적합이나 진동 위험이 있다.

장시간 학습에서 일반화 성능을 우선한다면 SGD+Nesterov가 적합하다. 최종 테스트 성능이 우수한 사례가 다수 있지만, 러닝레이트 스케줄에 성능이 민감하다.

매끄러운 손실을 가진 소중형 데이터셋에는 L-BFGS가 맞는다. 이터레이션당 비용은 높아도 필요한 에폭 수를 줄일 수 있다. 다만 비분리 가능 손실이나 비용이 큰 미니배치에서는 비효율적이며, 분산 환경에서는 통신 비용과 선탐색 때문에 선호되지 않는다. AdamW와 SGD는 분산 스케일링에 모두 적합하다.

비전·언어 대규모 모델의 미세조정에는 AdamW와 decoupled decay, cosine annealing, early warmup을 조합할 수 있다. 추천 시스템, 표준 MLP, ResNet 학습에서는 AdamW로 빠르게 수렴시킨 뒤 SGD+Nesterov로 바꾸는 하이브리드 전략도 가능하다. 고정형 특성을 사용하는 작은 데이터 회귀에서는 전체 배치 또는 큰 배치와 함께 L-BFGS를 적용해 후반 수렴을 확보한다.

학습률과 상태 변수를 조정하는 방법

먼저 배치 크기를 고정하고 η를 log scale로 스윕한다. 이 과정에서 grad norm과 발산 여부를 확인하며, cosine annealing·one-cycle·step decay 가운데 하나의 스케줄러를 선택한다.

SGD+Nesterov는 η 초기값을 0.1×(배치 크기/256)으로 두고, 모멘텀 β는 0.9~0.95, weight decay는 1e-4~5e-4 범위에서 조정한다. warmup은 3~10 epochs 적용하고, 마지막 10~20% 구간에서는 η를 급격히 낮춘다.

AdamW는 소형 모델에서 η=1e-3, 중형 모델에서 3e-4, 대형 모델에서 1e-4를 시작점으로 둔다. β1=0.9, β2=0.95~0.999, ε=1e-8을 사용하며, weight decay는 0.01 전후로 조정한다. LayerNorm과 Bias는 decay에서 제외한다.

L-BFGS에는 history_size=10~50을 두고 Wolfe 조건을 강제하는 line search 옵션을 사용한다. 손실이 미분 가능한지 확인한 뒤 미니배치 대신 풀배치 또는 큰 배치를 사용하며, 혼합정밀과 AMP 조합은 권장하지 않는다.

알고리즘별 운영 성향

알고리즘 수렴 속도(초기/후반) 학습 안정성 일반화 메모리/연산 비용 분산 스케일링 운영 편의
SGD+Momentum 중/중상 낮음
Nesterov 중상/상 중상 낮음
AdamW 상/중 중상
L-BFGS 하/상 중상 중하 중하

주: 모델/데이터에 따라 변동. 표는 경향성 기준.

PyTorch에서 구성하는 AdamW, Nesterov, L-BFGS

PyTorch 2.2+ 환경을 기준으로 하며 GPU를 권장한다. L-BFGS는 CPU에서도 사용할 수 있다. 아래 코드는 모델, 데이터로더, 손실함수 정의가 끝난 상태를 전제로 한다.

# pip install torch torchvision

import torch
from torch import nn
from torch.optim import AdamW, SGD, LBFGS
from torch.optim.lr_scheduler import CosineAnnealingLR

model = ...  # nn.Module
criterion = ...
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)

# Parameter groups: decay 제외(bias, norm)
decay, no_decay = [], []
for name, p in model.named_parameters():
    if not p.requires_grad:
        continue
    if p.ndim == 1 or name.endswith("bias"):
        no_decay.append(p)
    else:
        decay.append(p)

adamw = AdamW([
    {"params": decay, "weight_decay": 0.01},
    {"params": no_decay, "weight_decay": 0.0},
], lr=3e-4, betas=(0.9, 0.95), eps=1e-8)

sgd_nag = SGD(model.parameters(), lr=0.1, momentum=0.9, nesterov=True, weight_decay=1e-4)

scheduler = CosineAnnealingLR(adamw, T_max=50)

scaler = torch.cuda.amp.GradScaler(enabled=(device == "cuda"))

def train_one_epoch(optimizer):
    model.train()
    for xb, yb in dataloader:
        xb, yb = xb.to(device), yb.to(device)
        optimizer.zero_grad(set_to_none=True)
        with torch.cuda.amp.autocast(enabled=(device == "cuda")):
            loss = criterion(model(xb), yb)
        scaler.scale(loss).backward()
        # 안정화: gradient clipping
        scaler.unscale_(optimizer)
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        # NaN/Inf 방어
        if not torch.isfinite(loss):
            for g in model.parameters():
                if g.grad is not None and torch.any(~torch.isfinite(g.grad)):
                    g.grad = None
        scaler.step(optimizer)
        scaler.update()

# L-BFGS는 closure 필요(작은/풀 배치 권장)
lbfgs = LBFGS(model.parameters(), lr=1.0, max_iter=20, history_size=20, line_search_fn="strong_wolfe")

def lbfgs_step():
    model.train()
    xb, yb = next(iter(dataloader_full))  # 풀배치 또는 큰 배치
    xb, yb = xb.to(device), yb.to(device)

    def closure():
        lbfgs.zero_grad()
        out = model(xb)
        loss = criterion(out, yb)
        loss.backward()
        return loss

    loss = lbfgs.step(closure)
    return loss

AdamW에서는 bias와 정규화 파라미터를 decay 대상에서 제외한다. AMP를 쓸 때는 unscale 뒤에 gradient clipping을 수행하고, 체크포인트에는 optimizer.state_dict()를 포함한다. 스케줄러의 step() 호출 시점도 에폭 기준인지 스텝 기준인지 일관되게 설계해야 한다.

수렴 속도와 비용에 기대할 수 있는 변화

AdamW는 초기 수렴 속도를 20~40% 개선해 동일 에폭 대비 검증 성능에 더 일찍 도달할 수 있다. SGD+Nesterov 전환 또는 장기 cosine 스케줄 적용은 최종 정확도를 +0.5~1.5pp 높인 사례가 다수 있다.

AMP, clipping, 옵티마이저 선택을 함께 최적화하면 NaN과 발산 빈도를 50% 이상 줄일 수 있다. 동일 목표 성능에 필요한 에폭을 10~30% 절감하면 GPU 시간도 함께 절약된다.

재현성과 장애 대응을 포함한 운영

재현성을 위해 시드를 고정하고 cudnn.deterministic 옵션과 데이터 섞기 정책을 고정한다. lr, grad-norm, weight-norm, 모멘텀 통계를 기록하며, 발산 감지 시 lr 감소와 재시도를 자동화한다.

AdamW에는 warmup 뒤 cosine 스케줄을 적용할 수 있고 one-cycle도 유효하다. SGD는 cosine 또는 step decay를 사용하며 마지막 구간에서 급격한 감쇠를 둔다. L-BFGS는 비분리 가능 손실, 대규모 분산, 대형 미니배치와 AMP 조합에 적합하지 않다. 선탐색이 실패하면 보수적인 초기 η로 다시 설정한다.

초기 탐색과 안정적 학습에는 AdamW를, 장기 학습의 일반화 최적화에는 SGD+Nesterov를, 매끄러운 손실과 소형 데이터의 후반 수렴에는 L-BFGS를 선택할 수 있다. AdamW로 탐색한 뒤 성능 평형점 부근에서 필요에 따라 SGD+Nesterov로 전환하고, 스케줄과 정규화를 세부 조정하는 흐름이 권장된다.

최적화 알고리즘AdamWNesterovL-BFGS딥러닝