1. 程式人生 > 其它 >小批量隨機梯度下降

小批量隨機梯度下降

技術標籤:DeepLearning學習python深度學習演算法

小批量隨機梯度下降

在每一次迭代中,梯度下降使用整個訓練資料集來計算梯度,因此它有時也被稱為批量梯度下降(batch gradient descent)。

  • 隨機梯度下降在每次迭代中只隨機取樣一個樣本來計算梯度。
  • 可以在每輪迭代中隨機均勻取樣多個樣本來組成一個小批量,然後使用這個小批量來計算梯度。

下面就來描述小批量隨機梯度下降

  • 設目標函式 f ( x ) : R d → R f(\boldsymbol{x}): \mathbb{R}^d \rightarrow \mathbb{R} f(x):RdR
  • 在迭代開始前的時間步設為0。
  • 該時間步的自變數記為 x 0 ∈ R d \boldsymbol{x}_0\in \mathbb{R}^d x0Rd,通常由隨機初始化得到

在接下來的每一個時間步 t > 0 t>0 t>0中,小批量隨機梯度下降隨機均勻取樣一個由訓練資料樣本索引組成的小批量 B t \mathcal{B}_t Bt。我們可以通過重複取樣(sampling with replacement)或者不重複取樣(sampling without replacement)得到一個小批量中的各個樣本。前者允許同一個小批量中出現重複的樣本,後者則不允許如此,且更常見。對於這兩者間的任一種方式,都可以使用

g t ← ∇ f B t ( x t − 1 ) = 1 ∣ B ∣ ∑ i ∈ B t ∇ f i ( x t − 1 ) \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})

gtfBt(xt1)=B1iBtfi(xt1)

來計算時間步 t t t的小批量 B t \mathcal{B}_t Bt上目標函式位於 x t − 1 \boldsymbol{x}_{t-1} xt1處的梯度 g t \boldsymbol{g}_t gt。這裡 ∣ B ∣ |\mathcal{B}| B代表批量大小,即小批量中樣本的個數,是一個超引數。同隨機梯度一樣,重複取樣所得的小批量隨機梯度 g t \boldsymbol{g}_t gt也是對梯度 ∇ f ( x t − 1 ) \nabla f(\boldsymbol{x}_{t-1})

f(xt1)的無偏估計。給定學習率 η t \eta_t ηt(取正數),小批量隨機梯度下降對自變數的迭代如下:

x t ← x t − 1 − η t g t . \boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \eta_t \boldsymbol{g}_t. xtxt1ηtgt.

基於隨機取樣得到的梯度的方差在迭代過程中無法減小,因此在實際中,(小批量)隨機梯度下降的學習率可以在迭代過程中自我衰減,例如 η t = η t α \eta_t=\eta t^\alpha ηt=ηtα(通常 α = − 1 \alpha=-1 α=1或者 − 0.5 -0.5 0.5)、 η t = η α t \eta_t = \eta \alpha^t ηt=ηαt(如 α = 0.95 \alpha=0.95 α=0.95)或者每迭代若干次後將學習率衰減一次。如此一來,

  • 學習率和(小批量)隨機梯度乘積的方差會減小
  • 而梯度下降在迭代過程中一直使用目標函式的真實梯度,無須自我衰減學習率

小批量隨機梯度下降中每次迭代的計算開銷為 O ( ∣ B ∣ ) \mathcal{O}(|\mathcal{B}|) O(B)

  • 當批量大小為1時,該演算法即為隨機梯度下降;當批量大小等於訓練資料樣本數時,該演算法即為梯度下降。
  • 當批量較小時,每次迭代中使用的樣本少,這會導致並行處理和記憶體使用效率變低。這使得在計算同樣數目樣本的情況下比使用更大批量時所花時間更多。
  • 當批量較大時,每個小批量梯度裡可能含有更多的冗餘資訊。
  • 為了得到較好的解,批量較大時比批量較小時需要計算的樣本數目可能更多,例如增大迭代週期數。

實現小批量隨機梯度下降

  • 使用一個來自NASA的測試不同飛機機翼噪音的資料集來比較各個優化演算法
  • 使用該資料集的前1,500個樣本和5個特徵
  • 使用標準化對資料進行預處理。
%matplotlib inline
import numpy as np
import time
import torch
from torch import nn, optim
import sys
from matplotlib import pyplot as plt
sys.path.append("..") 

def get_data_ch7():
    data = np.genfromtxt('data/airfoil_self_noise.dat', delimiter='\t')
    data = (data - data.mean(axis=0)) / data.std(axis=0)#標準化處理
    # data.mean(axis=0) 輸出矩陣為一行,求每列的平均值,同理data.mean(axis=1) 輸出矩陣為一列,求每行的平均值
	# data.std(axis=0) 輸出矩陣為一列,求每列的標準差,同理data.std(axis=1) 輸出矩陣為一列,求每行的標準差
    return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
    torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500個樣本(每個樣本5個特徵)
	#[:-1]=[0:-1],表示除了最後一列的所有列
features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])

def sgd(params, states, hyperparams):#梯度下降(懲罰)
    for p in params:
        p.data -= hyperparams['lr'] * p.grad.data

def linreg(X, w, b):#線性函式
    return torch.mm(X, w) + b

def squared_loss(y_hat, y): 
    # 注意這裡返回的是向量, 另外, pytorch裡的MSELoss並沒有除以 2
    return ((y_hat - y.view(y_hat.size())) ** 2) / 2

def set_figsize(figsize=(3.5, 2.5)):
    use_svg_display()
    # 設定圖的尺寸
    plt.rcParams['figure.figsize'] = figsize
    
#初始化一個線性迴歸模型,然後可以使用小批量隨機梯度下降以及其他演算法來訓練模型
def train_ch7(optimizer_fn, states, hyperparams, features, labels,
              batch_size=10, num_epochs=2):
    # 初始化模型
    net, loss = linreg, 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))
    set_figsize()
    plt.plot(np.linspace(0, num_epochs, len(ls)), ls)
    #np.linspace分割橫座標
    plt.xlabel('epoch')
    plt.ylabel('loss')

飛機機翼噪音資料集

當批量大小為樣本總數1,500時,優化使用的是梯度下降。梯度下降的1個迭代週期對模型引數只迭代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)

當批量大小為1時,優化使用的是隨機梯度下降。為了簡化實現,有關(小批量)隨機梯度下降的實驗中,我們未對學習率進行自我衰減,而是直接採用較小的常數學習率。隨機梯度下降中,每處理一個樣本會更新一次自變數(模型引數),一個迭代週期裡會對自變數進行1,500次更新。可以看到,目標函式值的下降在1個迭代週期後就變得較為平緩。

train_sgd(0.005, 1)

雖然隨機梯度下降和梯度下降在一個迭代週期裡都處理了1,500個樣本,但實驗中隨機梯度下降的一個迭代週期耗時更多。這是因為隨機梯度下降在一個迭代週期裡做了更多次的自變數迭代,而且單樣本的梯度計算難以有效利用向量計算。

當批量大小為10時,優化使用的是小批量隨機梯度下降。它在每個迭代週期的耗時介於梯度下降和隨機梯度下降的耗時之間。

train_sgd(0.05, 10)
  • 小批量隨機梯度每次隨機均勻取樣一個小批量的訓練樣本來計算梯度。
  • 在實際中,(小批量)隨機梯度下降的學習率可以在迭代過程中自我衰減。
  • 通常,小批量隨機梯度在每個迭代週期的耗時介於梯度下降和隨機梯度下降的耗時之間。