從網路架構方面簡析迴圈神經網路RNN

秋沐霖發表於2019-05-17

 

一、前言

1.1 誕生原因

  在普通的前饋神經網路(如多層感知機MLP,卷積神經網路CNN)中,每次的輸入都是獨立的,即網路的輸出依賴且僅依賴於當前輸入,與過去一段時間內網路的輸出無關。但是在現實生活中,許多系統的輸出不僅依賴於當前輸入,還與過去一段時間內系統的輸出有關,即需要網路保留一定的記憶功能,這就給前饋神經網路提出了巨大的挑戰。除此之外,前饋神經網路難以處理時序資料,比如視訊、語音等,因為時序資料的序列長度一般是不固定的,而前饋神經網路要求輸入、輸出的維度都是固定的,不能任意改變。出於這兩方面的需求,迴圈神經網路RNN應運而生。

1.2 簡介

  迴圈神經網路(Recurrent Neural Network,RNN)是一類具有短期記憶能力的神經網路。在迴圈神經網路中,神經元既可以如同前饋神經網路中神經元那般從其他神經元那裡接受資訊,也可以接收自身以前的資訊。且和前饋神經網路相比,迴圈神經網路更加符合生物神經網路的結構。

1.3 與前饋神經網路的差異

  就功能層面和學習性質而言,迴圈神經網路也有異於前饋神經網路。前饋神經網路多用於迴歸(Regression)和分類(Classification),屬於監督學習(Supervised learning);而迴圈神經網路多用於迴歸(Regression)和生成(Generation),屬於無監督學習(Unsupervised learning)。

二、網路架構

  上圖所示為RNN的一個神經元模型。給定一個序列長度為T的輸入序列,在t時刻將該序列中的第t個元素xt送進網路,網路會結合本次輸入xt及上一次的 “ 記憶 ” ht-1,通過一個非線性啟用函式f()(該函式通常為tanh或relu),產生一箇中間值ht(學名為隱狀態,Hidden States),該值即為本次要保留的記憶。本次網路的輸出為ht的線形變換,即g(ht),其中g()為簡單的線性函式。

  由於迴圈神經網路具有時序性,因此其網路架構可以從空間和時間兩方面進行了解。

  為方便理解,特以此情景舉例:假設目前正在訓練一個RNN,用於生成視訊。訓練用的train_data為1000段等長度的視訊,劃分的batch_size為10,即每次送給網路10段視訊。假設每段視訊都有50幀,每幀的解析度為2X2。在此訓練過程中,不需要外界給label,將某幀圖片input進RNN時,下一幀圖片即為label。

2.1 空間角度

  在本例中,一個序列長度為T的輸入序列即一段50幀視訊,其中的x1,x2,x3...x50分別對應著第一幀圖片、第二幀圖片、第三幀圖片......到第五十幀圖片。隨著時間的推移,依次將每幀圖片送進網路,在每個時刻,只送進一幀圖片,同時生成一幀圖片。該網路架構如下圖所示。就如普通的前饋神經網路一般,除了輸入輸出層的維度固定外,隱藏層的維度及層數都是可以自主設計的。這裡假設只有一個隱藏層,該層有5個神經元。

2.2 時間角度

  從空間角度觀察整個網路,可將網路視為1個4X5X4的迴圈神經網路RNN,其中隱藏層的5個神經元是具有記憶功能的;從時間角度展開整個網路,可將網路視為50個4X5X4的多層感知機MLP,每個MLP隱層神經元不僅向該MLP的下一層輸出,同時還向下一個MLP中與之位置對應的神經元輸出,該輸出即為隱狀態h。由於隱狀態需要在MLP之間從前向後傳遞,因此這50個MLP只能依次運算,不能並行運算。

 

 

  迴圈神經網路獨特的神經元結構賦予其記憶功能,但有得有失,該結構也造成其在處理時序資料時不能進行並行運算。目前推動演算法進步的一大助力就是算力,而RNN卻無法充分利用算力,這也是一部分人不太看好其前景的原因。

三、pytorch demo 實現

  對一段正弦函式進行離散取樣作為輸入,利用RNN生成滯後的正弦函式取樣值。實現環境:Colab。

  引入必要標頭檔案。

1 import torch.nn as nn
2 import torch
3 import numpy as np
4 import matplotlib.pyplot as plt
5 %matplotlib inline

   取樣獲取input及label,並視覺化。

 1 # 制定畫布大小
 2 plt.figure(figsize=(8, 5))
 3 
 4 # 每個batch中資料個數
 5 num = 20
 6 
 7 # 生成資料
 8 time_steps = np.linspace(0, np.pi, num+1)
 9 data = np.sin(time_steps)
