PyTorch踩過的12坑精選

  1. nn.Module.cuda() 和 Tensor.cuda() 的作用效果差異
    無論是對(duì)于模型還是數(shù)據(jù),cuda()函數(shù)都能實(shí)現(xiàn)從CPU到GPU的內(nèi)存遷移,但是他們的作用效果有所不同。

對(duì)于nn.Module:

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

上面兩句能夠達(dá)到一樣的效果,即對(duì)model自身進(jìn)行的內(nèi)存遷移。

對(duì)于Tensor:

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

例子:

model = create_a_model()
tensor = torch.zeros([2,3,10,10])
model.cuda()
tensor.cuda()
model(tensor)    # 會(huì)報(bào)錯(cuò)
tensor = tensor.cuda()
model(tensor)    # 正常運(yùn)行
  1. PyTorch 0.4 計(jì)算累積損失的不同
    以廣泛使用的模式total_loss += loss.data[0]為例。Python0.4.0之前,loss是一個(gè)封裝了(1,)張量的Variable,但Python0.4.0的loss現(xiàn)在是一個(gè)零維的標(biāo)量。對(duì)標(biāo)量進(jìn)行索引是沒有意義的(似乎會(huì)報(bào) invalid index to scalar variable 的錯(cuò)誤)。使用loss.item()可以從標(biāo)量中獲取Python數(shù)字。所以改為:
total_loss += loss.item()

如果在累加損失時(shí)未將其轉(zhuǎn)換為Python數(shù)字,則可能出現(xiàn)程序內(nèi)存使用量增加的情況。這是因?yàn)樯厦姹磉_(dá)式的右側(cè)原本是一個(gè)Python浮點(diǎn)數(shù),而它現(xiàn)在是一個(gè)零維張量。因此,總損失累加了張量和它們的梯度歷史,這可能會(huì)產(chǎn)生很大的autograd 圖,耗費(fèi)內(nèi)存和計(jì)算資源。

  1. PyTorch 0.4 編寫不限制設(shè)備的代碼
# 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:
        ...
 
  1. torch.Tensor.detach()的使用
    detach()的官方說明如下:
Returns a new Tensor, detached from the current graph.
    The result will never require gradient.

假設(shè)有模型A和模型B,我們需要將A的輸出作為B的輸入,但訓(xùn)練時(shí)我們只訓(xùn)練模型B. 那么可以這樣做:

input_B = output_A.detach()

它可以使兩個(gè)計(jì)算圖的梯度傳遞斷開,從而實(shí)現(xiàn)我們所需的功能。

  1. ERROR: Unexpected bus error encountered in worker. This might be caused by insufficient shared memory (shm)
    出現(xiàn)這個(gè)錯(cuò)誤的情況是,在服務(wù)器上的docker中運(yùn)行訓(xùn)練代碼時(shí),batch size設(shè)置得過大,shared memory不夠(因?yàn)閐ocker限制了shm).解決方法是,將Dataloader的num_workers設(shè)置為0.

  2. pytorch中l(wèi)oss函數(shù)的參數(shù)設(shè)置
    以CrossEntropyLoss為例:

CrossEntropyLoss(self, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='elementwise_mean')

若 reduce = False,那么 size_average 參數(shù)失效,直接返回向量形式的 loss,即batch中每個(gè)元素對(duì)應(yīng)的loss.
若 reduce = True,那么 loss 返回的是標(biāo)量:
如果 size_average = True,返回 loss.mean().
如果 size_average = False,返回 loss.sum().
weight : 輸入一個(gè)1D的權(quán)值向量,為各個(gè)類別的loss加權(quán),如下公式所示:

