SGD vs Batch vs Mini-Batch
Vibe Prompt
「幫我比較 Batch GD、SGD、Mini-Batch SGD 在線性回歸上的收斂速度,畫出損失曲線。」
import numpy as np
def batch_gd(X, y, lr=0.01, epochs=100):
m, n = X.shape
w = np.zeros(n)
losses = []
for _ in range(epochs):
pred = X @ w
loss = np.mean((pred - y)**2)
losses.append(loss)
grad = (2/m) * X.T @ (pred - y)
w -= lr * grad
return w, losses
def sgd(X, y, lr=0.01, epochs=100):
m, n = X.shape
w = np.zeros(n)
losses = []
for _ in range(epochs):
idx = np.random.randint(m)
xi, yi = X[idx:idx+1], y[idx:idx+1]
pred = xi @ w
loss = np.mean((X @ w - y)**2)
losses.append(loss)
grad = 2 * xi.T @ (pred - yi)
w -= lr * grad
return w, losses
def minibatch_sgd(X, y, lr=0.01, epochs=100, batch_size=32):
m, n = X.shape
w = np.zeros(n)
losses = []
for _ in range(epochs):
idx = np.random.choice(m, batch_size, replace=False)
Xb, yb = X[idx], y[idx]
pred = Xb @ w
loss = np.mean((X @ w - y)**2)
losses.append(loss)
grad = (2/len(idx)) * Xb.T @ (pred - yb)
w -= lr * grad
return w, losses
# 測試
np.random.seed(42)
X = np.random.randn(1000, 5)
true_w = np.array([3, -2, 1, 0.5, -1])
y = X @ true_w + np.random.randn(1000) * 0.1
w_batch, _ = batch_gd(X, y)
w_sgd, _ = sgd(X, y)
w_mini, _ = minibatch_sgd(X, y)
print(f"真實權重: {true_w}")
print(f"Batch GD: {np.round(w_batch, 3)}")
print(f"SGD: {np.round(w_sgd, 3)}")
print(f"Mini-Batch:{np.round(w_mini, 3)}")
深入理解:三種變體的收斂特性
收斂路徑比較
| 特性 | Batch GD | SGD | Mini-Batch SGD | |------|:--------:|:---:|:--------------:| | 每次資料量 | $m$(全部) | 1 | 32-256 | | 每步計算成本 | $O(mn)$ | $O(n)$ | $O(bn)$ | | 收斂穩定性 | 最穩定 | 最不穩定 | 中等 | | 逃脫局部極小 | ✗ | ✓ | ✓ | | 平行化效率 | 高 | 低 | 高 | | 泛化能力 | 較差(易 overfit) | 較好 | 較好 |
學習率排程 (Learning Rate Scheduling)
SGD 需要隨著訓練進行降低學習率,常見策略:
def learning_rate_decay(initial_lr, epoch, decay_type='step'):
if decay_type == 'step':
# 每 30 個 epochs 降為 1/10
return initial_lr * (0.1 ** (epoch // 30))
elif decay_type == 'exp':
# 指數衰減
return initial_lr * (0.95 ** epoch)
elif decay_type == 'cosine':
# 餘弦退火
return 0.5 * initial_lr * (1 + np.cos(np.pi * epoch / 100))
Batch Size 的影響
| Batch Size | 梯度雜訊 | 收斂速度 | 記憶體 | |:----------:|:--------:|:--------:|:-----:| | 1 (SGD) | 高 | 每步快,整體慢 | 低 | | 32-64 | 中 | 最佳平衡點 | 中 | | 128-256 | 低 | 每步慢,整體快 | 高 | | 1024+ (Large Batch) | 極低 | 可能泛化差 | 極高 |
實戰:收斂速度比較
import matplotlib.pyplot as plt
# 執行三種方法並記錄損失
w_batch, losses_batch = batch_gd(X, y, lr=0.01, epochs=100)
w_sgd, losses_sgd = sgd(X, y, lr=0.01, epochs=100)
w_mini, losses_mini = minibatch_sgd(X, y, lr=0.01, epochs=100, batch_size=32)
plt.figure(figsize=(10, 6))
plt.plot(losses_batch, label='Batch GD', linewidth=2)
plt.plot(losses_sgd, label='SGD (noisy)', alpha=0.7)
plt.plot(losses_mini, label='Mini-Batch (32)', linewidth=2)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('三種 GD 變體的收斂曲線比較')
plt.legend()
plt.yscale('log')
plt.grid(True)
plt.show()
print(f"Batch GD 最終損失: {losses_batch[-1]:.6f}")
print(f"SGD 最終損失: {losses_sgd[-1]:.6f}")
print(f"Mini-Batch 最終損失: {losses_mini[-1]:.6f}")
實務建議
- 預設使用 Mini-Batch SGD:batch size 32-128 是通用起點
- SGD 適合小資料集:< 10,000 筆資料,可以接受完整批次
- 學習率排程至關重要:Step Decay 或 Cosine Annealing 是常見選擇
- Large Batch 需要特殊處理:> 1024 時需用 Linear Scaling Rule 調整學習率
- Shuffle 資料很重要:每個 epoch 前隨機打亂資料,避免學到順序偏差
關鍵要點
- ✅ Batch GD:穩定但慢,適合小資料集
- ✅ SGD:快速但雜訊大,能逃脫局部極小值
- ✅ Mini-Batch SGD:速度與穩定的最佳平衡
- ✅ 學習率排程是 SGD 族系成功的關鍵
- ✅ Batch Size 32-128 是實務上最常見的選擇
梯度下降實戰要點
梯度下降是機器學習中最核心的最佳化演算法。理解它的變體(SGD、Momentum、Adam)對於訓練深度學習模型至關重要。
核心概念
- 梯度方向是函數增加最快的方向
- 學習率決定每一步的大小
- SGD 用隨機樣本近似梯度
- Momentum 加速收斂
- Adam 結合 Momentum + RMSProp
SGD 的收斂分析
SGD 的梯度有隨機性,不像 Batch GD 那樣穩定收斂。它的收斂行為需要從期望角度分析。
收斂定理
$$E[L(w_{t+1})] \leq L(w_t) - \eta |\nabla L(w_t)|^2 + \eta^2 \sigma^2$$
- $\eta |\nabla L|^2$:預期改善項
- $\eta^2 \sigma^2$:雜訊項
這告訴我們:學習率不能太大(否則雜訊項主導),也不能太小(否則改善太慢)。
Learning Rate Scheduling
實務上不該用固定學習率:
- Step Decay:每 N 個 epoch 將學習率減半
- Exponential Decay:$\eta_t = \eta_0 \times e^{-kt}$
- Cosine Annealing:餘弦退火
下一章預告:自動微分 AutoDiff
SGD 需要計算梯度。手動推導梯度既繁瑣又容易錯——下一章的自動微分讓框架幫你算。