HOW AI WORKS

SGD는 어떻게 동작하는가
— 전부 보지 않고 내려가는 법

경사 하강법(GD)은 한 발짝을 떼기 위해 학습 데이터 전부를 봅니다. 데이터가 100만 장이면 100만 번 계산해서 딱 한 번 가중치를 고칩니다. 확률적 경사 하강법(SGD)은 그중 몇 개만 보고 그냥 갑니다. 방향이 조금 틀려도 상관없다는 것인데 — 왜 그래도 되는지, 틀린 정도가 얼마나 되는지, 그리고 그 "틀림"이 오히려 도움이 되는 경우까지 브라우저에서 직접 계산해 봅니다.

같은 시리즈의 How Gradient Descent Works가 "기울기를 따라 내려간다"는 것을 다뤘다면, 이 글은 그 기울기를 표본으로 대신하는 순간 무슨 일이 벌어지는지를 다룹니다. 원전: Robbins & Monro, A Stochastic Approximation Method (1951)

차례
  1. 손실은 평균이다 — GD의 한 발짝은 왜 비싼가
  2. 표본으로 기울기를 추정한다 — 평균 내면 제자리로 온다
  3. GD · 미니배치 · SGD — 정확히 무엇이 다른가
  4. 노이즈의 크기는 1/√B — 배치를 4배 키워야 노이즈가 절반
  5. 같은 계산 예산이면 누가 이기는가
  6. 노이즈는 버그가 아니다 — 지역 최솟값 탈출
  7. 그래서 학습률을 줄인다 — 노이즈 볼과 Robbins–Monro 조건
  8. 실전 메모 — 셔플·에폭·선형 스케일링, 그리고 이름의 혼선
  9. 여러 대로 나눠 돌리기 — 동기 SGD와 비동기 SGD
  10. 정리 — 한 문장으로
  11. 이해도 퀴즈

§1 손실은 평균이다 — GD의 한 발짝은 왜 비싼가

거의 모든 지도학습의 손실 함수는 데이터 하나하나의 손실을 평균 낸 것입니다.

L(θ) = (1/N) · Σi=1..N Li(θ) θ = 가중치 · N = 학습 데이터 개수 · Li = i번째 샘플 하나의 손실

미분은 선형이므로 기울기도 그대로 평균입니다.

∇L(θ) = (1/N) · Σi=1..N ∇Li(θ)

여기서 문제가 시작됩니다. 경사 하강법은 θ ← θ − η∇L(θ) 한 줄이지만, 그 ∇L 하나를 얻으려면 N개 전부에 대해 순전파와 역전파를 돌려야 합니다. ImageNet처럼 N = 128만이면, 가중치를 한 번 고치려고 128만 장을 통과시키는 셈입니다. 100번 고치려면 1억 2,800만 번. 학습이 끝나지 않습니다.

이 글에서 계속 쓸 장난감 문제. 데이터 N = 40개를 y = 2x + 1 + 잡음으로 만들고, 직선 ŷ = w·x + b를 맞춥니다. 파라미터가 (w, b) 둘뿐이라 손실 지형을 평면에 그대로 그릴 수 있고, 진짜 기울기도 정확히 계산됩니다. 샘플 하나의 손실은 Li = ½(w·xi + b − yi)², 따라서 ∇Li = [(ŷi−yi)·xi, (ŷi−yi)]입니다. 아래 데모의 숫자는 전부 이 식을 페이지 안에서 실제로 돌린 결과입니다.

§2 표본으로 기울기를 추정한다 — 평균 내면 제자리로 온다

SGD의 착상은 한 줄입니다. 평균을 구하려고 전수 조사를 할 필요는 없다. 여론조사가 5천만 명이 아니라 1천 명에게 묻듯, 기울기도 무작위로 뽑은 B개의 평균으로 대신합니다.

gB(θ) = (1/B) · Σi∈B ∇Li(θ)    E[gB(θ)] = ∇L(θ) B를 균등하게 무작위로 뽑는 한, 미니배치 기울기의 기댓값은 진짜 기울기와 정확히 같다 (불편 추정량)

