pytorch學習筆記(7):RNN 和 LSTM 實現分類和迴歸


在第三篇文章中,咱們介紹了 pytorch 中的一些常見網絡層。可是這些網絡層都是在 CNN 中比較常見的一些層,關於深度學習,咱們確定最瞭解的兩個知識點就是 CNN 和 RNN。那麼如何實現一個 RNN 呢?這篇文章咱們用 RNN 實現一個分類器和一個迴歸器。
javascript

本文須要你最好對 RNN 相關的知識有一個初步的認識,而後我會盡量的讓你明白在 pytorch 中是如何去實現這一點的。java

1. pytorch 提供了哪些 RNN?

若是咱們對 RNN 有所瞭解,就會知道 RNN 有不少變種,就像 CNN 也有不少變種同樣。只要帶了 recurrent 的功能,就都屬於 RNN 的範疇。那麼在 pytorch 中提供了哪些 RNN 呢?git

上面這幅圖是 pytorch 源碼中的結構,能夠看到除了一個 RNNBase() 類,下面還有 RNN,LSTM,GRU 分別繼承了 RNNBase() 類,實現了三個 RNN 子類。這三個也就是 pytorch 提供的 RNN 類型。github

今天的文章,咱們分別經過源碼中的 doc 和一些介紹來了解 RNN 和 LSTM,而後分別用它們實現一個迴歸器和一個分類。關於 GRU 的部分,就留給你們本身去展開啦。web

2. RNN,以及實現一個迴歸器

這一部分,咱們先從 RNN 開始進行介紹,分別簡單介紹一下 RNN 的原理,在 pytorch 中使用它的一些參數要求,最後是一個迴歸器,代碼示例參考了 @莫煩 的視頻教程,用 sim 曲線做爲輸入,cos 曲線做爲 label,判斷函數的擬合能力。在文章末尾有莫煩視頻教程的介紹和地址。ruby

2.1 簡單介紹 RNN

首先看一下 RNN 的內容,常見的介紹 RNN 的文章中都會有這樣一幅圖:微信

X0,X1 等等分別是輸入序列的一個維度上的數據,X0 首先傳進去,生成 h0 做爲第一個隱狀態。而後 h0 和 X1 一塊兒做爲下一個時間序列上的輸入,它們的輸出再和 X2 做爲下下個時間序列的輸入,以此類推。網絡

具體的細節咱們就不展開講了,默認你們對理論層面已經有了瞭解。在 pytorch 的源碼 doc 中也給出了,對下面的公式進行計算:app


這個式子也是對於 RNN 的常見描述。W_ih 表示對輸入數據進行處理的權重,而 W_hh 則表示對上一個時間序列的隱狀態進行處理的權重。b_ih 和 b_hh 則分別是兩個偏置項,有的公式裏面不會給出這兩項,也就是默認偏置爲 0。機器學習

2.2 RNN 類實現的參數要求

接下來咱們看一下 pytorch 中的 RNN 類有什麼參數吧。咱們主要是對這幾個參數進行介紹:

input_size:這個參數表示的輸入數據的維度。好比輸入一個句子,這裏表示的就是每一個單詞的詞向量的維度。

hidden_size :能夠理解爲在 CNN 中,一個卷積層的輸出維度同樣。這裏表示將前面的 input_size 映射到一個什麼維度上。

num_layers:表示循環的層數。舉個栗子,將 num_layers 設置爲  2,也就是將如前面圖所示的兩個 RNN 堆疊在一塊兒,第一層的輸出做爲第二層的輸入。默認爲 1。

nonlinearity:這個參數對激活函數進行選擇,目前 pytorch 支持 tanh 和 relu,默認的激活函數是 tanh。

bias:這個參數就是對前面公式中的 b_ih 和 b_hh。來選擇是否須要偏置項,默認爲 True。

batch_first:這個是咱們數據的格式描述,在 pytorch 中咱們常常以 batch 分組來訓練數據。這裏的 batch_size 表示 batch 是否在輸入數據的第一個維度,若是在第一個維度則爲 True,默認爲 False,也就是第二個維度。

dropout:這裏就是對每一層的輸出是否加一個 dropout 層,若是參數非 0,那麼就會加上這個 dropout 層。值得注意的是,對最後的輸出層並不會加,也就是這個參數只有在 num_layers 參數大於 1 的時候纔有意義。默認爲 0。

bidirectional:若是爲 True,則表示 RNN 網絡爲雙向結構,默認爲 False。

介紹完具體的參數,咱們就能夠很簡單的直接構建一個 RNN 的網絡了,這裏必須的兩個參數是 input_size,hidden_size,其他的參數都有默認值,並且也符合通常常見的須要。

2.3 用 sin 曲線來預測 cos 曲線

接下來咱們利用上面的知識來實現一個例子,對應的輸入是如圖的 sin 函數,label 則是 cos 函數。咱們用輸入來擬合這個輸出。

