天天看點

動手學PyTorch | (39) 小批量随機梯度下降

目錄

1. 小批量梯度下降

2. 讀取資料

3. 從0開始實作

4. 簡潔實作

5. 小結

1. 小批量梯度下降

在每一次疊代中,梯度下降使用整個訓練資料集來計算梯度,是以它有時也被稱為批量梯度下降 (batch gradient descent)。而随機梯度下降在每次疊代中隻随機采樣一個樣本來計算梯度。正如我們在前幾節中所看到的,我們還可以在每輪疊代中随機均勻采樣多個樣本來組成一個⼩批量,然後使用這個⼩批量來計算梯度。下⾯就來描述小批量随機梯度下降。

設⽬标函數f(x):

動手學PyTorch | (39) 小批量随機梯度下降

.在疊代開始前的時間步設為0。該時間步的⾃變量記為

動手學PyTorch | (39) 小批量随機梯度下降

,通常由随機初始化得到。在接下來的每⼀個時間步t>0中,⼩批量随機梯度下降随機均勻采樣⼀個由訓練資料樣本索引組成的⼩批量

動手學PyTorch | (39) 小批量随機梯度下降

 .我們可以通過􏰀重複采樣(sampling with replacement)或者不􏰀重複采樣(sampling without replacement)得到一個小批量中的各個樣本。前者允許同一個⼩小批量中出現重􏰀複的樣本(當不同類别的樣本數不均衡時,經常使用,在一個mini-batch中多采樣類别較少的樣本),後者則不允許如此,且更常見。對于這兩者間的任一種方式,都可以使⽤:

動手學PyTorch | (39) 小批量随機梯度下降

來計算時間步t的⼩批量 

動手學PyTorch | (39) 小批量随機梯度下降

上目标函數位于

動手學PyTorch | (39) 小批量随機梯度下降

處的梯度

動手學PyTorch | (39) 小批量随機梯度下降

.這裡

動手學PyTorch | (39) 小批量随機梯度下降

代表批量⼤小,即小批量中樣本的個數,是一個超參數。同随機梯度一樣,􏰀重複采樣所得的⼩批量随機梯度

動手學PyTorch | (39) 小批量随機梯度下降

也是對梯度

動手學PyTorch | (39) 小批量随機梯度下降

的⽆偏估計。給定學習率

動手學PyTorch | (39) 小批量随機梯度下降

,⼩批量随機梯度下降對自變量的疊代如下:

動手學PyTorch | (39) 小批量随機梯度下降

基于随機采樣得到的梯度的方差在疊代過程中無法減小,是以在實際中,(小批量)随機梯度下降的學習率可以在疊代過程中⾃我衰減,例如

動手學PyTorch | (39) 小批量随機梯度下降

(

動手學PyTorch | (39) 小批量随機梯度下降

)  

動手學PyTorch | (39) 小批量随機梯度下降

或者每疊代若幹次後将學習率衰減一次。如此一來,學習率和(⼩批量)随機梯度乘積的⽅差會減小。⽽梯度下降(batch)在疊代過程中一直使用⽬标函數的真實梯度,⽆須⾃我衰減學習率。

⼩批量随機梯度下降中每次疊代的計算開銷為O(|B|) 。當批量⼤小為1時,該算法即為随機梯度下降; 當批量⼤小等于訓練資料樣本數時,該算法即為梯度下降。當批量較小時,每次疊代中使用的樣本少, 這會導緻并行處理和記憶體使用效率變低。這使得在計算同樣數目樣本的情況下比使用更⼤批量時所花時間更多。當批量較⼤時,每個小批量梯度里可能含有更多的備援資訊。為了得到較好的解,批量較大時⽐批量較⼩時需要計算的樣本數⽬可能更多,例如增大疊代周期數。

2. 讀取資料

本章里我們将使用⼀個來⾃NASA的測試不同⻜機機翼噪音的資料集來⽐較各個優化算法。我們使⽤該資料集的前1,500個樣本和5個特征,并使用标準化對資料進行預處理。

%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

print(torch.__version__)
           
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
           
動手學PyTorch | (39) 小批量随機梯度下降

3. 從0開始實作

(線性回歸的從零開始實作)中已經實作過小批量随機梯度下降算法。我們在這里将它的輸⼊參數變得更加通用,主要是為了了⽅便後⾯介紹的其他優化算法也可以使⽤同樣的輸入。具體來說,我們添加了⼀個狀态輸入并将超參數放在字典里。此外,我們将在訓練函數里對各個⼩批量樣本的損失求平均,是以優化算法里的梯度不需要除以批量⼤小(已經有系數 1/batch_size)。

def sgd(params, states, hyperparams):
    for p in params:
        p.data -= hyperparams['lr'] * p.grad.data
           

下⾯實作⼀個通用的訓練函數,以⽅便後⾯介紹的其他優化算法使用。它初始化⼀個線性回歸模型,然後可以使⽤⼩批量随機梯度下降以及後續小節介紹的其他算法來訓練模型。

# 本函數已儲存在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個mini-batch記錄下目前訓練誤差
    # 列印結果和作圖
    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個疊代周期(epoch)對模型參數隻疊代(更新)1 次。可以看到6次疊代後⽬标函數值(訓練損失)的下降趨向了平穩。

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)
           
動手學PyTorch | (39) 小批量随機梯度下降

當批量⼤小為1時,優化使用的是随機梯度下降。為了簡化實作,有關(⼩批量)随機梯度下降的實驗中,我們未對學習率進⾏行⾃我衰減,⽽是直接采用較小的常數學習率。随機梯度下降中,每處理⼀個樣本會更新⼀次⾃變量(模型參數),一個疊代周期里會對⾃變量進行1,500次更新。可以看到,目标函數值的下降在1個疊代周期後就變得較為平緩。

train_sgd(0.005, 1)
           
動手學PyTorch | (39) 小批量随機梯度下降

雖然随機梯度下降和梯度下降在一個疊代周期里都處理了1,500個樣本,但實驗中随機梯度下降的一個疊代周期耗時更多。這是因為随機梯度下降在⼀個疊代周期里做了更多次的⾃變量疊代,⽽且單樣本的梯度計算難以有效利用⽮量計算(帶來的加速效果)。

當批量⼤小為10時,優化使用的是⼩批量随機梯度下降。它在每個疊代周期(epoch)的耗時介于梯度下降和随機梯度下降的耗時之間。

train_sgd(0.05, 10)
           
動手學PyTorch | (39) 小批量随機梯度下降

4. 簡潔實作

在PyTorch里可以通過建立optimizer實例來調用優化算法。這能讓實作更簡潔。下⾯實作一個通⽤的訓練函數,它通過優化算法的函數optimizer_fn和超參數optimizer_hyperparams來建立optimizer執行個體。

# 這裡第一個參數是優化器函數而不是優化器的名字
# 例如: 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():#自帶的mse沒有除以2
        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')
           
train_pytorch_ch7(optim.SGD, {"lr": 0.05}, features, labels, 10)
           
動手學PyTorch | (39) 小批量随機梯度下降

5. 小結

1)小批量随機梯度每次随機均勻采樣⼀個⼩批量的訓練樣本來計算梯度。

2)在實際中,(小批量)随機梯度下降的學習率可以在疊代過程中自我衰減。

3)通常,小批量随機梯度在每個疊代周期的耗時介于梯度下降和随機梯度下降的耗時之間。