증명은 한 줄입니다. 각 샘플이 뽑힐 확률이 모두 같으니 E[∇Li] = (1/N)Σ∇Lj = ∇L, 그 B개의 평균도 여전히 ∇L입니다. 한 스텝 한 스텝은 틀리지만, 틀린 방향이 한쪽으로 쏠려 있지 않습니다. 아래에서 직접 확인해 보세요 — 미니배치 기울기(회색)는 제각각이지만, 그것들을 평균 내면(초록) 진짜 기울기(파랑)로 수렴합니다.

실험 1 · 미니배치 기울기는 진짜 기울기의 불편 추정량
손실 지형 위 한 점에서의 기울기 화살표. 배경 등고선은 L(w, b), ★는 최적점. 평면을 클릭하면 측정 지점이 바뀝니다. 화살표 길이는 전부 같은 배율로 키워 그립니다(서로의 길이 비와 방향은 그대로).
진짜 기울기 ∇L (전체 40개 평균) 미니배치 기울기 gB 개별 지금까지 뽑은 gB들의 평균
뽑은 미니배치 수0
진짜 기울기 ∇L–
gB 평균–
평균의 오차 ‖ḡ − ∇L‖–
개별 gB의 평균 오차–

개별 화살표는 제각각인데(오른쪽 끝 수치) 평균의 오차는 계속 줄어듭니다. B를 키우면 개별 화살표부터 이미 촘촘해집니다 — 그 "촘촘해지는 속도"가 §4의 주제입니다.

§3 GD · 미니배치 · SGD — 정확히 무엇이 다른가

세 방법은 서로 다른 알고리즘이 아닙니다. 갱신 규칙은 전부 똑같은 한 줄이고, 기울기를 몇 개의 샘플로 계산하느냐(B)만 다릅니다.

θt+1 = θt − η · gB(θt) B = N이면 GD, B = 1이면 (좁은 의미의) SGD, 그 사이가 미니배치 SGD
 배치 경사 하강법 (GD)미니배치 SGDSGD (B=1)
한 스텝에 쓰는 샘플N (전부)B (보통 32~512)1
1 에폭당 갱신 횟수1N/BN
기울기 방향정확기댓값은 정확, 흔들림 있음기댓값은 정확, 매우 흔들림
궤적매끄럽게 단조 감소지그재그로 감소요동치며 감소
GPU 활용메모리 초과로 애초에 불가행렬 곱으로 병렬 — 최적병렬성 없음, 비효율
수렴 지점최솟값에 정확히 정착최솟값 주변을 맴돔 (노이즈 볼 · §7)
지역 최솟값·안장점갇히면 못 나옴노이즈가 밀어내 빠져나올 수 있음 (§6)
데이터가 계속 들어올 때불가 (전부를 알아야 함)가능 (온라인 학습)
흔한 오해. "SGD는 GD의 근사라서 더 나쁜 답을 준다"가 아닙니다. 딥러닝에서 SGD를 쓰는 이유는 메모리와 시간 때문만도 아닙니다. 같은 계산량을 쓰면 SGD가 먼저 내려가고(§5), 노이즈 자체가 일반화에 도움이 된다는 것이 경험적으로 알려져 있습니다(§6). GD는 "정확하지만 한 발짝이 너무 비싼" 방법입니다.

아래 실험실에서 B를 움직여 보세요. B = 40(전체)으로 두면 궤적이 GD와 정확히 겹칩니다 — 같은 알고리즘이라는 게 눈으로 보입니다. B를 1로 내리면 같은 목적지를 향해 가면서도 길이 들쭉날쭉해집니다.

실험 2 · 궤적 비교 실험실
▶ 실행을 누르면 같은 시작점 · 같은 학습률에서 GD(파랑)와 미니배치 SGD(주황)가 동시에 달립니다. 에폭마다 데이터를 섞어 B개씩 끊어 씁니다.
SGD 갱신 횟수0
에폭0
GD 손실–
SGD 손실–
최솟값 L*–

같은 스텝 수에서는 GD가 유리해 보입니다. 당연합니다 — GD는 한 스텝마다 40개를 보고 SGD는 B개만 봅니다. 공정한 비교는 "스텝 수"가 아니라 "본 샘플 수"로 해야 합니다. §5에서 그렇게 다시 겨룹니다.
η를 키워 보면 차이가 하나 더 보입니다. GD는 여전히 ★에 정확히 멈추지만, SGD는 ★ 주변을 도는 구름이 눈에 띄게 커집니다 — 이 구름의 정체가 §7입니다.

