梯度累加(Gradient Accumulation)


 

 上圖也是某種意義上的梯度累加:一般是直接加總或者取平均,這樣操作是scale了,其實影響不大,只是確保loss計算時的value不至於太大。batchsize超過64的情況不多(batchsize太大會有副作用),這時候優化的粒度沒那么細,scale操作適當又做了懲罰。可能在某些時候比不加收斂更快


 

  我們在訓練神經網絡的時候,超參數batch size的大小會對最終的模型效果產生很大的影響。一定條件下,batch size設置的越大,模型就會越穩定。batch size的值通常設置在 8-32 之間,但是當我們做一些計算量需求大的任務(例如語義分割、GAN等)或者輸入圖片尺寸太大的時候,我們的batch size往往只能設置為2或者4,否則就會出現 “CUDA OUT OF MEMORY” 的不可抗力報錯。

  貧窮是促進人類進步的階梯,如何在有限的計算資源的條件下,訓練時采用更大的batch size呢?這就是梯度累加(Gradient Accumulation)技術了。

  我們以Pytorch為例,一個神經網絡的訓練過程通常如下:

 

    for i, (inputs, labels) in enumerate(trainloader):
        optimizer.zero_grad()                   # 梯度清零
        outputs = net(inputs)                   # 正向傳播
        loss = criterion(outputs, labels)       # 計算損失
        loss.backward()                         # 反向傳播,計算梯度
        optimizer.step()                        # 更新參數
        if (i+1) % evaluation_steps == 0:
            evaluate_model()

  

  從代碼中可以很清楚地看到神經網絡是如何做到訓練的:
1.將前一個batch計算之后的網絡梯度清零
2.正向傳播,將數據傳入網絡,得到預測結果
3.根據預測結果與label,計算損失值
4.利用損失進行反向傳播,計算參數梯度
5.利用計算的參數梯度更新網絡參數

  下面來看梯度累加是如何做的:

 

    for i, (inputs, labels) in enumerate(trainloader):
        outputs = net(inputs)                   # 正向傳播
        loss = criterion(outputs, labels)       # 計算損失函數
        loss = loss / accumulation_steps        # 損失標准化
        loss.backward()                         # 反向傳播,計算梯度
        if (i+1) % accumulation_steps == 0:
            optimizer.step()                    # 更新參數
            optimizer.zero_grad()               # 梯度清零
            if (i+1) % evaluation_steps == 0:
                evaluate_model()

  


1.正向傳播,將數據傳入網絡,得到預測結果
2.根據預測結果與label,計算損失值
3.利用損失進行反向傳播,計算參數梯度
4.重復1-3,不清空梯度,而是將梯度累加
5.梯度累加達到固定次數之后,更新參數,然后將梯度清零

  總結來講,梯度累加就是每計算一個batch的梯度,不進行清零,而是做梯度的累加,當累加到一定的次數之后,再更新網絡參數,然后將梯度清零。

  通過這種參數延遲更新的手段,可以實現與采用大batch size相近的效果。在平時的實驗過程中,我一般會采用梯度累加技術,大多數情況下,采用梯度累加訓練的模型效果,要比采用小batch size訓練的模型效果要好很多。

參考資料

1.Loss Normalization
2.Model.zero_grad() or optimizer.zero_grad()?
3.A trick to use bigger batches for training: gradient accumulation
4.PyTorch中在反向傳播前為什么要手動將梯度清零?
5.Training Neural Nets on Larger Batches: Practical Tips for 1-GPU, Multi-GPU & Distributed setups

 

原文鏈接:https://cowarder.site/2019/10/29/Gradient-Accumulation/


使用PyTorch實現梯度累加變相擴大batch

PyTorch中在反向傳播前為什么要手動將梯度清零? - Pascal的回答 - 知乎
https://www.zhihu.com/question/303070254/answer/573037166

這種模式可以讓梯度玩出更多花樣,比如說梯度累加(gradient accumulation)

傳統的訓練函數,一個batch是這么訓練的:

for i,(images,target) in enumerate(train_loader):
    # 1. input output
    images = images.cuda(non_blocking=True)
    target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
    outputs = model(images)
    loss = criterion(outputs,target)
 
    # 2. backward
    optimizer.zero_grad()   # reset gradient
    loss.backward()
    optimizer.step()

  

 
 

  1. 簡單的說就是進來一個batch的數據,計算一次梯度,更新一次網絡獲取loss:輸入圖像和標簽,通過infer計算得到預測值,計算損失函數;
  2. optimizer.zero_grad()清空過往梯度;
  3. loss.backward()反向傳播,計算當前梯度
  4. optimizer.step()根據梯度更新網絡參數