咱們先看一下這個數據應該如何展現:

steps = np.linspace(0, np.pi*2, 100, dtype=np.float32)x_np = np.sin(steps)y_np = np.cos(steps)

經過 numpy 咱們能夠構建出來想要的 sin 和 cos 曲線,這裏的 x_np 做爲數據,y_np 做爲 label。就能夠進行咱們的網絡構建了。

根據前面學習的各個參數的做用,咱們就能夠構建以下的網絡結構:

class RNN(nn.Module): def __init__(self): super(RNN, self).__init__() self.rnn = nn.RNN( input_size=1, hidden_size=32, num_layers=1, batch_first=True, )        self.out = nn.Linear(321)
def forward(self, x, h_state): r_out, h_state = self.rnn(x, h_state) outs = [] for time_step in range(r_out.size(1)): outs.append(self.out(r_out[:, time_step, :])) return torch.stack(outs, dim=1), h_state

咱們介紹一下這裏的幾個關鍵點,首先參數上比較簡單,輸入就是一個橫座標,因此 input_size 是 1;將其 embedding 到一個 32 維的空間上,因此 hidden_size 選擇了 32,這個值固然能夠選擇其它的;一層的網絡,因此 num_layers 設置爲 1,其實默認值也是 1,這一步能夠省略;最後關注一下 batch_first 爲 True,是由於咱們選擇將輸入的維度設置爲(batch,time_step,input_size)。

值得注意的是 forward 函數中,咱們構建了一個 outs,而後一直往進 append 數據,具體的緣由是什麼呢?

當 RNN 在處理數據的時候,每次的輸入須要傳進去一個 sequence,其中每一個 entry 能夠看作一個詞,如前面理論部分的介紹,每一個詞向量的長度就是 input_size。可是每一個 sequence 的長度呢?其實對應了 time_step,也就是每次輸入多少個「詞向量」。

因此在 forward 的函數中,第二個維度 time_step 就是序列的長度,在這個例子中就是每次給網絡給多少個點的數據。這個 for 循環就是從 r_out 的第二個維度中選擇,將每一個 time_step 上的數據的映射結果拿出來,而後經過 out() 函數,也就是 full connected layer 進行處理。最後 append 的就是這個處理結果。

最後咱們把訓練過程展示出來看一看:

藍色線條展現了模型的擬合過程,能夠看到最終逐漸擬合到了目標的 cos 曲線上(紅色線條)。

3. LSTM,以及實現一個分類器

前面咱們介紹了原始的 RNN,衆所周知的一些 RNN 的缺陷,咱們就不贅述了(梯度爆炸,梯度消失等等)。那麼 LSTM 成爲了一個在序列化數據中很是受歡迎的選項。這一部分,咱們先介紹一下簡單的 LSTM 的理論基礎,而後主要是 pytorch 中對於 LSTM 提供的接口,最後經過一個分類器的例子,來完成一個分類器。

3.1 簡單介紹 LSTM

和前面 RNN 同樣,咱們先甩出來一張你們確定見過的圖:

從圖中能夠看到,不一樣於 RNN,LSTM 提出了三個門(gate)的概念:input gate,forget gate,output gate。其實能夠這樣來理解,input gate 決定了對輸入的數據作哪些處理,forget gate 決定了哪些知識被過濾掉,無需再繼續傳遞,而 output gate 決定了哪些知識須要傳遞到下一個時間序列。

pytorch 中 LSTM 源碼的 doc 中給出瞭如下公式:

 咱們本篇文章不是專門介紹 LSTM 的文章,因此這裏就不展開細講。可是能夠簡略的幫你們回顧一下幾個式子的總體流程。

i_t 是處理 input 的 input gate,外面一個 sigmoid 函數處理,其中的輸入是當前輸入 x_t 和前一個時間狀態的輸出 h_(t-1)。全部的 b 都是偏置項。

f_t 則是 forget gate 的操做,一樣對當前輸入 x_t 和前一個狀態的輸出 h_(t-1) 進行處理。

g_t 也是 input gate 中的操做,一樣的輸入,只是外面換成了 tanh 函數。

o_t 是前面圖中 output gate 中左下角的操做,操做方式和前面 i_t,以及 f_t 同樣。

c_t 則是輸出之一,對 forget gate 的輸出,input gate 的輸出進行相加,而後做爲當前時間序列的一個隱狀態向下傳遞。

h_t 一樣是輸出之一,對 前面的 c_t 作一個 tanh 操做,而後和前面獲得的 o_t 進行相乘,h_t 既向下一個狀態傳遞,也做爲當前狀態的輸出。

3.2 LSTM 在 pytorch 中的參數要求

簡單介紹完上面的數學內容,咱們只須要知道 LSTM 內部進行了這些運算,具體的運算細節已經無需去自行設計,pytorch 已經給咱們封裝好了。