§4 노이즈의 크기는 1/√B — 배치를 4배 키워야 노이즈가 절반

미니배치 기울기는 평균적으로는 옳지만(§2), 한 번 한 번은 틀립니다. 얼마나 틀릴까요? 독립적인 값 B개를 평균 내면 분산이 B로 나뉘는, 표본평균의 기본 성질 그대로입니다.

E‖gB − ∇L‖² = (S² / B) · (N − B)/(N − 1)  →  오차 ∝ 1/√B S² = (1/N)Σ‖∇Li − ∇L‖² (샘플 기울기의 퍼짐) · 뒤의 항은 비복원 추출 보정 — B = N이면 정확히 0

이 식이 SGD의 손익 계산서 전부입니다. 배치를 2배 키우면 계산은 2배 드는데 노이즈는 √2배(≈1.41배)밖에 안 줄어듭니다. 계산량당 노이즈 감소로 보면 큰 배치는 손해입니다. 그런데도 실무에서 B를 키우는 이유는 GPU가 32개든 256개든 거의 같은 시간에 처리하기 때문이지, 통계적으로 이득이어서가 아닙니다.

실험 3 · 배치 크기 vs 기울기 오차 — 이론과 실측
측정 지점 θ = (0.4, 1.0)(실험 1의 그 지점)에서 각 B마다 미니배치를 2,000번 뽑아 ‖gB − ∇L‖의 제곱평균제곱근(RMS)을 실측하고, 위 공식과 겹쳐 그립니다.

보정 항을 끄면(복원 추출 가정) B가 N에 가까울 때 이론이 실측보다 큽니다 — 실제로는 배치가 데이터 전체에 가까워질수록 "뽑을 게 남지 않아" 오차가 0으로 떨어지기 때문입니다. B = N에서 오차가 정확히 0인 것이 곧 GD입니다.

§5 같은 계산 예산이면 누가 이기는가

지금까지의 비교는 전부 불공정했습니다. 진짜 질문은 이것입니다 — 샘플 기울기를 딱 1만 번 계산할 수 있다면, 어떻게 나눠 쓰는 게 이득인가? GD는 40번 계산해 1스텝, B=1은 1번 계산해 1스텝입니다. 같은 예산으로 SGD는 40배 많이 움직입니다.

실험 4 · 같은 예산 경주 — x축은 스텝이 아니라 "계산한 샘플 기울기 수"
네 가지 배치 크기가 동일한 예산을 쓰고, 손실(로그 축)이 어디까지 떨어지는지 겨룹니다.

초반이 승부처입니다. 기본값(η = 0.12)에서 1에폭 시점의 남은 거리는 B=1이 0.023, GD가 6.44 — 280배 차이입니다. GD가 첫 한 발짝을 떼는 동안 B=1은 이미 40발짝을 갔고, 그 한 발짝 한 발짝이 틀린 방향이더라도 평균적으로는 옳은 방향이기(§2) 때문입니다. 대신 B=1은 금세 더 내려가지 못하고 0.0006~0.04 사이를 오르내립니다 — 노이즈 바닥(§7)에 닿은 것입니다. 12에폭까지 가면 중간 크기인 B=4·16이 가장 깊이(≈0.0009) 내려가고, GD는 그때까지도 0.08에 머뭅니다. "가장 작은 배치가 항상 이긴다"가 아니라, "전체를 다 보는 것은 거의 항상 손해다"가 결론입니다.

실험 5 · 실제 규모에서의 갱신 횟수 계산기
"1 에폭"이 GD에게는 한 발짝, SGD에게는 수만 발짝이라는 것을 숫자로 확인합니다.
GD 총 갱신 횟수–
미니배치 SGD 총 갱신 횟수–
배수–
둘의 총 기울기 계산량–

