PyTorch踩過的坑(長期更新,轉載請註明出處)

最近在知乎看到有人轉載了這裏的內容,提醒一下,歡迎轉載,但是請註明出處,謝謝。

 

1. nn.Module.cuda() 和 Tensor.cuda() 的作用效果差異

無論是對於模型還是數據,cuda()函數都能實現從CPU到GPU的內存遷移,但是他們的作用效果有所不同。

對於nn.Module:

model = model.cuda() 
model.cuda() 

上面兩句能夠達到一樣的效果,即對model自身進行的內存遷移。

對於Tensor:

和nn.Module不同,調用tensor.cuda()只是返回這個tensor對象在GPU內存上的拷貝,而不會對自身進行改變。因此必須對tensor進行重新賦值,即tensor=tensor.cuda().

例子:

model = create_a_model()
tensor = torch.zeros([2,3,10,10])
model.cuda()
tensor.cuda()
model(tensor)    # 會報錯
tensor = tensor.cuda()
model(tensor)    # 正常運行

 

2. PyTorch 0.4 計算累積損失的不同

以廣泛使用的模式total_loss += loss.data[0]爲例。Python0.4.0之前,loss是一個封裝了(1,)張量的Variable,但Python0.4.0的loss現在是一個零維的標量。對標量進行索引是沒有意義的(似乎會報 invalid index to scalar variable 的錯誤)。使用loss.item()可以從標量中獲取Python數字。所以改爲:

total_loss += loss.item()

如果在累加損失時未將其轉換爲Python數字,則可能出現程序內存使用量增加的情況。這是因爲上面表達式的右側原本是一個Python浮點數,而它現在是一個零維張量。因此,總損失累加了張量和它們的梯度歷史,這可能會產生很大的autograd 圖,耗費內存和計算資源。

 

3. PyTorch 0.4 編寫不限制設備的代碼

# torch.device object used throughout this script
device = torch.device("cuda" if use_cuda else "cpu")
model = MyRNN().to(device)

# train
total_loss= 0
for input, target in train_loader:
    input, target = input.to(device), target.to(device)
    hidden = input.new_zeros(*h_shape)       # has the same device & dtype as `input`
    ...                                                               # get loss and optimize
    total_loss += loss.item()

# test
with torch.no_grad():                                    # operations inside don't track history
    for input, targetin test_loader:
        ...

 

4. torch.Tensor.detach()的使用

detach()的官方說明如下:

Returns a new Tensor, detached from the current graph.
    The result will never require gradient.

假設有模型A和模型B,我們需要將A的輸出作爲B的輸入,但訓練時我們只訓練模型B. 那麼可以這樣做:

input_B = output_A.detach()

它可以使兩個計算圖的梯度傳遞斷開,從而實現我們所需的功能。

 

5. ERROR: Unexpected bus error encountered in worker. This might be caused by insufficient shared memory (shm)

出現這個錯誤的情況是,在服務器上的docker中運行訓練代碼時,batch size設置得過大,shared memory不夠(因爲docker限制了shm).解決方法是,將Dataloader的num_workers設置爲0.

 

6. pytorch中loss函數的參數設置

以CrossEntropyLoss爲例:

CrossEntropyLoss(self, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='elementwise_mean')
  • reduce = False,那麼 size_average 參數失效,直接返回向量形式的 loss,即batch中每個元素對應的loss.
  • reduce = True,那麼 loss 返回的是標量:
    • 如果 size_average = True,返回 loss.mean().
    • 如果 size_average = False,返回 loss.sum().
  • weight : 輸入一個1D的權值向量,爲各個類別的loss加權,如下公式所示:\text{loss}(x, class) = weight[class] \left(-x[class] + \log\left(\sum_j \exp(x[j])\right)\right) 
  • ignore_index : 選擇要忽視的目標值,使其對輸入梯度不作貢獻。如果 size_average = True,那麼只計算不被忽視的目標的loss的均值。
  • reduction : 可選的參數有:‘none’ | ‘elementwise_mean’ | ‘sum’, 正如參數的字面意思,不解釋。

 

7. pytorch的可重複性問題

參考這篇博文: https://blog.csdn.net/hyk_1996/article/details/84307108

 

8. 多GPU的處理機制

使用多GPU時,應該記住pytorch的處理邏輯是:

1.在各個GPU上初始化模型。

2.前向傳播時,把batch分配到各個GPU上進行計算。

3.得到的輸出在主GPU上進行彙總,計算loss並反向傳播,更新主GPU上的權值。