使用梯度累加是這么寫的:

 
for i,(images,target) in enumerate(train_loader):
    # 1. input output
    images = images.cuda(non_blocking=True)
    target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
    outputs = model(images)
    loss = criterion(outputs,target)
 
    # 2.1 loss regularization
    loss = loss/accumulation_steps
    # 2.2 back propagation
    loss.backward()
 
    # 3. update parameters of net
    if((i+1)%accumulation_steps)==0:
        # optimizer the net
        optimizer.step()        # update parameters of net
        optimizer.zero_grad()   # reset gradient

  


  1. 總結來說:梯度累加就是,每次獲取1個batch的數據,計算1次梯度,梯度不清空,不斷累加,累加一定次數后,根據累加的梯度更新網絡參數,然后清空梯度,進行下一次循環。
    獲取loss:輸入圖像和標簽,通過infer計算得到預測值,計算損失函數;
  2. loss.backward() 反向傳播,計算當前梯度
  3. 多次循環步驟1-2,不清空梯度,使梯度累加在已有梯度上;
  4. 梯度累加了一定次數后,先 optimizer.step() 根據累計的梯度更新網絡參數,然后 optimizer.zero_grad() 清空過往梯度,為下一波梯度累加做准備;

一定條件下,batchsize越大訓練效果越好,梯度累加則實現了batchsize的變相擴大,如果 accumulation_steps 為8,則batchsize '變相' 擴大了8倍,是我們這種乞丐實驗室解決顯存受限的一個不錯的trick,使用時需要注意,學習率也要適當放大。

更新1:關於BN是否有影響,之前有人是這么說的:

As far as I know, batch norm statistics get updated on each forward pass, so no problem if you don't do .backward() every time.

BN的估算是在forward階段就已經完成的,並不沖突,只是 accumulation_steps=8 和真實的batchsize放大八倍相比,效果自然是差一些,畢竟八倍Batchsize的BN估算出來的均值和方差肯定更精准一些。

更新2:根據李韶華的分享,可以適當調低BN自己的momentum參數:

bn自己有個momentum參數:x_new_running = (1 - momentum) * x_running + momentum * x_new_observed. momentum越接近0,老的running stats記得越久,所以可以得到更長序列的統計信息

我簡單看了下PyTorch 1.0的源碼:https://github.com/pytorch/pytorch/blob/162ad945902e8fc9420cbd0ed432252bd7de673a/torch/nn/modules/batchnorm.py#L24,BN類里面momentum這個屬性默認為0.1,可以嘗試調節下。

借助梯度累加,避免同時計算多個損失時存儲多個計算圖

PyTorch中在反向傳播前為什么要手動將梯度清零? - Forever123的回答 - 知乎
https://www.zhihu.com/question/303070254/answer/608153308

原因在於在PyTorch中,計算得到的梯度值會進行累加。

而這樣的好處可以從內存消耗的角度來看。

1. Edition1

在PyTorch中,multi-task任務一個標准的train from scratch流程為:

 
for idx, data in enumerate(train_loader):
    xs, ys = data
    pred1 = model1(xs)
    pred2 = model2(xs)
 
    loss1 = loss_fn1(pred1, ys)
    loss2 = loss_fn2(pred2, ys)
 
    ******
    loss = loss1 + loss2
    optmizer.zero_grad()
    loss.backward()
    ++++++
    optmizer.step()

  


上述代碼執行到 ****** 時,內存中是包含了兩張計算圖的,而隨着求和得到loss,這兩張圖進行了合並,而且大小的變化可以忽略。從PyTorch的設計原理上來說,在每次進行前向計算得到pred時,會產生一個**用於梯度回傳的計算圖,這張圖儲存了進行back propagation需要的中間結果,當調用了 ****.backward()** 后,會從內存中將這張圖進行釋放。

  • 執行到 ++++++ 時,得到對應的grad值並且釋放內存。這樣,訓練時必須存儲兩張計算圖,而如果loss的來源組成更加復雜,內存消耗會更大。

2. Edition2

為了減小每次的內存消耗,借助梯度累加,又有,有如下變種。

 
for idx, data in enumerate(train_loader):
    xs, ys = data
 
    optmizer.zero_grad()
 
    # 計算d(l1)/d(x)
    pred1 = model1(xs) #生成graph1
    loss = loss_fn1(pred1, ys)
    loss.backward()  #釋放graph1
 
    # 計算d(l2)/d(x)
    pred2 = model2(xs)#生成graph2
    loss2 = loss_fn2(pred2, ys)
    loss.backward()  #釋放graph2
 
    # 使用d(l1)/d(x)+d(l2)/d(x)進行優化
    optmizer.step()

  


3. Other
可以從代碼中看出,利用梯度累加,可以在最多保存一張計算圖的情況下進行multi-task任務的訓練。

另外一個理由就是在內存大小不夠的情況下疊加多個batch的grad作為一個大batch進行迭代,因為二者得到的梯度是等價的。

綜上可知,這種梯度累加的思路是對內存的極大友好,是由FAIR的設計理念出發的。

相關鏈接

本文作者:lart

本文鏈接:https://www.cnblogs.com/lart/p/11628696.html

版權聲明:本作品采用知識共享署名-非商業性使用-禁止演繹 2.5 中國大陸許可協議進行許可。

知識無價。對於有用的知識,請為后來者指明出處。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM