# 7.3 小批量隨機梯度下降
在每一次迭代中,梯度下降使用整個訓練數據集來計算梯度,因此它有時也被稱為批量梯度下降(batch gradient descent)。而隨機梯度下降在每次迭代中只隨機采樣一個樣本來計算梯度。正如我們在前幾章中所看到的,我們還可以在每輪迭代中隨機均勻采樣多個樣本來組成一個小批量,然后使用這個小批量來計算梯度。下面就來描述小批量隨機梯度下降。
設目標函數`$ f(\boldsymbol{x}): \mathbb{R}^d \rightarrow \mathbb{R} $`。在迭代開始前的時間步設為0。該時間步的自變量記為`$ \boldsymbol{x}_0\in \mathbb{R}^d $`,通常由隨機初始化得到。在接下來的每一個時間步`$ t>0 $`中,小批量隨機梯度下降隨機均勻采樣一個由訓練數據樣本索引組成的小批量`$ \mathcal{B}_t $`。我們可以通過重復采樣(sampling with replacement)或者不重復采樣(sampling without replacement)得到一個小批量中的各個樣本。前者允許同一個小批量中出現重復的樣本,后者則不允許如此,且更常見。對于這兩者間的任一種方式,都可以使用
```[tex]
\boldsymbol{g}_t \leftarrow \nabla f_{\mathcal{B}_t}(\boldsymbol{x}_{t-1}) = \frac{1}{|\mathcal{B}|} \sum_{i \in \mathcal{B}_t}\nabla f_i(\boldsymbol{x}_{t-1})
```
來計算時間步`$ t $`的小批量`$ \mathcal{B}_t $`上目標函數位于`$ \boldsymbol{x}_{t-1} $`處的梯度`$ \boldsymbol{g}_t $`。這里`$ |\mathcal{B}| $`代表批量大小,即小批量中樣本的個數,是一個超參數。同隨機梯度一樣,重復采樣所得的小批量隨機梯度`$ \boldsymbol{g}_t $`也是對梯度`$ \nabla f(\boldsymbol{x}_{t-1}) $`的無偏估計。給定學習率`$ \eta_t $` (取正數),小批量隨機梯度下降對自變量的迭代如下:
```[tex]
\boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \eta_t \boldsymbol{g}_t.
```
基于隨機采樣得到的梯度的方差在迭代過程中無法減小,因此在實際中,(小批量)隨機梯度下降的學習率可以在迭代過程中自我衰減,例如`$ \eta_t=\eta t^\alpha $`(通常`$ \alpha=-1 $`或者`$ -0.5 $`)、`$ \eta_t = \eta \alpha^t $`(如`$ \alpha=0.95 $`)或者每迭代若干次后將學習率衰減一次。如此一來,學習率和(小批量)隨機梯度乘積的方差會減小。而梯度下降在迭代過程中一直使用目標函數的真實梯度,無須自我衰減學習率。
小批量隨機梯度下降中每次迭代的計算開銷為`$ \mathcal{O}(|\mathcal{B}|) $`。當批量大小為1時,該算法即為隨機梯度下降;當批量大小等于訓練數據樣本數時,該算法即為梯度下降。當批量較小時,每次迭代中使用的樣本少,這會導致并行處理和內存使用效率變低。這使得在計算同樣數目樣本的情況下比使用更大批量時所花時間更多。當批量較大時,每個小批量梯度里可能含有更多的冗余信息。為了得到較好的解,批量較大時比批量較小時需要計算的樣本數目可能更多,例如增大迭代周期數。
## 7.3.1 讀取數據
本章里我們將使用一個來自NASA的測試不同飛機機翼噪音的數據集來比較各個優化算法 [1]。我們使用該數據集的前1,500個樣本和5個特征,并使用標準化對數據進行預處理。
```python
%matplotlib inline
import numpy as np
import time
import torch
from torch import nn, optim
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
def get_data_ch7(): # 本函數已保存在d2lzh_pytorch包中方便以后使用
data = np.genfromtxt('../../data/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500個樣本(每個樣本5個特征)
features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])
```
## 7.3.2 從零開始實現
3.2節(線性回歸的從零開始實現)中已經實現過小批量隨機梯度下降算法。我們在這里將它的輸入參數變得更加通用,主要是為了方便本章后面介紹的其他優化算法也可以使用同樣的輸入。具體來說,我們添加了一個狀態輸入`states`并將超參數放在字典`hyperparams`里。此外,我們將在訓練函數里對各個小批量樣本的損失求平均,因此優化算法里的梯度不需要除以批量大小。
``` python
def sgd(params, states, hyperparams):
for p in params:
p.data -= hyperparams['lr'] * p.grad.data
```
下面實現一個通用的訓練函數,以方便本章后面介紹的其他優化算法使用。它初始化一個線性回歸模型,然后可以使用小批量隨機梯度下降以及后續小節介紹的其他算法來訓練模型。
``` python
# 本函數已保存在d2lzh_pytorch包中方便以后使用
def train_ch7(optimizer_fn, states, hyperparams, features, labels,
batch_size=10, num_epochs=2):
# 初始化模型
net, loss = d2l.linreg, d2l.squared_loss
w = torch.nn.Parameter(torch.tensor(np.random.normal(0, 0.01, size=(features.shape[1], 1)), dtype=torch.float32),
requires_grad=True)
b = torch.nn.Parameter(torch.zeros(1, dtype=torch.float32), requires_grad=True)
def eval_loss():
return loss(net(features, w, b), labels).mean().item()
ls = [eval_loss()]
data_iter = torch.utils.data.DataLoader(
torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)
for _ in range(num_epochs):
start = time.time()
for batch_i, (X, y) in enumerate(data_iter):
l = loss(net(X, w, b), y).mean() # 使用平均損失
# 梯度清零
if w.grad is not None:
w.grad.data.zero_()
b.grad.data.zero_()
l.backward()
optimizer_fn([w, b], states, hyperparams) # 迭代模型參數
if (batch_i + 1) * batch_size % 100 == 0:
ls.append(eval_loss()) # 每100個樣本記錄下當前訓練誤差
# 打印結果和作圖
print('loss: %f, %f sec per epoch' % (ls[-1], time.time() - start))
d2l.set_figsize()
d2l.plt.plot(np.linspace(0, num_epochs, len(ls)), ls)
d2l.plt.xlabel('epoch')
d2l.plt.ylabel('loss')
```
當批量大小為樣本總數1,500時,優化使用的是梯度下降。梯度下降的1個迭代周期對模型參數只迭代1次。可以看到6次迭代后目標函數值(訓練損失)的下降趨向了平穩。
``` python
def train_sgd(lr, batch_size, num_epochs=2):
train_ch7(sgd, None, {'lr': lr}, features, labels, batch_size, num_epochs)
train_sgd(1, 1500, 6)
```
輸出:
```
loss: 0.243605, 0.014335 sec per epoch
```
<div align=center>
<img width="300" src="images/7.3_output1.png"/>
</div>
當批量大小為1時,優化使用的是隨機梯度下降。為了簡化實現,有關(小批量)隨機梯度下降的實驗中,我們未對學習率進行自我衰減,而是直接采用較小的常數學習率。隨機梯度下降中,每處理一個樣本會更新一次自變量(模型參數),一個迭代周期里會對自變量進行1,500次更新。可以看到,目標函數值的下降在1個迭代周期后就變得較為平緩。
``` python
train_sgd(0.005, 1)
```
輸出:
```
loss: 0.243433, 0.270011 sec per epoch
```
<div align=center>
<img width="300" src="images/7.3_output2.png"/>
</div>
雖然隨機梯度下降和梯度下降在一個迭代周期里都處理了1,500個樣本,但實驗中隨機梯度下降的一個迭代周期耗時更多。這是因為隨機梯度下降在一個迭代周期里做了更多次的自變量迭代,而且單樣本的梯度計算難以有效利用矢量計算。
當批量大小為10時,優化使用的是小批量隨機梯度下降。它在每個迭代周期的耗時介于梯度下降和隨機梯度下降的耗時之間。
``` python
train_sgd(0.05, 10)
```
輸出:
```
loss: 0.242805, 0.078792 sec per epoch
```
<div align=center>
<img width="300" src="images/7.3_output3.png"/>
</div>
## 7.3.3 簡潔實現
在PyTorch里可以通過創建`optimizer`實例來調用優化算法。這能讓實現更簡潔。下面實現一個通用的訓練函數,它通過優化算法的函數`optimizer_fn`和超參數`optimizer_hyperparams`來創建`optimizer`實例。
``` python
# 本函數與原書不同的是這里第一個參數優化器函數而不是優化器的名字
# 例如: optimizer_fn=torch.optim.SGD, optimizer_hyperparams={"lr": 0.05}
def train_pytorch_ch7(optimizer_fn, optimizer_hyperparams, features, labels,
batch_size=10, num_epochs=2):
# 初始化模型
net = nn.Sequential(
nn.Linear(features.shape[-1], 1)
)
loss = nn.MSELoss()
optimizer = optimizer_fn(net.parameters(), **optimizer_hyperparams)
def eval_loss():
return loss(net(features).view(-1), labels).item() / 2
ls = [eval_loss()]
data_iter = torch.utils.data.DataLoader(
torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)
for _ in range(num_epochs):
start = time.time()
for batch_i, (X, y) in enumerate(data_iter):
# 除以2是為了和train_ch7保持一致, 因為squared_loss中除了2
l = loss(net(X).view(-1), y) / 2
optimizer.zero_grad()
l.backward()
optimizer.step()
if (batch_i + 1) * batch_size % 100 == 0:
ls.append(eval_loss())
# 打印結果和作圖
print('loss: %f, %f sec per epoch' % (ls[-1], time.time() - start))
d2l.set_figsize()
d2l.plt.plot(np.linspace(0, num_epochs, len(ls)), ls)
d2l.plt.xlabel('epoch')
d2l.plt.ylabel('loss')
```
使用PyTorch重復上一個實驗。
``` python
train_pytorch_ch7(optim.SGD, {"lr": 0.05}, features, labels, 10)
```
輸出:
```
loss: 0.245491, 0.044150 sec per epoch
```
<div align=center>
<img width="300" src="images/7.3_output4.png"/>
</div>
## 小結
* 小批量隨機梯度每次隨機均勻采樣一個小批量的訓練樣本來計算梯度。
* 在實際中,(小批量)隨機梯度下降的學習率可以在迭代過程中自我衰減。
* 通常,小批量隨機梯度在每個迭代周期的耗時介于梯度下降和隨機梯度下降的耗時之間。
## 參考文獻
[1] 飛機機翼噪音數據集。https://archive.ics.uci.edu/ml/datasets/Airfoil+Self-Noise
-----------
> 注:除代碼外本節與原書此節基本相同,[原書傳送門](https://zh.d2l.ai/chapter_optimization/minibatch-sgd.html)
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