ignore_index : 選擇要忽視的目標(biāo)值,使其對(duì)輸入梯度不作貢獻(xiàn)。如果 size_average = True,那么只計(jì)算不被忽視的目標(biāo)的loss的均值。
reduction : 可選的參數(shù)有:‘none’ | ‘elementwise_mean’ | ‘sum’, 正如參數(shù)的字面意思,不解釋。

  1. pytorch的可重復(fù)性問題
    參考這篇博文:
    https://blog.csdn.net/hyk_1996/article/details/84307108

  2. 多GPU的處理機(jī)制
    使用多GPU時(shí),應(yīng)該記住pytorch的處理邏輯是:
    1)在各個(gè)GPU上初始化模型。
    2)前向傳播時(shí),把batch分配到各個(gè)GPU上進(jìn)行計(jì)算。
    3)得到的輸出在主GPU上進(jìn)行匯總,計(jì)算loss并反向傳播,更新主GPU上的權(quán)值。
    4)把主GPU上的模型復(fù)制到其它GPU上。

  3. num_batches_tracked參數(shù)
    今天讀取模型參數(shù)時(shí)出現(xiàn)了錯(cuò)誤

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

經(jīng)過研究發(fā)現(xiàn),在pytorch 0.4.1及后面的版本里,BatchNorm層新增了num_batches_tracked參數(shù),用來統(tǒng)計(jì)訓(xùn)練時(shí)的forward過的batch數(shù)目,源碼如下(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

大概可以看出,這個(gè)參數(shù)和訓(xùn)練時(shí)的歸一化的計(jì)算方式有關(guān)。

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

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

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

  1. 訓(xùn)練時(shí)損失出現(xiàn)nan的問題
    最近在訓(xùn)練模型時(shí)出現(xiàn)了損失為nan的情況,發(fā)現(xiàn)是個(gè)大坑。暫時(shí)先記錄著。

可能導(dǎo)致梯度出現(xiàn)nan的三個(gè)原因:
1.梯度爆炸。也就是說梯度數(shù)值超出范圍變成nan. 通??梢哉{(diào)小學(xué)習(xí)率、加BN層或者做梯度裁剪來試試看有沒有解決。
2.損失函數(shù)或者網(wǎng)絡(luò)設(shè)計(jì)。比方說,出現(xiàn)了除0,或者出現(xiàn)一些邊界情況導(dǎo)致函數(shù)不可導(dǎo),比方說log(0)、sqrt(0).
3.臟數(shù)據(jù)??梢允孪葘?duì)輸入數(shù)據(jù)進(jìn)行判斷看看是否存在nan.

補(bǔ)充一下nan數(shù)據(jù)的判斷方法:

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

例如:

import numpy as np

# 判斷輸入數(shù)據(jù)是否存在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!')
  1. ValueError: Expected more than 1 value per channel when training
    當(dāng)batch里只有一個(gè)樣本時(shí),再調(diào)用batch_norm就會(huì)報(bào)下面這個(gè)錯(cuò)誤:
 raise ValueError('Expected more than 1 value per channel when training, got input size {}'.format(size))

沒有什么特別好的解決辦法,在訓(xùn)練前用 num_of_samples % batch_size 算一下會(huì)不會(huì)正好剩下一個(gè)樣本。

  1. 優(yōu)化器的weight_decay項(xiàng)導(dǎo)致的隱蔽bug
    我們都知道weight_decay指的是權(quán)值衰減,即在原損失的基礎(chǔ)上加上一個(gè)L2懲罰項(xiàng),使得模型趨向于選擇更小的權(quán)重參數(shù),起到正則化的效果。但是我經(jīng)常會(huì)忽略掉這一項(xiàng)的存在,從而引發(fā)了意想不到的問題。

這次的坑是這樣的,在訓(xùn)練一個(gè)ResNet50的時(shí)候,網(wǎng)絡(luò)的高層部分layer4暫時(shí)沒有用到,因此也并不會(huì)有梯度回傳,于是我就放心地將ResNet50的所有參數(shù)都傳遞給Optimizer進(jìn)行更新了,想著layer4應(yīng)該能保持原來的權(quán)重不變才對(duì)。但是實(shí)際上,盡管layer4沒有梯度回傳,但是weight_decay的作用仍然存在,它使得layer4權(quán)值越來越小,趨向于0。后面需要用到layer4的時(shí)候,發(fā)現(xiàn)輸出異常(接近于0),才注意到這個(gè)問題的存在。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容