SGD — Stochastic Gradient Descent
딥러닝 모델 학습의 가장 기본이 되는 알고리즘. 이것을 이해하면 Adam, AdamW 같은 모든 optimizer의 기반을 이해한 것이다.
문제 설정
신경망 학습의 목표는 손실함수(loss)를 최소화하는 파라미터를 찾는 것입니다.
w ← w - η ∇wL
즉 gradient가 가리키는 상승 방향의 반대로 이동해 loss를 줄입니다.
이 수식은 매우 짧지만 의미는 세 덩어리로 나눠서 봐야 이해가 쉽습니다. w는 지금 모델이 가진 파라미터 위치, ∇L은 loss가 가장 빨리 커지는 방향, η는 한 번에 얼마나 움직일지 정하는 보폭입니다.
한 줄 요약
오차(loss)를 줄이기 위해 그래디언트 방향으로 조금씩 파라미터를 움직이는 방법이다.
이 문서를 읽고 설명할 수 있어야 하는 핵심 3가지
- GD와 SGD의 차이, 그리고 왜 SGD가 더 빠른지
- noisy gradient가 왜 오히려 장점이 될 수 있는지
- SGD가 Adam/AdamW와 어떤 관계인지
처음 보는 사람용 핵심 용어
- Loss: 모델의 예측이 정답과 얼마나 다른지를 숫자로 나타낸 것. 클수록 틀린 것이다.
- Gradient: loss가 어느 방향으로 커지는지를 나타내는 방향 벡터. 반대 방향으로 가면 loss가 줄어든다.
- Learning rate (η): 한 번에 얼마나 움직일지 결정하는 보폭. 너무 크면 발산, 너무 작으면 느리다.
- Momentum: 이전 step들의 방향을 조금 기억해서 현재 update에 반영하는 장치. 흔들림을 줄이고 일관된 방향으로 더 빨리 가게 돕는다.
- Mini-batch: 전체 데이터에서 랜덤하게 뽑은 소규모 묶음 (예: 128개).
- Epoch: 전체 데이터를 한 번 다 훑는 것. 학습은 보통 수십~수백 epoch 반복한다.
1단. 왜 이런 방법이 필요한가
신경망은 이런 흐름으로 동작한다.
입력 → 신경망 → 예측 → 정답과 비교 → loss 발생
학습의 목표는 이 loss를 최소로 만드는 파라미터 w를 찾는 것이다.
# 목표
min L(w) # w: 파라미터, L: loss function
파라미터 공간은 수십억 차원이기 때문에 전수 탐색은 불가능하다. gradient를 이용해 조금씩 내려가는 방법이 필요하다.
2단. 기본 Gradient Descent
loss가 줄어드는 방향 = gradient의 반대 방향. 이 방향으로 조금씩 이동한다.
w = w - η * ∇L(w)
# η: learning rate (보폭)
# ∇L(w): gradient (오르막 방향 → 반대로 가면 내리막)
여기서 중요한 것은 gradient가 "좋은 방향"이 아니라 loss가 커지는 방향이라는 점입니다. 그래서 업데이트 식에 항상 마이너스가 붙습니다. 마이너스가 없으면 학습이 아니라 오히려 loss를 키우는 방향으로 올라가게 됩니다.
또 η는 방향을 바꾸지 않고 이동량만 조절합니다. 즉 gradient가 어디로 갈지를 알려주고, learning rate가 얼마나 멀리 갈지를 정한다고 생각하면 됩니다.
그래서 learning rate는 optimizer의 성격을 바꾸는 값이 아니라, 같은 방향을 얼마나 공격적으로 따를지 정하는 값입니다. 아무리 좋은 optimizer라도 learning rate가 지나치게 크면 발산하고, 너무 작으면 거의 학습이 안 되는 것처럼 보일 수 있습니다.
문제: 전체 데이터로 gradient를 계산해야 한다. 데이터가 1조 토큰이면 1 step에 전부 읽어야 한다. 계산량이 엄청나고 GPU를 오래 점유한다.
3단. SGD 아이디어 — 일부만 쓴다
전체 데이터 대신 랜덤하게 일부만 뽑아서 gradient를 계산한다.
| 방식 | 사용 데이터 | 속도 | 정확도 |
|---|---|---|---|
| Gradient Descent | 전체 (1,000,000) | 느림 | 정확 |
| SGD (mini-batch) | 일부 (128) | 빠름 | 근사 |
Stochastic(확률적)이라는 이름이 여기서 나온다. 매 step마다 랜덤하게 샘플을 뽑는다.
w = w - η * ∇L_i(w)
# L_i: mini-batch i로 계산한 loss (전체 loss의 근사)
즉 SGD는 "방향을 대충 보더라도 자주 움직이자"는 전략입니다. 한 번의 gradient는 덜 정확하지만 계산이 훨씬 싸기 때문에, 실제 wall-clock time 기준으로는 훨씬 빠르게 학습을 진행할 수 있습니다.
4단. 실제 학습 흐름
실제로는 mini-batch SGD를 사용한다. 아래가 딥러닝 학습의 기본 루프다.
for x, y in dataloader: # mini-batch 로드 (예: 128개)
pred = model(x) # 순전파: 예측
loss = criterion(pred, y) # loss 계산
optimizer.zero_grad() # 이전 gradient 초기화 (누적 방지)
loss.backward() # 역전파: gradient 계산
optimizer.step() # SGD: w = w - η * gradient
이 루프가 전체 데이터를 한 번 다 돌면 1 epoch이다. 학습은 이것을 수십~수백번 반복한다.
핵심 수식 정리
① 최적화 목표
min L(w) = min (1/N) * Σ ℓ(f(x_i; w), y_i)
# N: 전체 데이터 수
# ℓ: 개별 샘플의 loss
# f(x_i; w): 파라미터 w를 가진 모델의 예측값
직관: 모든 샘플에서의 평균 오차를 최소화하는 w를 찾는 것이 목표다.
② Gradient Descent (전체 데이터)
w = w - η * ∇L(w) # 전체 데이터 기준. 정확하지만 느리다.
여기서 ∇L(w)는 전체 데이터 평균 loss를 기준으로 계산한 gradient입니다. 그래서 방향은 비교적 안정적이지만, 한 번 계산하는 데 비용이 매우 큽니다.
③ SGD (mini-batch)
w = w - η * ∇L_i(w) # mini-batch 기준. 빠르고 노이즈가 있다.
∇L_i(w)는 전체 loss가 아니라 현재 mini-batch가 만들어낸 근사 gradient입니다. 즉 정확도는 조금 희생하지만, 계산량을 크게 줄여 더 자주 업데이트할 수 있게 만든 것이 SGD의 핵심입니다.
한계: learning rate η를 직접 정해야 하고, 모든 파라미터에 동일한 η를 사용한다. 이 한계를 극복하려고 Adam이 나왔다.
5단. SGD의 장단점
장점 1 — 계산이 빠르다
전체 데이터 대신 128개만 쓰기 때문에 1 step이 수천 배 빠르다. step이 많아져도 전체 학습 시간은 줄어든다.
장점 2 — 노이즈가 오히려 도움이 된다
gradient가 정확하지 않아 생기는 노이즈가 두 가지 효과를 낸다.
- local minimum 탈출: 정확한 gradient는 local minimum에 갇히기 쉽다. 노이즈가 그 함정을 벗어나게 도와준다.
- generalization 향상: 노이즈 덕분에 sharp minimum 대신 flat minimum을 찾는 경향이 있다. flat minimum이 일반적으로 generalization이 좋다.
단점 — loss가 들쭉날쭉
noisy gradient 때문에 loss curve가 불안정하다. learning rate 조절이 중요하고, 잘못 설정하면 수렴이 느리거나 발산한다.
이때 momentum은 learning rate와 다른 역할을 합니다. learning rate가 "한 걸음 크기"를 조절한다면, momentum은 "이전 방향을 얼마나 기억할지"를 조절합니다. 그래서 둘은 서로 대체 관계가 아니라 함께 튜닝하는 값입니다.
6단. SGD와 개선된 Optimizer 비교
| Optimizer | 핵심 아이디어 | 주요 사용처 |
|---|---|---|
| SGD | 기본. momentum 옵션 추가 가능 | CNN, ResNet 학습 |
| RMSProp | 파라미터별 learning rate 자동 조절 | RNN |
| Adam | SGD + momentum + RMSProp 결합 | 범용 |
| AdamW | Adam + weight decay를 gradient와 분리 | LLM (거의 표준) |
핵심: 이름이 달라도 모두 gradient를 계산하고 w를 업데이트하는 구조는 SGD와 같다. Adam은 SGD에 adaptive learning rate와 momentum을 추가한 것이다.
즉 optimizer 비교의 핵심 질문은 "gradient를 계산하느냐 마느냐"가 아니라, 계산된 gradient를 어떻게 더 안정적으로 쓰느냐입니다. Momentum은 방향 기억을 추가하고, Adam/AdamW는 파라미터별 스케일 조절까지 넣는다고 보면 됩니다.
실험 설명 섹션
- 실험 목적: SGD vs Adam의 수렴 속도와 최종 성능 비교
- 실험 설정: 동일 모델(2-layer MLP), 동일 데이터, learning rate 그리드 서치
- 평가 지표: train loss curve, validation accuracy
- 결과 해석: Adam이 초반 수렴은 빠르지만, SGD+momentum이 최종 accuracy에서 앞서는 경우가 있다
- 한계: task/데이터에 따라 결과가 다르다. 단일 실험으로 일반화하지 말 것
예상 질문 5개와 답변
- Q1. SGD가 GD보다 느린데 왜 쓰나?
GD는 전체 데이터를 읽어야 1 step. SGD는 128개로 즉시 업데이트. 실제 wall-clock time은 SGD가 훨씬 빠르다. - Q2. Stochastic이 왜 유리한가?
noisy gradient가 local minimum 탈출과 generalization에 도움이 된다. 완벽히 정확한 gradient가 오히려 sharp minimum에 빠지기 쉽다. - Q3. learning rate를 크게 하면 어떻게 되나?
파라미터가 너무 크게 이동해서 loss가 발산한다. 반대로 너무 작으면 수렴이 매우 느리다. - Q4. Adam이 SGD보다 항상 좋은가?
아니다. CNN/ResNet 이미지 분류에서는 SGD+momentum이 Adam보다 generalization이 좋은 경우가 많다. LLM에서는 AdamW가 사실상 표준이다. - Q5. zero_grad()를 왜 매 iteration마다 해야 하나?
PyTorch는 기본적으로 gradient를 누적한다. 초기화 없이 backward()를 반복하면 이전 batch의 gradient가 더해져 잘못된 방향으로 업데이트된다.
다음에 스스로 해볼 실습 2가지
- SGD / SGD+momentum / Adam 세 가지를 같은 모델에 적용하고 loss curve를 한 그래프에 비교해보기
- learning rate를 0.001 / 0.01 / 0.1 / 1.0 로 바꾸면서 수렴 여부와 속도 차이를 기록해보기