那麼咱們須要給它傳入哪些輸入,設置哪些參數呢?

class torch.nn.LSTM(*args, **kwargs): input_sizex的特徵維度 hidden_size:隱藏層的特徵維度 num_layerslstm隱層的層數,默認爲1 biasFalsebihbih=0和bhhbhh=0. 默認爲True batch_first:True則輸入輸出的數據格式爲 (batch, seq, feature) dropout:除最後一層,每一層的輸出都進行dropout,默認爲: 0 bidirectional:True則爲雙向lstm默認爲False

看得出來,和前面的 RNN 參數很是類似,只少了一個 nonlinearty 參數。這是由於 LSTM 無需設置具體的非線性函數,從上面的公式中能夠看到,具體每一步的操做,都已經有了清晰的定義。

因此這裏的參數的意義就簡略註釋了,詳細的意義和 RNN 同樣。

3.3 在 mnist 數據集上實現一個分類器

雖說起 LSTM 你們確定以爲是在 nlp 的數據集上比較常見,不過在圖片分類中,它一樣也可使用。由於只是實現一個例子,因此咱們就選擇前面屢次使用,比較熟悉的 mnist 進行驗證吧。

咱們知道 mnist 數據集是 28*28 的手寫數字,並且由於是黑白照片,因此不像彩色圖片同樣是三通道,只有一個通道。

這裏對於數據的理解,咱們進行一下簡單的介紹:對於每一張圖片,咱們看做一條數據,就像 nlp 中的一個句子同樣。將照片的每一行看作一個向量,對應一個句子中的詞向量,因此很顯然,圖片的行數就句子的長度。因此對這個 28*28 的照片,就是一個由 28 個向量組成的序列,且每一個向量的長度都是 28。在 nlp 領域中,就是一個有 28 個單詞的句子,且每一個單詞的詞向量長度都爲 28.

因此咱們能夠直接構建 LSTM 的網絡結構以下:

class RNN(nn.Module): def __init__(self): super(RNN, self).__init__()
self.rnn = nn.LSTM( input_size=28, hidden_size=64, num_layers=1, batch_first=True, ) self.out = nn.Linear(64, 10)
def forward(self, x): r_out, (h_n, h_c) = self.rnn(x, None) out = self.out(r_out[:, -1, :]) return out

網絡的結構方面須要介紹的很少,將一個 28 維的向量映射到 64 維的空間上,網絡層數爲 1,batch 在數據的第一個維度上。

對 forward 這裏簡單介紹一下,LSTM 的輸出是由兩部分組成,第一部分是 r_out,第二部分是 (h_n,h_c)。

對於 r_out 而言,就是最後一個狀態的隱藏層的神經元的輸出,換句話說,就是上面圖片中各個 h_t 連起來的樣子,若是是一個翻譯的 LSTM,那就是翻譯後的句子。

對於第二部分的兩個參數而言,分別是最後一個狀態的隱含層的隱狀態,也就是上面圖片中的兩條向右一直傳遞的線,在最後一個狀態的結果。

因此咱們須要輸出的是 out = r_out[:, -1, :],若是不能理解是什麼的話,咱們先給出來 r_out 的 shape:(batch,seq_len,input_size)。如今是否是就明白了,這個 out 就是最後一個向量傳進去 LSTM 後,獲得的輸出結果。也就是咱們把圖片的全部行都穿進去之後,LSTM 給出的分類結果。

接下來是對結果進行驗證的部分,咱們就不貼出來這部分代碼了,意義不是很明顯,感興趣的朋友能夠看文章最後,會給出本文的所有代碼連接。

下面是咱們對訓練結果進行的測試,測試的效果天然不如 CNN 優秀,可是也到了 90% 以上了。

4. 總結

今天的文章主要是介紹了 RNN 和 LSTM 在 pytorch 中的實現,前面也提到 pytorch 還提供了 GRU 的接口,感興趣的朋友能夠自行看看 doc,實現一個分類器或者回歸器來進行接口驗證。

這篇文章中,你們若是仔細閱讀,代碼認真的敲一遍,基本上對接口的各個部分也就熟悉了,因此拿起鍵盤嘗試一下吧~

PS:給出幾個連接供你們參考,一個是知乎上面一篇對於 LSTM 的解讀,一個是莫煩小哥的視頻教程,本文的代碼示例也都是參考莫煩老師的 pytorch 教程,結合本身的理解進行整理的。

【1】https://zhuanlan.zhihu.com/p/139617364

【2】https://www.bilibili.com/video/BV1Vx411j7kT?p=23

PS2:本文的 RNN 和 LSTM 兩部分的代碼完整連接以下:

RNN:https://github.com/TrWestdoor/pytorch-practice/blob/master/rnn_regression.py

LSTM:<https://github.com/TrWestdoor/pytorch-practice/blob/master/rnn_classify.py>


本文分享自微信公衆號 - 機器學習與推薦系統(ml-recsys)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索