最常見的神經網絡錯誤:
1)你沒有首先嘗試過擬合單個batch。
3)在.backward()之前忘記了.zero_grad()(在pytorch中)。
4)將softmaxed輸出傳遞給了期望原始logits的損失,還有其他嗎?
這篇文章將逐點分析這些錯誤是如何在PyTorch代碼示例中體現出來的。
代碼:https://github.com/missinglinkai/common-nn-mistakes
常見錯誤 #1 你沒有首先嘗試過擬合單個batch
Andrej說我們應該過擬合單個batch。為什么?好吧,當你過擬合了單個batch —— 你實際上是在確保模型在工作。我不想在一個巨大的數據集上浪費了幾個小時的訓練時間,只是為了發現因為一個小錯誤,它只有50%的準確性。當你的模型完全記住輸入時,你會得到的結果是對其最佳表現的很好的預測。
可能最佳表現為零,因為在執行過程中拋出了一個異常。但這沒關系,因為我們很快就能發現問題并解決它。總結一下,為什么你應該從數據集的一個小子集開始過擬合:
發現bug
估計最佳的可能損失和準確率
快速迭代
在PyTorch數據集中,你通常在dataloader上迭代。你的第一個嘗試可能是索引train_loader。
#TypeError:'DataLoader'objectdoesnotsupportindexing first_batch=train_loader[0]
你會立即看到一個錯誤,因為DataLoaders希望支持網絡流和其他不需要索引的場景。所以沒有__getitem__方法,這導致了[0]操作失敗,然后你會嘗試將其轉換為list,這樣就可以支持索引。
#slow,wasteful first_batch=list(train_loader)[0]
但這意味著你要評估整個數據集這會消耗你的時間和內存。那么我們還能嘗試什么呢?
在Python for循環中,當你輸入如下:
foriteminiterable: do_stuff(item)
你有效地得到了這個:
iterator=iter(iterable) try: whileTrue: item=next(iterator) do_stuff(item) exceptStopIteration: pass
調用“iter”函數來創建迭代器,然后在循環中多次調用該函數的“next”來獲取下一個條目。直到我們完成時,StopIteration被觸發。在這個循環中,我們只需要調用next, next, next… 。為了模擬這種行為但只獲取第一項,我們可以使用這個:
first=next(iter(iterable))
我們調用“iter”來獲得迭代器,但我們只調用“next”函數一次。注意,為了清楚起見,我將下一個結果分配到一個名為“first”的變量中。我把這叫做“next-iter” trick。在下面的代碼中,你可以看到完整的train data loader的例子:
forbatch_idx,(data,target)inenumerate(train_loader): #trainingcodehere
下面是如何修改這個循環來使用 first-iter trick :
first_batch=next(iter(train_loader)) forbatch_idx,(data,target)inenumerate([first_batch]*50): #trainingcodehere
你可以看到我將“first_batch”乘以了50次,以確保我會過擬合。
常見錯誤 #2: 忘記為網絡設置 train/eval 模式
為什么PyTorch關注我們是訓練還是評估模型?最大的原因是dropout。這項技術在訓練中隨機去除神經元。
想象一下,如果右邊的紅色神經元是唯一促成正確結果的神經元。一旦我們移除紅色神經元,它就迫使其他神經元訓練和學習如何在沒有紅色的情況下保持準確。這種drop-out提高了最終測試的性能 —— 但它對訓練期間的性能產生了負面影響,因為網絡是不全的。在運行腳本并查看MissingLink dashobard的準確性時,請記住這一點。
在這個特定的例子中,似乎每50次迭代就會降低準確度。
如果我們檢查一下代碼 —— 我們看到確實在train函數中設置了訓練模式。
deftrain(model,optimizer,epoch,train_loader,validation_loader): model.train()#???????????? forbatch_idx,(data,target)inexperiment.batch_loop(iterable=train_loader): data,target=Variable(data),Variable(target) #Inference output=model(data) loss_t=F.nll_loss(output,target) #Theiconicgrad-back-steptrio optimizer.zero_grad() loss_t.backward() optimizer.step() ifbatch_idx%args.log_interval==0: train_loss=loss_t.item() train_accuracy=get_correct_count(output,target)*100.0/len(target) experiment.add_metric(LOSS_METRIC,train_loss) experiment.add_metric(ACC_METRIC,train_accuracy) print('TrainEpoch:{}[{}/{}({:.0f}%)] Loss:{:.6f}'.format( epoch,batch_idx,len(train_loader), 100.*batch_idx/len(train_loader),train_loss)) withexperiment.validation(): val_loss,val_accuracy=test(model,validation_loader)#???????????? experiment.add_metric(LOSS_METRIC,val_loss) experiment.add_metric(ACC_METRIC,val_accuracy)
這個問題不太容易注意到,在循環中我們調用了test函數。
deftest(model,test_loader): model.eval() #...
在test函數內部,我們將模式設置為eval!這意味著,如果我們在訓練過程中調用了test函數,我們就會進eval模式,直到下一次train函數被調用。這就導致了每一個epoch中只有一個batch使用了drop-out ,這就導致了我們看到的性能下降。
修復很簡單 —— 我們將model.train()向下移動一行,讓如訓練循環中。理想的模式設置是盡可能接近推理步驟,以避免忘記設置它。修正后,我們的訓練過程看起來更合理,沒有中間的峰值出現。請注意,由于使用了drop-out ,訓練準確性會低于驗證準確性。
常用的錯誤 #3: 忘記在.backward()之前進行.zero_grad()
當在 “loss”張量上調用 “backward” 時,你是在告訴PyTorch從loss往回走,并計算每個權重對損失的影響有多少,也就是這是計算圖中每個節點的梯度。使用這個梯度,我們可以最優地更新權值。
這是它在PyTorch代碼中的樣子。最后的“step”方法將根據“backward”步驟的結果更新權重。從這段代碼中可能不明顯的是,如果我們一直在很多個batch上這么做,梯度會爆炸,我們使用的step將不斷變大。
output=model(input)#forward-pass loss_fn.backward()#backward-pass optimizer.step()#updateweightsbyanevergrowinggradient????????????
為了避免step變得太大,我們使用zero_grad方法。
output=model(input)#forward-pass optimizer.zero_grad()#resetgradient???? loss_fn.backward()#backward-pass optimizer.step()#updateweightsusingareasonablysizedgradient????
這可能感覺有點過于明顯,但它確實賦予了對梯度的精確控制。有一種方法可以確保你沒有搞混,那就是把這三個函數放在一起:
zero_grad
backward
step
在我們的代碼例子中,在完全不使用zero_grad的情況下。神經網絡開始變得更好,因為它在改進,但梯度最終會爆炸,所有的更新變得越來越垃圾,直到網絡最終變得無用。
調用backward之后再做zero_grad。什么也沒有發生,因為我們擦掉了梯度,所以權重沒有更新。剩下的唯一有變化的是dropout。
我認為在每次step方法被調用時自動重置梯度是有意義的。
在backward的時候不使用zero_grad的一個原因是,如果你每次調用step()時都要多次調用backward,例如,如果你每個batch只能將一個樣本放入內存中,那么一個梯度會噪聲太大,你想要在每個step中聚合幾個batch的梯度。另一個原因可能是在計算圖的不同部分調用backward—— 但在這種情況下,你也可以把損失加起來,然后在總和上調用backward。
常見錯誤 #4: 你把做完softmax的結果送到了需要原始logits的損失函數中
logits是最后一個全連接層的激活值。softmax也是同樣的激活值,但是經過了標準化。logits值,你可以看到有些是正的,一些是負的。而log_softmax之后的值,全是負值。如果看柱狀圖的話,可以看到分布式一樣的,唯一的差別就是尺度,但就是這個細微的差別,導致最后的數學計算完全不一樣了。但是為什么這是一個常見的錯誤呢?在PyTorch的官方MNIST例子中,查看forward方法,在最后你可以看到最后一個全連接層self.fc2,然后就是log_softmax。
但是當你查看官方的PyTorch resnet或者AlexNet模型的時候,你會發現這些模型在最后并沒有softmax層,最后得到就是全連接的輸出,就是logits。
這兩個的差別在文檔中沒有說的很清楚。如果你查看nll_loss函數,并沒有提得輸入是logits還是softmax,你的唯一希望是在示例代碼中發現nll_loss使用了log_softmax作為輸入。
原文標題:收藏 | 使用PyTorch時,最常見的4個錯誤
文章出處:【微信公眾號:深度學習自然語言處理】歡迎添加關注!文章轉載請注明出處。
責任編輯:haq
-
神經網絡
+關注
關注
42文章
4771瀏覽量
100720 -
pytorch
+關注
關注
2文章
808瀏覽量
13202
原文標題:收藏 | 使用PyTorch時,最常見的4個錯誤
文章出處:【微信號:zenRRan,微信公眾號:深度學習自然語言處理】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論