직접 확률적 경사 하강법(SGD) 구현하기: 100만 건의 데이터를 100건씩 사용하여 전체 데이터 경사 방식과 비교
요약
본 기사는 100만 건의 가상 로그 데이터를 이용해 회귀 문제를 풀며, 확률적 경사 하강법(SGD)을 구현하고 그 원리를 설명합니다. SGD는 전체 데이터 대신 무작위 미니 배치로 경사를 추정하여 계산량을 획기적으로 줄이는 방법을 다룹니다. 이를 통해 알고리즘의 효율성과 수학적 근거를 비교 분석합니다.
핵심 포인트
- SGD는 전체 데이터를 사용하지 않고, 무작위 샘플(미니 배치)로 경사를 추정하여 계산량을 대폭 절감합니다.
- 전체 데이터 방식과 SGD 방식을 비교하며 알고리즘적 효율성을 중점적으로 다룹니다.
- 회귀 문제의 목적 함수가 '데이터별 합' 형태일 때, 경사 역시 평균으로 근사 가능함을 설명합니다.
- 에포크(Epoch) 개념과 스텝 사이즈 변화가 수렴 과정에 미치는 영향을 수학적으로 분석합니다.
서론
『분석가를 위한 수리 최적화 입문』(이토 모토하루・하라다 코헤이・마츠오카 유키, 소시무, 2026년)의 제4장을 5편에 걸쳐 기사로 작성하고 있습니다. 이 글은 네 번째 글로, 확률적 경사 하강법(Stochastic Gradient Descent, SGD)을 다룹니다.
경사 방식(최대 하강법 등)은 반복할 때마다 전체 데이터를 사용하여 경사를 계산합니다. 데이터가 100건이라면 크게 신경 쓰지 않지만, 100만 건이 되면 한 번의 경사 계산에도 100만 건에 대한 합산이 필요합니다. SGD는 매번 무작위로 선택한 일부 데이터만을 사용하여 경사를 추정함으로써 이 계산량을 크게 줄이는 방법입니다. 데이터가 많은 머신러닝 분야에서 광범위하게 사용됩니다.
본 기사에서는 100만 건의 가상 접속 로그를 이용해 회귀 문제를 풀고, SGD와 전체 데이터를 사용하는 방식을 비교합니다. 글을 마치면 다음 질문에 답할 수 있도록 목표로 합니다.
- 회귀의 목적 함수가 '데이터별 합'으로 되어 있을 때, 왜 일부 데이터로 경사를 추정할 수 있는지
- 에포크(Epoch)란 무엇인가. SGD는 어떻게 멈추는지
- SGD는 왜 적은 계산량으로 답 근처에 도달하는지
- SGD는 왜 정답에 정확히 도달하지 못하는지. 스텝 사이즈를 바꾸면 무엇이 달라지는지
수식은 고등학교 미분과 대학 1학년 수준의 편미분 및 행렬을 사용합니다. 새로운 도구가 나올 때마다 짧은 예시로 설명하겠습니다.
이번에 만들 것
웹 서버의 가상 접속 로그 100만 건(요청당 1행)으로부터 응답 시간을 예측하는 다중 선형 회귀 문제를 풀어봅니다.
| 열 | 의미 | 생성 방법 |
|---|---|---|
| 응답 사이즈 | 반환된 데이터 크기 (KB) | 평균 50, 표준편차 20 |
| ... |
만들 파일은 다음 세 가지입니다.
| 파일 | 역할 |
|---|---|
access_log.py | 데이터를 생성하고 목적 함수와 경사를 준비합니다 |
sgd_optimizer.py | 확률적 경사 하강법 (SGD) |
sgd.py | SGD와 전체 데이터 경사를 사용하는 방식을 비교합니다 |
계산량은 실행 시간이 아니라 '전체 데이터를 몇 번 사용했는지'로 계산합니다. Python에서는 NumPy를 사용하여 한 번에 계산할 수 있는지 여부에 따라 실행 시간이 크게 달라지므로, 알고리즘 간의 비교가 되지 않기 때문입니다.
이번에는 다음 내용은 다루지 않습니다.
- 모멘텀법 등 이전 방향을 활용하는 기법
- 성분별로 스텝 사이즈를 변경하는 방법 (AdaGrad, RMSProp, Adam)
전제 조건
| 항목 | 구동 환경 |
|---|---|
| OS | Windows 11 |
| ... |
환경 준비
1. 목적 함수는 '데이터별 합'으로 되어 있다
설명 변수를 표준화(평균 0, 표준편차 1)한 값을
합의 미분은 미분의 합이므로, 경사 역시 데이터별 경사의 평균입니다.
따라서, 경사를 한 번 계산하려면,
2. 비교 대상: 전체 데이터를 사용하는 방식
비교 대상은 전체 데이터로 경사를 계산하고 스텝 사이즈
한 번의 반복에서 전체 데이터를 1번 사용합니다.
스텝 사이즈는,
3. 확률적 경사 하강법 (SGD)
3-1. 미니 배치로 경사를 추정하기
SGD는 무작위로 선택한 일부 데이터
선거 출구 조사에 비유하면 이해하기 쉽습니다. 전원에게 물어보지 않아도, 무작위로 선택한 일부 사람들의 평균으로 전체적인 경향을 대략적으로 알 수 있습니다. 조사할 때마다 결과는 조금씩 달라지지만, 편파된 방식을 따르지 않는다면 평균을 내면 전체 값에 일치합니다. SGD의 경사도 마찬가지입니다. 무작위로 선택한 100건의 평균은 고르는 매번 달라지지만, 평균을 내면 전체 데이터의 경사에 일치합니다. 한 번 계산에 사용하는 데이터는 100만 건에서 100건으로 줄어듭니다.
3-2. 에포크와 멈추는 방법
본 기사의 SGD에서는, 데이터 순서를 셔플(shuffle)한 후, 앞에서부터 100건씩 미니 배치로 나누어 하나씩 사용하며 업데이트합니다. 전체 데이터를 한 번 다 사용하는 것을 1 에포크(Epoch)라고 부르며, 에포크마다 다시 셔플합니다. 100만 건을 100건씩 사용하므로, 1 에포크 동안 1만 번 업데이트합니다.
| 방식 | 1회 업데이트에 사용하는 데이터 | 1 에포크 동안 업데이트하는 횟수 |
|---|---|
| 전체 데이터 경사 사용 방식 | 100만 건 | 1회 |
| SGD (미니 배치 100건) | 100건 | 1만 번 |
둘 다, 1 에포크 동안 사용하는 데이터의 양(계산량)은 같습니다. 다른 점은 그 계산을 몇 번 진행하느냐입니다.
종료 조건으로는 기울기(gradient)의 크기를 사용할 수 없습니다. 기울기의 크기를 확인하려면 전체 데이터로 기울기를 계산해야 하므로, 아낀 노력이 무효가 됩니다. 따라서 '몇 에포크를 돌리면 멈춘다'와 같이 횟수로 중지합니다.
4. Python으로 작성하여 비교하기
4-1. 데이터와 목적 함수 준비하기
"""웹 서버의 가상 접속 로그 100만 건과, 응답 시간을 예측하는 다중 선형 회귀(Multiple Linear Regression)의 목적 함수 및 기울기"""
import numpy as np
N = 1_000_000
...
| 부분 | 의미 |
|---|---|
0.9 * z1 + np.sqrt(1 - 0.9**2) * rng.normal(...) | z1과 상관계수가 0.9이고 표준편차가 1인 난수 생성 |
X | 1열과 표준화된 두 개의 설명 변수를 나열한 100만 행 3열의 행렬 |
lambda | 이름을 붙이지 않고 그 자리에서 만드는 작은 함수. f = lambda t: ...는 def f(t): return ...와 같은 의미입니다. |
f | 1장의 목적 함수. np.mean으로 전체 데이터의 평균을 구합니다. |
grad | 전체 데이터에 대한 기울기 (gradient) |
4-2. SGD 작성하기
"""확률적 경사 하강법: 미니 배치만으로 기울기를 계산하여 진행"""
import numpy as np
def sgd(X, y, x0, alpha, batch_size, epochs, record_every, seed=0):
...
| 부분 | 의미 |
|---|---|
rng.permutation(n) | 0부터 n - 1까지의 번호를 무작위 순서로 배열한 것. 에포크마다 재설정합니다. |
order[start:start + batch_size] | 배열된 번호에서 처음부터 100개씩 잘라냅니다. 이것이 미니 배치(mini-batch)입니다. |
Xb.T @ (Xb @ x - yb) / len(batch) | 3-1의 미니 배치 기울기. 전체 데이터의 기울기 grad와 동일한 식을 100개만으로 계산합니다. |
snapshots | 과정 중 지점 기록. 그래프를 그리는 데 사용됩니다. |
4-3. 전체 데이터 기울기를 사용하는 방법과 비교하기
SGD (미니 배치 100개,
"""확률적 경사 하강법과, 전체 데이터의 기울기를 사용하는 방법을, 전체 데이터를 사용한 횟수(에포크)로 비교"""
from pathlib import Path
import matplotlib
...
| 부분 | 의미 |
|---|---|
L | 2장의 곡률(curvature) 최대값. 헤세 행렬(Hessian matrix) |
f_star | 목적 함수의 최솟값. 연립 방정식을 풀어 lstsq로 구한 값에서의 값 |
gd_gaps, sgd_gaps | 최솟값과의 차이 (difference from minimum value) |
record_every=500 | 500회 업데이트(5만 건, 0.05 에포크)마다 점을 기록합니다. |
[f(s) - f_star for s in snapshots] | 기록된 지점에서의 목적 함수 값. 그래프를 그리기 위한 계산으로, SGD 계산에는 사용되지 않습니다. |
uv run python sgd.py
최솟값과의 차이 f - f*
0.05 에포크: SGD α=0.1/L 6.1e-02, SGD α=0.01/L 1.1e+01
1 에포크: 전체 데이터 4.7e+02, SGD α=0.1/L 2.1e-03, SGD α=0.01/L 4.2e-03
...
최솟값과의 차이 변화를 그래프로 그리면 다음과 같습니다 (세로축은 로그 스케일입니다).

결과를 순서대로 살펴봅니다.
데이터의 일부만 보고도 답 근처에 도달하는 SGD: SGD는 0.05 에포크(5만 건분) 만에 최솟값과의 차이를 0.061까지 낮췄습니다. 전체 데이터의 기울기를 사용하는 방법은 1 에포크에서 차이가 470이었으며, 차이가 0.01 이하로 떨어지기까지 22 에포크가 걸렸습니다. 같은 1 에포크 계산을 기준으로 할 때, 전체 데이터 방식은 단 한 번만 진행할 수 있는 반면, SGD는 1만 번이나 진행할 수 있습니다 ($\alpha = 0.1/L$)
SGD는 어느 지점부터 감소하지 않게 되었다: SGD는 2 에포크 이후로는 차이가 0.010.05 사이를 오르내릴 뿐, 계속해서 줄어들지 않았습니다. 반면 전체 데이터의 기울기를 사용하는 방법은 느리지만 확실하게 줄어들어, 30 에포크에서는 0.0039가 되어 이 SGD보다 작은 값을 기록했습니다 ($\alpha = 0.1/L$)0.004 범위까지 줄어들었습니다. 그 대신, 0.05 에포크 시점에서는 차이가 11로, $\alpha = 0.01/L$ 일 때보다 훨씬 느렸습니다 ($\alpha = 0.1/L$)
스텝 사이즈를 작게 하면 흔들림은 줄지만, 시작이 느려졌다: SGD는 2 에포크 이후 차이가 0.0005
최솟값
5. SGD가 답에 정확히 도달하지 못하는 이유
전체 데이터의 기울기는 답이 되는 지점에서 정확히 0이 됩니다. 하지만 미니 배치(mini-batch)의 기울기는 답이 되는 지점에서도 0이 되지 않습니다. 이는 100건 분량의 오차가 우연히 양(+)으로 치우치거나 음(-)으로 치우치기 때문입니다. 따라서 SGD는 답 근처에 도달해도, 미니 배치마다 조금씩 밀려나게 됩니다. 이 밀리는 정도는 스텝 사이즈(step size)에 비례합니다.
Bottou 외 (2018)의 정리 4.6 역시 상수 스텝 사이즈를 가진 SGD에 대해 목적 함수 값은 “답 주변, 스텝 사이즈에 비례하는 폭의 범위”까지밖에 근접하지 못함을 보여줍니다. 스텝 사이즈를 작게 하면 접근 속도는 느려지지만, 그 범위는 좁아집니다. 이번 연구에서도
따라서 실제로는 처음에는 비교적 큰 스텝 사이즈로 답 근처까지 빠르게 나아가고, 더 이상 줄어들지 않으면 스텝 사이즈를 줄이는 방식이 자주 사용됩니다. 같은 논문에서는 진행이 멈추면 스텝 사이즈를 절반으로 만드는 방법도 소개되었습니다.
막혔을 때 확인 항목
| 에러 또는 증상 | 원인 | 대처 방안 |
|---|---|---|
| SGD에서 목적 함수가 어느 지점부터 줄지 않고 오르내림 | 미니 배치 기울기의 산포(ばらつき)로 인해 답 주변을 계속 움직이고 있음 | 5장과 같이 스텝 사이즈를 중간에 작게 조정합니다. 미니 배치의 개수를 늘려도 산포는 줄어듭니다 |
| ... | PYTHONUTF8=1 uv run python sgd.py 와 같이 UTF-8 모드로 실행합니다 |
요약
본 기사에서는 100만 건의 데이터로 회귀 문제를 풀면서, 확률적 경사 하강법(SGD)과 전체 데이터를 사용하는 기울기 방식을 비교했습니다.
- 회귀 목적 함수는 데이터 1건당 평가의 평균이며, 기울기도 데이터별 기울기의 평균이었습니다. 따라서 무작위로 선택된 100건의 평균으로 전체 데이터의 기울기를 추정할 수 있었습니다.
- SGD는 1 에포크(전체 데이터 1회분 계산) 만에 1만 번 진행할 수 있어, 0.05 에포크에서 답 근처에 도달했습니다. 전체 데이터의 기울기를 사용하는 방식은 차이가 0.01 이하로 떨어지는 데 22 에포크가 걸렸습니다.
- 멈추는 조건으로 기울기의 크기는 사용할 수 없었기 때문에, 에포크 수로 중단했습니다.
- 상수 스텝 사이즈를 가진 SGD는 답 주변을 오르내리며 계속했고, 정확히 도달하지 못했습니다. 그 폭은 스텝 사이즈를 작게 하면 좁아졌지만, 시작은 느려졌습니다.
전체 데이터의 기울기를 사용하는 방식은 “1회 계산은 무겁지만, 꾸준히 답에 가까워진다”는 관계였고, SGD는 “1회 계산은 가볍지만, 답 주변에서 흔들린다”는 관계였습니다. 데이터가 많을수록 1회 계산이 가볍다는 것의 효과가 커집니다.
참고 자료
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn ML의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기