기본값은 ImageNet(N = 1,281,167) · B = 256 · 90에폭입니다. 계산량은 완전히 같은데 가중치를 고치는 횟수가 90번이냐 45만 번이냐로 갈립니다. GD로 딥러닝이 불가능한 이유가 이 한 줄입니다.

§6 노이즈는 버그가 아니다 — 지역 최솟값 탈출

여기까지는 노이즈를 "참고 견디는 비용"으로 다뤘습니다. 그런데 노이즈에는 GD가 절대 할 수 없는 일이 하나 있습니다. GD는 기울기가 0인 곳에 도착하면 영원히 멈춥니다. 그곳이 전체 최솟값이든, 얕은 웅덩이든, 안장점이든 똑같습니다. ∇L = 0 → θ ← θ − η·0 → 제자리. 반면 SGD의 기울기는 그 지점에서도 0이 아닙니다 — 평균이 0일 뿐이라 계속 흔들리고, 운이 좋으면 언덕을 넘어갑니다.

실험 6 · 노이즈 크기에 따른 탈출률
오른쪽 얕은 웅덩이에서 출발합니다. 왼쪽에 더 깊은 진짜 최솟값이 있습니다. 노이즈 σ = 0이 곧 GD입니다.
현재 위치 w–
밟은 스텝–
탈출률 (300회 중)–
깊은 최솟값 도달–
어느 쪽에도 못 앉음–

σ = 0에서 탈출률은 정확히 0%입니다 — GD는 얕은 웅덩이에서 절대 못 나옵니다. σ를 올리면 탈출률이 오르지만, 너무 올리면 깊은 웅덩이에서도 튕겨 나와 "어느 쪽에도 못 앉음"이 늘어납니다. 이 맞교환이 §7의 학습률 감쇠로 이어집니다.

과장하지 않기. 이 데모는 노이즈를 "기울기에 더해지는 평균 0의 난수"로 단순화한 1차원 모형입니다. 실제 SGD의 노이즈는 위치에 따라 크기와 방향이 달라지고(샘플마다 손실 지형이 다르므로), 가우시안도 아닙니다. 또한 깊은 신경망에서 학습을 방해하는 것은 지역 최솟값보다 안장점이라는 관점이 우세합니다 (Dauphin et al., 2014). "노이즈가 있으면 정지점에 갇히지 않는다"는 결론은 같지만, 웅덩이 그림 그대로를 고차원에 옮기면 안 됩니다.

§7 그래서 학습률을 줄인다 — 노이즈 볼과 Robbins–Monro 조건

학습률 η를 상수로 두면 SGD는 최솟값에 정착하지 못합니다. 최솟값에 도착해도 gB가 0이 아니라서 계속 걷어차이기 때문입니다. 결국 최솟값을 중심으로 한 일정 반경의 구름 안을 맴도는데, 이걸 노이즈 볼이라고 부릅니다. 그 반경은 대략 √(η·S²)에 비례합니다 — η를 1/4로 줄이면 반경이 절반이 됩니다.

실험 7 · 상수 학습률의 바닥 vs 감쇠 학습률
B = 1로 20회 독립 실행한 평균 거리 ‖θt − θ*‖를 로그 축에 그립니다. η 상수 두 가지와 ηt = η₀/(1 + t/τ) 하나.
상수 η₀ 상수 η₀/4 감쇠 η₀/(1+t/τ)
η₀의 바닥–
η₀/4의 바닥–
바닥 비율–
감쇠 쪽 최종 거리–

상수 학습률 두 줄은 각자 다른 높이에서 평평해집니다. η를 1/4로 줄였을 때 바닥이 약 2배 낮아지는지 확인해 보세요 — √η 법칙 그대로입니다. "학습률을 10배 줄였더니 손실이 뚝 떨어지는" 계단식 스케줄이 실제로 하는 일이 이것입니다. 반면 감쇠 학습률(초록)은 바닥 없이 계속 내려갑니다.