4.把主GPU上的模型複製到其它GPU上。

 

9. num_batches_tracked參數

今天讀取模型參數時出現了錯誤

KeyError: 'unexpected key "module.bn1.num_batches_tracked" in state_dict'

經過研究發現,在pytorch 0.4.1及後面的版本里,BatchNorm層新增了num_batches_tracked參數,用來統計訓練時的forward過的batch數目,源碼如下(pytorch0.4.1):

    if self.training and self.track_running_stats:
        self.num_batches_tracked += 1
        if self.momentum is None:  # use cumulative moving average
            exponential_average_factor = 1.0 / self.num_batches_tracked.item()
        else:  # use exponential moving average
            exponential_average_factor = self.momentum

大概可以看出,這個參數和訓練時的歸一化的計算方式有關。

因此,我們可以知道該錯誤是由於訓練和測試所用的pytorch版本(0.4.1版本前後的差異)不一致引起的。具體的解決方案是:如果是模型參數(Orderdict格式,很容易修改)裏少了num_batches_tracked變量,就加上去,如果是多了就刪掉。偷懶的做法是將load_state_dict的strict參數置爲False,如下所示:

load_state_dict(torch.load(weight_path), strict=False)

還看到有人直接修改pytorch 0.4.1的源代碼把num_batches_tracked參數刪掉的,這就非常不建議了。

 

10. 訓練時損失出現nan的問題

最近在訓練模型時出現了損失爲nan的情況,發現是個大坑。暫時先記錄着。

可能導致梯度出現nan的三個原因:

1.梯度爆炸。也就是說梯度數值超出範圍變成nan. 通常可以調小學習率、加BN層或者做梯度裁剪來試試看有沒有解決。

2.損失函數或者網絡設計。比方說,出現了除0,或者出現一些邊界情況導致函數不可導,比方說log(0)、sqrt(0).

3.髒數據。可以事先對輸入數據進行判斷看看是否存在nan.

補充一下nan數據的判斷方法:

注意!像nan或者inf這樣的數值不能使用 == 或者 is 來判斷!爲了安全起見統一使用 math.isnan() 或者 numpy.isnan() 吧。

例如:

import numpy as np

# 判斷輸入數據是否存在nan
if np.any(np.isnan(input.cpu().numpy())):
  print('Input data has NaN!')

# 判斷損失是否爲nan
if np.isnan(loss.item()):
  print('Loss value is NaN!')

 

11. ValueError: Expected more than 1 value per channel when training

當batch裏只有一個樣本時,再調用batch_norm就會報下面這個錯誤:

  raise ValueError('Expected more than 1 value per channel when training, got input size {}'.format(size))

沒有什麼特別好的解決辦法,在訓練前用 num_of_samples % batch_size 算一下會不會正好剩下一個樣本。

可以考慮將`DataLoader`的`drop_last`選項設爲True,這樣的話,當最後一個batch湊不滿時,就會捨棄掉。

 

12. 優化器的weight_decay項導致的隱蔽bug

我們都知道weight_decay指的是權值衰減,即在原損失的基礎上加上一個L2懲罰項,使得模型趨向於選擇更小的權重參數,起到正則化的效果。但是我經常會忽略掉這一項的存在,從而引發了意想不到的問題。

這次的坑是這樣的,在訓練一個ResNet50的時候,網絡的高層部分layer4暫時沒有用到,因此也並不會有梯度回傳,於是我就放心地將ResNet50的所有參數都傳遞給Optimizer進行更新了,想着layer4應該能保持原來的權重不變纔對。但是實際上,儘管layer4沒有梯度回傳,但是weight_decay的作用仍然存在,它使得layer4權值越來越小,趨向於0。後面需要用到layer4的時候,發現輸出異常(接近於0),才注意到這個問題的存在。

雖然這樣的情況可能不容易遇到,但是還是要謹慎:暫時不需要更新的權值,一定不要傳遞給Optimizer,避免不必要的麻煩。

 

13. 優化TensorDataset的數據加載速度

`TensorDataset`提供了已經完全加載到內存中的矩陣的數據讀取接口。在使用`TensorDataset`的時候,如果直接用`DataLoader`,會導致數據加載速度非常緩慢,嚴重拖慢訓練速度,分析和解決方案詳見https://huangbiubiu.github.io/2019/BEST-PRACTICE-PyTorch-TensorDataset/

(感謝網友sinat_26833407分享)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章