一、前言
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個神經元。需要注意的是,每個具有記憶功能的神經元中所存儲的隱狀態h,不僅參與本神經元的下次輸入,還同時參與本層其他具有記憶功能神經元的下次輸入。
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