체크박스를 꺼 보세요 — 실무 방식이 이론보다 빠릅니다. 위 데모는 기본적으로 매 스텝 독립적으로 한 개를 뽑습니다(i.i.d.). 교과서 이론이 가정하는 방식이고, 바닥 비율이 √4 = 2로 맞아떨어집니다. 체크를 끄면 에폭마다 섞어 한 번씩 쓰는 실제 DataLoader 방식이 되는데, 이때 바닥 비율은 3 이상으로 커집니다 — 같은 η에서 바닥이 더 낮다는 뜻입니다. 한 에폭 안에서 모든 샘플이 정확히 한 번씩 쓰이므로 기울기 오차가 에폭 안에서 서로 상쇄되기 때문입니다 (random reshuffling이 i.i.d. SGD보다 빠르다는, 이론적으로도 알려진 현상 — Gürbüzbalaban et al., 2015). 셔플이 그저 구현 편의가 아니라는 근거입니다.

언제까지 줄여야 할까요? 1951년 Robbins와 Monro가 이미 답을 내놨습니다. 충분히 많이 걸을 수 있을 만큼 크되, 노이즈가 결국 사그라들 만큼 작아야 한다는 두 조건입니다.

Σt ηt = ∞  그리고  Σt ηt² < ∞ 앞 조건: 아무리 멀리 있어도 도달할 수 있다 · 뒤 조건: 노이즈의 누적 효과가 유한하다

ηt = η₀/t는 둘 다 만족하고(조화급수는 발산, 제곱합은 수렴), ηt = η₀/√t는 두 번째를 어기며, 상수 η는 첫 번째만 만족합니다 — 그래서 상수 η는 수렴하지 않고 노이즈 볼 안을 맴돕니다. 위 실험이 보여준 그대로입니다.

그렇다고 실무에서 1/t를 쓰지는 않습니다. 너무 빨리 줄어들어 학습이 조기에 멈추기 때문입니다. 현실에서는 계단식 감쇠(30에폭마다 ÷10), 코사인 감쇠, 초반에 오히려 올리는 워밍업 등을 씁니다. 이론적 수렴 조건은 "감쇠가 왜 필요한가"를 설명해 줄 뿐, 스케줄 자체를 정해 주지는 않습니다.

§8 실전 메모 — 셔플·에폭·선형 스케일링, 그리고 이름의 혼선

1. 실제 구현은 "무작위 추출"이 아니라 "섞고 자르기"

이론은 매 스텝 독립적으로 B개를 뽑는다고 가정하지만, 실제 DataLoader는 에폭 시작 때 전체를 한 번 섞고 앞에서부터 B개씩 끊어 씁니다. 한 에폭 안에서 모든 샘플이 정확히 한 번씩 쓰이고(비복원), 구현도 빠르며, 경험적으로 더 잘 됩니다. shuffle=True를 빼면 데이터가 클래스 순으로 정렬돼 있을 때 배치 하나가 통째로 한 클래스가 되어 학습이 망가집니다. 위 실험 2·4도 이 방식(에폭마다 셔플 후 분할)으로 돌아갑니다.

2. 배치를 키우면 학습률도 키운다 — 선형 스케일링 법칙

B를 k배 키우면 스텝 수가 1/k로 줄어듭니다. 같은 거리를 가려면 한 걸음을 k배로 키워야 하므로 η ← k·η. 이것이 Goyal et al. (2017)이 ImageNet을 B = 8,192로 1시간에 학습시킬 때 쓴 규칙입니다(초반 워밍업을 함께 씀). 다만 노이즈는 1/√k로만 줄어드는데(§4) 걸음은 k배로 키우는 것이라 무한정 통하지는 않습니다 — 어느 배치 크기부터는 깨집니다. GPU를 여러 장 쓰는 것이 곧 배치를 키우는 것인 이유는 §9에서 확인합니다.

3. 이름의 혼선 — 프레임워크의 SGD는 B=1이 아니다

torch.optim.SGD를 쓴다고 해서 샘플 하나씩 학습하는 게 아닙니다. 배치 크기는 DataLoader가 정하고, 옵티마이저는 "들어온 기울기로 θ ← θ − η·g를 한다"는 규칙일 뿐입니다. 게다가 momentum=0.9를 주면 그건 이미 순수 SGD가 아니라 모멘텀 SGD입니다. 오늘날 "SGD"는 거의 항상 "미니배치 확률적 경사 하강법"을 뜻하고, B=1은 실무에서 쓰지 않습니다(GPU를 놀리기 때문).

