- 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)行
- 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ì)算資源。
- 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:
...
- 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)我們所需的功能。
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.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ù)的字面意思,不解釋。
pytorch的可重復(fù)性問題
參考這篇博文:
https://blog.csdn.net/hyk_1996/article/details/84307108多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上。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ù)刪掉的,這就非常不建議了。
- 訓(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!')
- 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è)樣本。
- 優(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,避免不必要的麻煩。