深度學習之循環神經網絡(4)RNN層使用方法 1. SimpleRNNCell 2. 多層SimpleRNNCell網絡 3. SimpleRNN層
?在介紹完循環神經網絡的算法原理之后,我們來學習如何在TensorFlow中實現RNN層。在TensorFlow中,可以通過layers.SimpleRNNCell來完成
σ(Wxhxt+Whhht?1+b)σ(\boldsymbol W_{xh} \boldsymbol x_t+\boldsymbol W_{hh} \boldsymbol h_{t-1}+\boldsymbol b) σ ( W x h ? x t ? + W h h ? h t ? 1 ? + b ) 的計算。需要注意的是,在TensorFlow中,RNN表示通用意義上的循環神經網絡,對于我們目前介紹的基礎循環神經網絡,它一般叫做
SimpleRNN 。SimpleRNN與SimpleRNNCell的區別在于,帶Cell的層僅僅是完成了一個時間戳的前向運算,不帶Cell的層一般是基于Cell層實現的,它在內部已經完成了多個時間戳的循環運算,因此使用起來更為方便快捷。
?我們先來介紹SimpleRNNCell的使用方法,再介紹SimpleRNN層的使用方法。
1. SimpleRNNCell
?以某輸入特征長度n=4n=4 n = 4 (即輸入的詞由幾個位組成,例如[x1,x2,x3,x4][x_1,x_2,x_3,x_4] [ x 1 ? , x 2 ? , x 3 ? , x 4 ? ] 代表一個詞,那么這個詞輸入的特征長度n=4n=4 n = 4 ),Cell狀態向量特征長度為h=3h=3 h = 3 (即每個時間戳tt t 上狀態張量h\boldsymbol h h 的特征長度,其中ht=σ(Wxhxt+Whhht?1+b)\boldsymbol h_t=σ(\boldsymbol W_{xh} \boldsymbol x_t+\boldsymbol W_{hh} \boldsymbol h_{t-1}+\boldsymbol b) h t ? = σ ( W x h ? x t ? + W h h ? h t ? 1 ? + b ) )為例,首先我們新建一個SimpleRNNCell,不需要指定序列長度ss s ,代碼如下:
from tensorflow.keras import layerscell = layers.SimpleRNNCell(3) # 創建RNN Cell,內存向量長度為3
cell.build(input_shape=(None, 4)) # 輸出特征長度n=4
print(cell.trainable_variables) # 打印Wxh,Whh,b張量
運行結果如下圖所示:
可以看到,SimpleRNNCell內部維護了3個張量,kernel變量即Wxh\boldsymbol W_{xh} W x h ? 張量,recurrent_kernel變量即Whh\boldsymbol W_{hh} W h h ? 張量,bias變量即偏置b\boldsymbol b b 向量。但是RNN的Memory向量h\boldsymbol h h 并不由SimpleRNNCell維護,需要用戶自行初始化向量h0\boldsymbol h_0 h 0 ? 并記錄每個時間戳上的ht\boldsymbol h_t h t ? 。 ?通過調用Cell實例即可完成前向運算: ot,[ht]=Cell(xt,[ht?1])\boldsymbol o_t,[\boldsymbol h_t]=\text{Cell}(\boldsymbol x_t,[\boldsymbol h_{t-1}]) o t ? , [ h t ? ] = Cell ( x t ? , [ h t ? 1 ? ] ) 對于SimpleRNNCell來說,ot=ht\boldsymbol o_t=\boldsymbol h_t o t ? = h t ? ,并沒有經過額外的線性層轉換,是同一個對象; [ht][\boldsymbol h_t ] [ h t ? ] 通過一個List包裹起來,這么設置是為了與LSTM、GRU等RNN變種格式統一。在循環神經網絡的初始化階段,狀態向量h0\boldsymbol h_0 h 0 ? 一般初始化為全0向量,例如:
import tensorflow as tf
from tensorflow.keras import layers# 初始化狀態向量,用列表包裹,統一格式
h0 = [tf.zeros([4, 64])]
x = tf.random.normal([4, 80, 100]) # 生成輸入張量,4個80單詞的句子
xt = x[:, 0, :] # 所有句子的第1個單詞
# 構建輸入特征n=100,序列長度s=80,狀態長度h=64的Cell
cell = layers.SimpleRNNCell(64)
out, h1 = cell(xt, h0) # 前向計算
print(out.shape, h1[0].shape)
運行結果如下圖所示:
可以看到經過一個時間戳的計算后,輸出張量的shape都為[b,h][b,h] [ b , h ] ,打印出這兩者的id如下:
print(id(out), id(h1[0]))
運行結果如下圖所示:
兩者id一致,即狀態向量直接作為輸出向量。對于長度為ss s 的訓練來說,需要循環通過Cell類ss s 次才算完成一次網絡層的前向運算。例如:
h = h0 # h保存每個時間戳上的狀態向量表
# 在序列長度的維度解開輸入,得到xt:[b,n]
for xt in tf.unstack(x, axis=1):out, h = cell(xt, h) # 前向計算,out和h均被覆蓋
# 最終輸出可以聚合每個時間戳上的輸出,也可以只取最后時間戳的輸出
out = out
print(out)
注: stack與unstack操作詳見深度學習(12)TensorFlow高階操作一: 合并與分割 運行結果如下所示:
tf.Tensor(
[[-0.2677562 0.48381227 0.6812192 -0.9811414 -0.95568067 0.040094060.9091274 -0.99737924 -0.2262151 0.63944376 -0.9013501 0.99265766-0.09092428 -0.73986536 0.93987006 0.23288447 0.94647026 -0.93396217-0.98536897 0.813241 0.2766947 -0.25673908 -0.8504294 -0.459959980.7178784 -0.01069952 -0.35384497 -0.7301667 -0.42860696 -0.91951180.96424794 0.93540084 -0.8629409 0.54582363 -0.8481167 -0.88403730.998077 0.7212096 -0.31695995 -0.33156037 0.9733765 0.470587730.9242944 -0.9082541 -0.52866167 0.9714778 0.00706163 -0.22000399-0.22981001 0.35692227 0.9605445 0.73061293 0.7366635 0.16476776-0.19073665 0.8935988 0.88425654 0.6517266 0.43205526 -0.50979210.35872322 -0.42525575 0.4747447 -0.82216126][ 0.7601129 -0.84973663 0.07257108 0.4074115 0.85890645 0.40316352-0.49802104 -0.46189487 -0.97344846 0.33110482 0.22007078 -0.6415040.9584318 0.48941532 0.9487777 -0.4180974 0.403612 -0.888372960.08162808 0.7211986 0.41622642 -0.7644256 -0.9502167 0.7591871-0.76903707 0.54298973 -0.746649 0.8129116 0.4728199 -0.94986810.3234566 -0.0890093 0.24190407 -0.9862916 -0.8878334 0.367466330.7691656 -0.42555293 -0.9808859 0.38541916 -0.9439697 0.337625320.08059914 -0.85767996 0.4056216 0.20410602 0.14420648 -0.13230170.30473053 -0.49009833 -0.93254864 -0.24999127 0.37115836 -0.99332243-0.36034808 0.20640826 -0.7829328 -0.9780473 0.9820612 0.855272-0.38713187 0.4631712 -0.85671836 0.93773407][-0.8166884 0.9315958 0.9914151 -0.02406353 0.969364 0.94645980.00994941 0.8504445 0.94859165 -0.16112864 -0.6662656 -0.346213160.09543993 0.99852633 0.82953227 0.13884324 0.31297988 -0.87489945-0.2261714 -0.538083 -0.89584523 0.6099533 0.37234947 0.48815438-0.99152416 -0.4111157 0.54102963 0.04263455 0.88183767 0.7196480.9528599 0.83965695 0.9976097 0.18376695 -0.7623534 -0.964800830.6696029 -0.98376185 -0.5559587 0.00449213 -0.6537535 0.81219536-0.12517768 -0.2835646 0.59366983 -0.05620956 -0.93757176 -0.93908745-0.8291558 0.49092057 -0.5600199 0.8819002 0.9935199 -0.034000450.8780934 -0.95364624 0.8695547 -0.7339648 0.8402839 -0.81569680.5867556 0.91071755 -0.90019256 0.35353023][ 0.22378804 -0.8577115 -0.9898991 -0.13579184 -0.62707126 0.75127320.97475743 -0.32653475 0.10074215 0.37040377 0.9023177 0.9916534-0.07671987 0.9395568 -0.9829578 0.8298786 -0.89936143 0.241889730.66845345 -0.98227394 0.9421198 0.06484365 -0.6897533 -0.8032981-0.9555406 0.89325416 -0.998938 0.6184817 0.7335308 0.80259580.47380012 -0.57215804 0.78067404 0.9241867 -0.35744458 -0.9898047-0.9867912 0.8348301 0.63395554 -0.9371648 0.64949805 0.999461230.9775342 -0.7392784 0.9140785 -0.27143526 -0.99955875 0.94143620.2668406 0.35915926 -0.82338697 0.9371646 -0.8107094 -0.97396994-0.9549606 -0.2685872 0.61806554 -0.94509935 -0.98313683 0.95650950.23148598 0.81570596 -0.5302515 0.63490754]], shape=(4, 64), dtype=float32)
最后一個時間戳的輸出變量out將作為網絡的最終輸出。實際上,也可以將每個時間戳上的輸出保存,然后求和或者均值,將其作為網絡的最終輸出。
2. 多層SimpleRNNCell網絡
?和卷積神經網絡一樣,循環神經網絡雖然在時間軸上面展開了多次,但只能算一個網絡層。通過在深度方向堆疊多個Cell類來實現深層卷積神經網絡一樣的效果,大大的提升網絡的表達能力。但是和卷積神經網絡動輒幾十、上百的深度層數來比,循環神經網絡很容易出現梯度彌散和梯度爆炸的現象,深層的循環神經網絡訓練起來非常困難,目前常見的循環神經網絡層數一般控制在十層以內。 ?我們這里以兩層的循環神經網絡為例,介紹利用Cell方式構建多層RNN網絡。首先新建兩個SimpleRNNCell單元,代碼如下:
import tensorflow as tf
from tensorflow.keras import layersx = tf.random.normal([4, 80, 100])
xt = x[:, 0, :] # 取第一個時間戳的輸入x0
# 構建2個Cell,先cell0,后cell1,內存狀態張量長度都為64
cell0 = layers.SimpleRNNCell(64)
cell1 = layers.SimpleRNNCell(64)
h0 = [tf.zeros([4, 64])] # cell0的初始狀態向量
h1 = [tf.zeros([4, 64])] # cell1的初始狀態向量
在時間軸上面循環計算多次來實現整個網絡的前向計算,每個時間戳上的輸入xt首先通過第一層,得到輸出out0,再通過第二層,得到輸出out1,代碼如下:
for xt in tf.unstack(x, axis=1):# xt作為輸入,輸出為0out0, h0 = cell0(xt, h0)# 上一個cell的輸出out0作為本cell的輸入out1, h1 = cell1(out0, h1)print(out0.shape, out1.shape)
運行結果如下所示:
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
(4, 64) (4, 64)
共80行,即共80個時間戳上的out0和out1。 上述方式先完成一個時間戳上的輸入在所有層上的傳播,再循環計算所有時間戳上的輸入。 ?實際上,也可以先完成輸入在第一層上所有時間戳的計算,并保存第一層在所有時間戳上的輸出列表,再計算第二層、第三層等的傳播。代碼如下:
# 保存上一層的所有時間戳上面的輸出
middle_sequences = []
# 計算第一層的所有時間戳上的輸出,并保存
for xt in tf.unstack(x, axis=1):out0, h0 = cell0(xt, h0)middle_sequences.append(out0)print(out0.shape)
# 計算第二層的所有時間戳上的輸出
# 如果不是末層,需要保存所有時間戳上面的輸出
for xt in middle_sequences:out1, h1 = cell1(out0, h1)print(out1.shape)
使用這種方式的話,我們需要一個額外的List來保存上一層所有時間戳上面的狀態信息: middle_sequences.append(out0)。這兩種方式效果相同,可以根據個人喜好選擇編程風格。 ?需要注意的是,循環神經網絡的每一層、每一個時間戳上面均由狀態輸出,那么對于后續任務來說,我們應該收集哪些狀態輸出最有效呢?一般來說,最末層Cell的狀態有可能保存了高層的全局語義特征,因此一般使用最末層的輸出作為后續任務網絡的輸入。更特別地,每層最后一個時間戳上的狀態輸出包含了整個序列的全局信息,如果只希望選用一個狀態變量來完成后續任務,比如情感分類問題,一般選用最末層、最末時間戳的狀態輸出最為合適。
3. SimpleRNN層
?通過SimpleRNNCell層的使用,我們可以非常深入地理解循環神經網絡前向運算的每個細節,但是在實際使用中,為了簡便,不希望手動參與循環神經網絡內部的計算過程,比如每一層的h狀態向量的初始化,以及每一層在時間軸上展開的運算。通過SimpleRNN層蓋層接口可以非常方便地幫助我們實現此目的。 ?比如我們要完成單層循環神經網絡的前向計算,可以方便地實現如下:
import tensorflow as tf
from tensorflow.keras import layerslayer = layers.SimpleRNN(64) # 創建狀態張量長度為64的SimpleRNN層
x = tf.random.normal([4, 80, 100])
out = layer(x) # 和普通卷積網絡一樣,一行代碼即可獲得輸出
print(out.shape)
運行結果如下圖所示:
可以看到,通過SimpleRNN可以僅需一行代碼即可完成整個前向運算過程,它默認返回最后一個時間戳上的輸出。 ?如果希望返回所有時間戳上的輸出列表,可以設置return_sequences=True參數,代碼如下:
import tensorflow as tf
from tensorflow.keras import layers# 創建RNN時,設置返回所有時間戳上的輸出
layer = layers.SimpleRNN(64, return_sequences=True) # 創建狀態張量長度為64的SimpleRNN層
x = tf.random.normal([4, 80, 100])
out = layer(x) # 前向計算
print(out.shape) # 輸出,自動進行了concat操作
運行結果如下圖所示: 可以看到,返回的輸出張量shape為[4,80,64][4,80,64] [ 4 , 8 0 , 6 4 ] ,中間維度的80即為時間戳維度。同樣的,對于多層循環神經網絡,我們可以通過堆疊多個SimpleRNN實現,如兩層的網絡,用法和普通的網絡類似。例如:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequentialnet = keras.Sequential([ # 構建2層RNN網絡# 除最末層外,都需要返回所有時間戳的輸出,用作下一層的輸入layers.SimpleRNN(64, return_sequences=True),layers.SimpleRNN(64),
])
x = tf.random.normal([4, 80, 100])
out = net(x) # 前向計算
print(out.shape) # 輸出,自動進行了concat操作
運行結果如下圖所示:
每層都需要上一層的每個時間戳上面的狀態輸出,因此除了最末層以外,所有的RNN層都需要返回每個時間戳上面的狀態輸出,通過設置return_sequences=True來實現。可以看到,使用SimpleRNN層,與卷積神經網絡的用法類似,非常簡潔和高效。
創作挑戰賽 新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔 為你收集整理的深度学习之循环神经网络(4)RNN层使用方法 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。