말엄밀한 뜻실무에서 통용되는 뜻
SGDB = 1, 샘플 하나로 갱신미니배치 갱신 전반 (B = 32~512 등)
batch size—미니배치 크기 B (전체 N이 아님)
batch gradient descentB = N, 전체 배치거의 안 씀 (혼동을 피하려 "full-batch"라 부름)
iteration / step갱신 1회미니배치 1개 처리 = 갱신 1회
epoch데이터 전체를 한 번 훑음= ⌈N/B⌉ iteration

4. Adam·모멘텀은 SGD를 대체하는 게 아니라 위에 얹는 것

모멘텀·RMSProp·Adam은 전부 "gB를 받아서 어떻게 쓸 것인가"에 대한 개선입니다. 기울기를 미니배치로 추정한다는 전제는 똑같이 깔고 갑니다. 즉 §2~§4의 노이즈 이야기는 Adam을 쓰더라도 그대로 유효하며, 모멘텀은 그 노이즈를 시간축으로 평균 내 흔들림을 줄이는 장치로도 읽을 수 있습니다. 이 옵티마이저들의 비교는 How Gradient Descent Works에서 다룹니다.

§9 여러 대로 나눠 돌리기 — 동기 SGD와 비동기 SGD

한 대로는 부족할 때 워커 K대에 데이터를 나눕니다(데이터 병렬). 각 워커는 자기 몫의 미니배치 B개로 기울기를 계산하는데, 그 다음이 문제입니다 — 다른 워커를 기다릴 것인가, 말 것인가. 이 한 가지 선택이 동기 SGD와 비동기 SGD를 가릅니다.

동기 SGD — 전원이 같은 θ에서 출발한다

모든 워커가 같은 θt로 기울기를 계산하고, 다 모아 평균 낸 뒤 한 번 갱신합니다 (이 "다 모아 평균 내기"가 all-reduce입니다).

θt+1 = θt − η · (1/K) Σk=1..K gB(k)(θt) K개 미니배치의 평균 = K·B개짜리 미니배치 하나의 기울기 — 완전히 같은 식입니다
동기 SGD는 새로운 알고리즘이 아닙니다. 워커 8대가 각자 64개씩 보는 것은 한 대가 512개를 보는 것과 수학적으로 동일합니다(아래 데모에서 두 궤적의 차이가 10−16 수준임을 확인합니다). 그래서 §4의 1/√B도, §8의 선형 스케일링 법칙도 그대로 적용됩니다 — GPU를 8장으로 늘리는 것은 배치를 8배로 키우는 것이고, 그러면 학습률도 따라 올려야 합니다. 한 대에서 미니배치를 여러 번 나눠 계산해 더했다가 갱신하는 gradient accumulation도 똑같은 이야기입니다.

대가는 동기화 장벽입니다. 매 스텝 전원을 기다리므로 가장 느린 워커가 전체 속도를 정합니다. 한 대가 2배 느리면 8대 전체가 2배 느려집니다. 이 느린 워커를 스트래글러(straggler)라고 부릅니다.

비동기 SGD — 기다리지 않는다, 대신 낡은 θ를 쓴다

파라미터 서버를 두고, 워커는 θ를 받아 가 계산이 끝나는 대로 기울기를 밀어 넣습니다. 아무도 기다리지 않으니 노는 시간이 없습니다. 문제는 받아 간 θ와 밀어 넣을 때의 θ가 다르다는 것입니다 — 그 사이 다른 워커들이 이미 서버를 여러 번 갱신했으니까요. 이 시차를 지연(staleness) τ라고 하고, 워커 K대가 비슷한 속도라면 평균 τ ≈ K − 1입니다.

θt+1 = θt − η · gB(θt−τ) 기울기를 지금 위치가 아니라 과거 위치에서 계산한다
여기서 §2의 전제가 깨집니다. 미니배치 노이즈는 평균이 0이었습니다(불편 추정량) — 그래서 한 스텝씩은 틀려도 쌓이면 상쇄됐습니다. 그런데 지연에서 오는 오차는 E[gB(θt−τ)] = ∇L(θt−τ) ≠ ∇L(θt) — 평균이 0이 아닌 편향입니다. 많이 뽑는다고 상쇄되지 않습니다. 비동기 SGD를 "노이즈가 조금 더 낀 SGD"로 생각하면 안 되는 이유가 이것입니다.

