경사 하강법(Gradient Descent)은 선형 회귀 모델의 파라미터를 최적화하는 대표적인 방법이다. 전체 데이터를 사용하는 배치 경사 하강법은 계산 비용이 크고, 무작위로 하나의 샘플만 사용하는 확률적 경사 하강법(SGD)은 수렴이 불안정할 수 있다. 이 글에서는 그 중간인 미니배치 경사 하강법을 단계별로 살펴보고, 성능을 개선하는 두 가지 방법을 소개한다.
1. 확률적 경사 하강법 (SGD)
매 반복마다 하나의 샘플을 무작위로 선택하여 기울기를 계산한다. 아래 코드는 100개의 데이터 포인트를 생성하고 SGD로 theta를 추정한다.
import numpy as np
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
X_b = np.c_[np.ones((100, 1)), X]
epochs = 10000
lr = 0.001
m = 100
theta = np.random.randn(2, 1)
for epoch in range(epochs):
idx = np.random.randint(m)
x_i = X_b[idx:idx+1]
y_i = y[idx:idx+1]
grad = x_i.T.dot(x_i.dot(theta) - y_i)
theta -= lr * grad
print(theta)
2. 미니배치 경사 하강법
매 반복마다 일정 개수의 샘플(미니배치)을 사용한다. 배치 크기를 10으로 설정한 예제다.
import numpy as np
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
X_b = np.c_[np.ones((100, 1)), X]
epochs = 10000
lr = 0.001
batch_size = 10
m = 100
num_batches = m // batch_size
theta = np.random.randn(2, 1)
for epoch in range(epochs):
for i in range(num_batches):
idx = np.random.randint(m)
X_batch = X_b[idx:idx+batch_size]
y_batch = y[idx:idx+batch_size]
grad = X_batch.T.dot(X_batch.dot(theta) - y_batch)
theta -= lr * grad
print(theta)
3. 성능 개선 1: 에포크마다 데이터 셔플
에포크가 시작될 때마다 전체 데이터를 무작위로 섞어준다. 이렇게 하면 미니배치가 다양한 데이터 분포를 접하게 되어 수렴이 더 안정적이다.
import numpy as np
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
X_b = np.c_[np.ones((100, 1)), X]
epochs = 10000
lr = 0.001
batch_size = 10
m = 100
num_batches = m // batch_size
theta = np.random.randn(2, 1)
for epoch in range(epochs):
# 데이터 셔플
shuffle_idx = np.arange(m)
np.random.shuffle(shuffle_idx)
X_b_shuffled = X_b[shuffle_idx]
y_shuffled = y[shuffle_idx]
for i in range(num_batches):
X_batch = X_b_shuffled[i*batch_size : (i+1)*batch_size]
y_batch = y_shuffled[i*batch_size : (i+1)*batch_size]
grad = X_batch.T.dot(X_batch.dot(theta) - y_batch)
theta -= lr * grad
print(theta)
4. 성능 개선 2: 학습률 감소 (Learning Rate Decay)
반복 횟수가 증가함에 따라 학습률을 점차 줄이면 초기에는 빠르게 수렴하고 후반에는 세밀하게 조정할 수 있다. 아래는 t0 / (t + t1) 형태의 감소 함수를 사용한 예제다.
import numpy as np
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
X_b = np.c_[np.ones((100, 1)), X]
epochs = 10000
batch_size = 10
m = 100
num_batches = m // batch_size
t0, t1 = 5, 500
def learning_rate_schedule(t):
return t0 / (t + t1)
theta = np.random.randn(2, 1)
for epoch in range(epochs):
shuffle_idx = np.arange(m)
np.random.shuffle(shuffle_idx)
X_b_shuffled = X_b[shuffle_idx]
y_shuffled = y[shuffle_idx]
for i in range(num_batches):
X_batch = X_b_shuffled[i*batch_size : (i+1)*batch_size]
y_batch = y_shuffled[i*batch_size : (i+1)*batch_size]
grad = X_batch.T.dot(X_batch.dot(theta) - y_batch)
# 현재 반복 횟수에 따른 학습률 계산
iter_count = epoch * m + i
lr = learning_rate_schedule(iter_count)
theta -= lr * grad
print(theta)