Pytorch源碼理解: RNNbase LSTM

 0.Pytorch中LSTM 的使用方法

LSTM使用方法:

lstm=nn.LSTM(input_size, hidden_size,num_layers)
output,(hn,cn)=lstm(x,(h0,c0))

各參數的shape:

x: (seq_len, batch, input_size)

h0: (num_layers×num_directions, batch, hidden_size)

c0: (num_layers×num_directions, batch, hidden_size)

output: (seq_len, batch, num_directions×hidden_size)

hn: (num_layers×num_directions, batch, hidden_size)

cn: (num_layers×num_directions, batch, hidden_size)

 

1.Packed_Sequence問題

根據pack之後的結果,是按照豎向方法存儲,這使得我對packsequence輸入的順序正確性產生了理解問題,經過查詢官方文檔和內部實現後發現,這樣的操作是使得並行化更爲方便,同時也要更深刻的理解, RNN是按照time_step的方法來輸入序列的,這樣的排序使得處於同一個time_step的在內存空間上相互比鄰,比如在第一個step,rnn知道每個句子的第一個step是[ I,I,THIS,NO,YES],第二個step是[love,love,is,way],而如果是按照序列存儲[I,love,Mom,',s,cooking]則一個輸入裏每一個time_step都有,而非是豎存儲方式的每一個step都是一列,同時每一個step都是一列,這使得GPU的並行化更加容易。

2.內部結構

祭出這一張出名的LSTM圖吧,說什麼都不如這一張圖說的清楚,基於這個圖進行解釋。

LSTM

要注意幾點:
這是時間步上的展開,並不是3個LSTM單元,而是一個,輸出的隱狀態向量會直接循環輸入進輸入進,所以這三個是一個展開,而不是3個LSTM

3.源碼

好下面開始解讀LSTM的源碼:

可以見到LSTM繼承了RNNBase這個父類。

先看它的初始化,也挺長的:

可以看見在RNNBase裏還傳入了一個mode參數,這個mode在圖底部判斷了使用的模型名字,參數賦值就不說了,直接進入條件語句裏看,首先判斷了dropout是不是一個正常值,同時在我們只有一個LSTM的時候並不讓我們dropout,根據解釋是,dropout是在所有recurrent layer之後的,也就是隻有當我們的隱層狀態作爲第二個LSTM的輸入的時候才允許使用dropout,我個人這樣理解,既然第一層是明確的處理時序信息,那我們就不應該在訓練時使用dropout,因爲這樣會降低時序的處理能力,因爲drop掉一部分權重,會讓我們在第一層就損失時序信息,而當我們輸出隱層之後,由於隱層是不明確的時序信息,可以在訓練時dropout。隨後判斷我們使用的是什麼模型,當我們輸入LSTM的時候,門的維度是隱向量維度的四倍,因爲我們在 LSTM裏,我們一共有4個gate,分別是input_gate,控制了我有多少要輸入進去,而是forget_gate決定了LSTM裏cell有多少要遺忘,然後是output_gate決定了我有多少要傳入下一個狀態,還有就是cell_gate決定有多少要來更新這個cell,這四個gate控制了整個LSTM的讀寫過程,而每個gate的維度要和隱狀態要匹配。

我們先進入每一層,然後判斷該層是否是雙向LSTM,如果是的話,input的size將會乘以2,同時明確在第一層時以input_size輸入。

在這裏,我們把所有層要訓練的參數都添加進pytorch的Parameter中,當torch的parameter被在一個nn.Module下的神經網絡啓用時,它將自定把用Parameter包裝的向量作爲神經網絡中需要訓練的參數,次數我們有了每層的權值和偏置,然後包裝成一個元組作爲layer_params,即layer_params包含了該層的所有可訓練參數,如果有反響的話還會添加suffix後綴,隨後將所有可訓練參數都通過_all_weights.append添加進去。下面有個flatten函數,是用於cuda的,不作解讀,理解cpu版本,cuda的工作原理一樣,只不過可以更簡單的並行化。

在這個函數中,我們對parameters進行初始化。

這個函數檢查了要前向傳輸的自變量,當batch_size不爲空時,這以爲着我們的sequence已經是pack之後的了,當這個sequence是pad之後的我們輸入的維度便期待是2維,這正好和pack後的sequence對準,如果此處理解不了的話可以先看一下pack_sequence的函數功能。

當輸入維度不是期待維度時提出錯誤,然後在確定pack之後得到mini_batch的size,否則則以input的size來作爲mini_batch。然後判斷是否是雙向LST,如果是,則期待的隱狀態維度要變化,然後check隱狀態的size,不對則報錯。

在這裏定義了前向的傳播,也是最終的一部分,首先還是判斷sequence是否是pack之後的,如果是pack了的,則獲得data和batch大小。同時默認hx爲空,此處是如果我們沒有默認的隱向量輸入時,hx會啓用,並初始化爲0,隨後通過查詢_rnn_impls來知道我們要啓用的前向函數。

當模型是lstm時,則啓動_VF.lstm,進行前向傳播,而_VF.lstm是c++代碼,在pytorch上找到了相關源碼,貼c++代碼。

可見,在c++裏的這個_lstm_impl還是沒有告訴我們具體的計算過程,讓我們再看看這個函數做了什麼,首先,它將每個層的隱狀態向量和細胞狀態分層化給每一個layer,注意我們看到result是以_rnn_imp的調用出現的,再往下看,得到了result之後,我們又把每一層的隱狀態向量和細胞狀態組合起來,做成一個tuple返回,所以在這裏我們發現,所有的隱藏狀態並不是放在一個大矩陣裏不斷更新的,而是不斷的切分成一層一層的,計算之後,再合在一起返回,既可以理解爲分層後可以更快的並行同時將隱藏向量分層計算也比較符合LSTM的特性。

然後我們在追到result所調用的_rnn_impl了,這個應該已經是所有RNN網絡都要調用的一個模塊了,體現了LSTM只不過是RNN的一種形式。然而我們看到,這裏面還是沒有告訴我們是怎麼計算的。在這裏,首先是保存了一堆參數,然後直接判斷是否是,如果是,則構雙向層,然後調用apply_layer_stack得到雙層結果,如果不是也調用這個也會返回結果,那麼我們繼續找apply_layer_stack這個函數做了什麼。

在這裏,我們看到還是沒有告訴我們是怎麼計算的,但是我們先看看,首先檢查了的層數是否一致,然後複製給一些參數,隨後我們看到每一個層的layer_output都是調用layer得到的,layer的參數是上一層layer的input,隱狀態還有權重,然後我們看到,這裏有一個RNN的遞歸實現,每一個的最後隱狀態都會push__back進layer中,實現隱狀態進入下一時刻的LSTM,而下一層的layer_input則是等於上一層的layer_output的輸出,同時如果有dropout則會調用dropout,又由於在const裏定義了Layer&layer,在c++中相當於給Layer取了一個別名layer,所以我們去看Layer.

但是經過一番查詢後,還是沒有找到定義計算的過程,瀏覽了一整遍的代碼,在傳入的cell_parameters中,有LSTM,而在構建這個結構體時就已經進行了計算。

直到這裏,終於發現了計算過程和整體的定義,在這裏有一個cuda版本的我們不作閱讀,首先我們就看見了,它將集合起來的gate分成了4個gate,這和python中的操作不謀而合。然後就是根據門來更新細胞的狀態和隱向量,隨後組成一個tuple返回,再細查這個cell_params,它包括了所有的RNN帶有cell的結構,LSTM和GRU這兩個帶有cell的RNN

回到result這條語句上,這裏的rnn_impl就是輸入了LSTMCell的結構體進行了運算。

 

源自《Pytorch-RNNBASE-LSTM python+c源碼理解

 

參考:
【1】Understanding LSTM Networks​colah.github.io

【2】pytorch對可變長度序列的處理 - 深度學習1 - 博客園​www.cnblogs.com

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