그래서 누가 이기는가 — 벽시계 시간으로 재기

동기는 스텝당 품질이 좋고, 비동기는 시간당 스텝 수가 많습니다. 둘을 한 축에서 비교하려면 "스텝"이 아니라 벽시계 시간으로 재야 합니다(§5와 같은 논리입니다). 아래는 워커마다 처리 시간이 다른 상황을 이산 사건으로 모의한 것입니다.

실험 8 · 동기 vs 비동기 — 벽시계 시간 경주
워커 K대, 워커당 미니배치 B = 4. 1번 워커만 느리게(스트래글러) 만들 수 있습니다. 시간 단위는 "보통 워커가 미니배치 하나를 처리하는 시간".
동기 SGD (all-reduce) 비동기 SGD (파라미터 서버)
동기 라운드 수–
비동기 갱신 수–
비동기 평균 지연 τ–
동기 최종 L−L*–
비동기 최종 L−L*–
동기 ≡ 배치 K·B 검증–

스트래글러가 1.0×면 동기가 이깁니다 — 같은 시간에 처리하는 샘플 수가 비슷한데 동기 쪽 기울기가 더 정확하니까요. 스트래글러를 4×로 올려 보세요. 동기는 매 라운드 그 한 대를 기다리느라 라운드 수가 3분의 1 아래로(101 → 30) 줄지만, 비동기는 956 → 867로 거의 그대로입니다. 비동기가 존재하는 이유는 "더 좋은 기울기"가 아니라 "놀지 않는 워커"입니다. 그리고 같은 조건에서 η를 0.12로 올려 보세요 — 이번엔 지연이 발목을 잡아 순서가 다시 뒤집힙니다(동기 0.0003 vs 비동기 0.0825). K를 16으로 올리면 비동기는 아예 발산합니다.

비동기의 진짜 비용 — 학습률 상한이 내려간다

지연이 있으면 "지금 있지도 않은 위치의 기울기"로 걷는 셈이라, 걸음이 크면 목표를 지나쳐 진동합니다. 아래 격자는 지연 τ와 학습률 η를 바꿔 가며 같은 400번의 갱신 뒤 남은 거리를 잰 것입니다.

실험 9 · 지연 τ × 학습률 η 안정성 격자
모든 칸이 400번 갱신 · B = 4 · 같은 시작점. 칸의 값은 L(θ) − L*이고, 발산하면 "발산"으로 표시합니다.
τ = 0이 동기 SGD, τ ≈ K−1이 워커 K대짜리 비동기 SGD입니다.

τ = 0 줄은 η를 0.30까지 올려도 멀쩡합니다. τ = 16 줄은 η = 0.10에서 이미 터집니다. 워커를 늘릴수록(τ가 커질수록) 쓸 수 있는 학습률이 작아진다 — 비동기의 확장성이 여기서 막힙니다. 동기 SGD는 정반대로, 워커를 늘리면 배치가 커져서 학습률을 올릴 수 있습니다(§8 선형 스케일링).

Deep Speech 2가 동기를 고른 이유

2015년의 주류는 파라미터 서버 + 비동기 SGD였습니다. DS2는 정반대로 갔고, 근거가 정확도가 아니었습니다.

We find synchronous SGD useful because it is reproducible and deterministic. We have found that the appearance of non-determinism in our system often signals a serious bug. — Amodei et al., 4.1절

비동기 SGD는 같은 코드를 두 번 돌려도 결과가 다릅니다 — 어느 워커의 기울기가 몇 번째로 도착했는지가 매번 달라지니까요. 그러면 "어제보다 나빠졌는데 이게 내 코드 탓인지 운 탓인지"를 가릴 수가 없습니다. DS2는 디버깅 가능성을 근거로 동기 SGD를 고르고, 대신 통신을 직접 최적화했습니다(링 all-reduce 직접 구현, GPU 8장 이하에서 OpenMPI 대비 최대 20배). 자세한 내용은 How Deep Speech 2 Works §11에 있습니다.

