天天看點

PyTorch踩過的12坑

作者 | hyk_1996

 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權重,如下公式所示: 

PyTorch踩過的12坑

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 算一下會不會正好剩下一個樣本。 12. 優化器的weight_decay項導緻的隐蔽bug 我們都知道weight_decay指的是權值衰減,即在原損失的基礎上加上一個l2懲罰項,使得模型趨向于選擇更小的權重參數,起到正則化的效果。但是我經常會忽略掉這一項的存在,進而引發了意想不到的問題。

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

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

PyTorch踩過的12坑