10 data = data.reshape((num+1, 1))
11 
12 x = data[0:num, :] # 除了最後一個資料外的所有其他資料
13 y = data[1:num+1, :] # 除了第一個資料外的所有其他資料
14 
15 # 視覺化資料
16 plt.plot(time_steps[1:num+1], x, 'r.', label='input_x')
17 plt.plot(time_steps[1:num+1], y, 'b.', label='output_y')
18 
19 plt.legend(loc='best')
20 plt.show()

 

  通過torch.nn.RNN及torch.nn.fc自定義網路模型。

 1 # 自定義網路
 2 class myRNN(nn.Module):
 3     def __init__(self, input_size, output_size, hidden_dim, n_layers):
 4         super(myRNN, self).__init__()
 5         self.hidden_dim = hidden_dim # 隱藏層節點個數
 6         self.rnn = nn.RNN(input_size, hidden_dim, n_layers, batch_first=True) # rnn層
 7         self.fc = nn.Linear(hidden_dim, output_size) # 全連線層
 8         
 9     def forward(self, x, hidden):
10         batch_size = x.shape[0]
11         # 生成預測值和隱狀態,預測值傳向下一層,隱狀態作為記憶參與下一次輸入
12         r_out, hidden = self.rnn(x, hidden)       
13         r_out = r_out.view(-1, self.hidden_dim)       
14         output = self.fc(r_out)
15         
16         return output, hidden

   例項化模型,並指定超引數、損失函式和優化器。

 1 # 指定超引數
 2 input_size = 1
 3 output_size = 1
 4 hidden_dim = 32
 5 n_layers = 1
 6 
 7 # 例項化模型
 8 rnn = myRNN(input_size, output_size, hidden_dim, n_layers)
 9 
10 # 指定損失函式和優化器,學習率設定為0.01
11 loss = nn.MSELoss()
12 optimizer = torch.optim.Adam(rnn.parameters(), lr=0.01)

   定義訓練並列印輸出的函式。 

 1 def train(rnn, n_steps, print_every):
 2     
 3     # 記憶初始化
 4     hidden = None
 5     loss_list = []
 6     for batch_i,step in enumerate(range(n_steps)):
 7         optimizer.zero_grad() # 梯度清零
 8         # 生成訓練資料
 9         time_steps = np.linspace(step*np.pi, (step+1)*np.pi, num+1)
10         data = np.sin(time_steps)
11         data = data.reshape((num+1, 1))
12 
13         x = data[0:num, :] # 除了最後一個資料外的所有其他資料
14         y = data[1:num+1, :] # 除了第一個資料外的所有其他資料
15         
16         x_tensor = torch.from_numpy(x).unsqueeze(0).type('torch.FloatTensor')
17         y_tensor = torch.from_numpy(y).type('torch.FloatTensor')
18         
19         prediction, hidden = rnn(x_tensor, hidden) # 生成預測值和隱狀態       
20         hidden = hidden.data        
21         loss_rate = loss(prediction, y_tensor) # 計算損失
22         loss_rate.backward() # 誤差反向傳播        
23         optimizer.step() # 梯度更新
24         loss_list.append(loss_rate)
25 
26         
27         if batch_i%print_every == 0:
28             plt.plot(time_steps[1:num+1], x, 'r.', label='input')
29             plt.plot(time_steps[1:num+1], prediction.data.numpy().flatten(), 'b.', label='predicte')
30             plt.show()  
31     
32     x = np.linspace(0, n_steps, n_steps)
33     plt.plot(x, loss_list, color='blue', linewidth=1.0, linestyle='-', label='loss')
34     plt.legend(loc='upper right')
35     plt.show()
36         
37     return rnn

   訓練模型並列印輸出。

1 n_steps = 100
2 print_every = 25
3 trained_rnn = train(rnn, n_steps, print_every)

  從左到右、自上而下,分別是訓練25、50、75和100次後的模型效果。

 

參考資料:

  邱錫鵬老師《神經網路與深度學習》 https://nndl.github.io/

  優達學城pytorch學習課程 https://github.com/udacity/deep-learning-v2-pytorch

  慕課手記——RNN架構詳解  http://www.imooc.com/article/details/id/31105

  pytorch官方手冊 https://pytorch.org/docs/stable/nn.html#recurrent-layers

  

 注:有誤之處,敬請交流、雅正!

 

<<-----------------------------------------------    如有幫助,歡迎點贊、留言、關注,麼麼噠~     -------------------------------------------------->>

相關文章