오늘날. NCCL·InfiniBand·NVLink로 all-reduce가 싸지고, 선형 스케일링 법칙과 워밍업으로 큰 배치를 다루는 법이 정리되면서 동기 all-reduce가 사실상 표준이 됐습니다(PyTorch DistributedDataParallel이 하는 일이 바로 이것입니다). 비동기가 여전히 쓰이는 곳은 워커 성능이 제각각이거나 중간에 사라질 수 있는 환경(선점형 인스턴스, 연합 학습), 또는 갱신이 희소해서 충돌이 거의 없는 경우입니다 — 잠금 없이 공유 메모리를 덮어쓰는 Hogwild!(2011)가 후자의 대표입니다.

§10 정리 — 한 문장으로

GD는 손실의 진짜 기울기를 쓰고, SGD는 그 기울기의 불편 추정량을 씁니다. 추정에는 1/√B로 줄어드는 오차가 따라붙지만, 그 대가로 같은 계산량에 N/B배 더 많이 움직일 수 있고, 덤으로 정지점에 갇히지 않습니다. 대신 최솟값에 정확히 정착하지 못하므로 학습률을 줄여 노이즈 볼을 조입니다.

헷갈리기 쉬운 것 5가지

  1. SGD는 GD의 "빠른 근사"가 아니다. 방향의 기댓값은 완전히 같습니다. 다른 것은 분산뿐입니다.
  2. 배치를 키운다고 그만큼 좋아지지 않는다. 계산은 B에 비례해 늘지만 노이즈는 √B로만 줄어듭니다.
  3. "1 에폭"은 진척도의 단위가 아니다. 같은 1 에폭이 GD에겐 1스텝, B=256·N=128만이면 5,005스텝입니다.
  4. 상수 학습률로는 수렴하지 않는다. 손실 곡선이 평평해진 것은 "다 배웠다"가 아니라 "노이즈 볼에 닿았다"일 수 있습니다 — 학습률을 낮춰 보면 구분됩니다.
  5. 노이즈가 크다고 좋은 게 아니다. 탈출에는 유리하지만 정착에는 불리합니다. 그래서 초반엔 크게, 후반엔 작게 씁니다.
  6. GPU를 늘리는 것은 새로운 알고리즘이 아니다. 동기 SGD는 배치를 키우는 것과 정확히 같은 일이고, 비동기 SGD만이 다른 물건입니다 — 지연된 기울기는 편향이라 §2의 "평균 내면 맞는다"가 통하지 않습니다.

§11 이해도 퀴즈

부록 · 1951년, 신경망도 없던 시절에 이미 증명된 것

"확률적 경사 하강법"이라는 이름의 뿌리는 딥러닝보다 60년 이상 앞섭니다. 1951년 Herbert Robbins와 Sutton Monro는 A Stochastic Approximation Method에서 전혀 다른 문제를 풀고 있었습니다 — 약물의 반응 곡선처럼 측정할 때마다 값이 튀는 함수에서 M(x) = α가 되는 x를 어떻게 찾는가. 측정이 노이즈 투성이라 함수를 정확히 알 수 없는 상황입니다.

그들의 답은 xn+1 = xn − an(yn − α)였고, yn은 노이즈 섞인 한 번의 측정값입니다. 오늘날의 SGD와 같은 모양입니다. 그리고 Σan = ∞, Σan² < ∞이면 노이즈가 있어도 수렴한다는 것을 증명했습니다. §7에서 본 그 조건입니다. 신경망도, GPU도, 미니배치도 없던 시절에 나온 결과가 지금 우리가 학습률을 왜 감쇠시키는지를 설명하고 있는 셈입니다.

여기서 "stochastic"은 알고리즘이 무작위라는 뜻이 아니라, 우리가 손에 넣는 관측이 무작위라는 뜻입니다. 딥러닝에서 그 "노이즈 섞인 관측"의 정체가 바로 무작위로 뽑은 미니배치의 기울기고요. 이 연결을 신경망 학습에 본격적으로 가져온 것은 1980~90년대의 온라인 학습 연구, 그리고 대규모 학습에서의 이론적 이점을 정리한 Bottou (2010) 등입니다.

↩ §7로 돌아가기