Tensorflow梯度相關,LSTM,GRU

我們把梯度值接近於 0 的現象叫做梯度彌散(Gradient Vanishing),把梯度值遠大於 1 的 現象叫做梯度爆炸(Gradient Exploding)。梯度彌散和梯度爆炸是神經網絡優化過程中間比 較容易出現的兩種情況,也是不利於網絡訓練的。

𝜃′ =𝜃−𝜂∇𝜃L

當出現梯度彌散時,∇𝜃L ≈ 0,此時𝜃′ ≈ 𝜃,也就是說每次梯度更新後參數基本保持不變, 神經網絡的參數長時間得不到更新,具體表現爲L幾乎保持不變,其它評測指標,如準確 度,也保持不變。當出現梯度爆炸時,∇𝜃L ≫ 1,此時梯度的更新步長η∇𝜃L非常大,使得 更新後的𝜃′與𝜃差距很大,網絡L出現突變現象,甚至可能出現來回震盪、不收斂的現象。

通過推導循環神經網絡的梯度傳播公式,我們發現循環神經網絡很容易出現梯度彌散 和梯度爆炸的現象。那麼怎麼解決這兩個問題呢?

梯度爆炸可以通過梯度裁剪(Gradient Clipping)的方式在一定程度上的解決。梯度裁剪 與張量限幅非常類似,也是通過將梯度張量的數值或者範數限制在某個較小的區間內,從 而將遠大於 1 的梯度值減少,避免出現梯度爆炸。

在深度學習中,有 3 種常用的梯度裁剪方式。
❑ 直接對張量的數值進行限幅,使得張量𝑾的所有元素𝑤𝑖𝑗 ∈ [min, max]。在 TensorFlow中,可以通過 tf.clip_by_value()函數來實現。

a=tf.random.uniform([2,2])
tf.clip_by_value(a,0.4,0.6) # 梯度值裁剪
<tf.Tensor: id=110, shape=(2, 2), dtype=float32, numpy=
array([[0.4086107, 0.4      ],
       [0.4      , 0.6      ]], dtype=float32)>
a
<tf.Tensor: id=106, shape=(2, 2), dtype=float32, numpy=
array([[0.4086107 , 0.38549483],
       [0.2081635 , 0.9825165 ]], dtype=float32)>

1:tf.random_normal:

正態分佈產生的隨機值:常用的參數就是shape,和dtype了,但是也包括方差和均值;

參數(shape,stddev,mean,dtype)

2:tf.random_uniform 

默然是在0到1之間產生隨機數:

但是也可以通過maxval指定上界,通過minval指定下界

//感覺這個理解才正確?

❑ 通過限制梯度張量𝑾的範數來實現梯度裁剪。比如對𝑾的二範數‖𝑾‖2約束在[0,max] 之間,如果‖𝑾‖2大於max值,則按照

W = \frac{W}{\left \| W \right \|_{2}}\cdot max

方式將‖𝑾′‖2約束max內。可以通過 tf.clip_by_norm 函數方便的實現梯度張量𝑾裁剪。例如:

a=tf.random.uniform([2,2]) * 5
# 按範數方式裁剪
b = tf.clip_by_norm(a, 5) 

tf.norm(a),tf.norm(b)
(<tf.Tensor: id=162, shape=(), dtype=float32, numpy=6.430176>,
 <tf.Tensor: id=167, shape=(), dtype=float32, numpy=5.0>)

a
<tf.Tensor: id=141, shape=(2, 2), dtype=float32, numpy=
array([[3.4196944, 1.4182079],
       [1.8361145, 4.9264812]], dtype=float32)>

b
<tf.Tensor: id=157, shape=(2, 2), dtype=float32, numpy=
array([[2.6590989, 1.1027755],
       [1.4277327, 3.8307517]], dtype=float32)>

numpy=6.430176 是a裏面數值平方和的平方根。

可以看到,對於大於max的 L2 範數的張量,通過裁剪後範數值縮減爲 5。

❑ 神經網絡的更新方向是由所有參數的梯度張量𝑾共同表示的,前兩種方式只考慮單個 梯度張量的限幅,會出現網絡更新方向發生變動的情況。如果能夠考慮所有參數的梯 度𝑾的範數,實現等比例的縮放,那麼就能既很好地限制網絡的梯度值,同時不改變 網絡的更新方向。這就是第三種梯度裁剪的方式:全局範數裁剪。在 TensorFlow 中, 可以通過 tf.clip_by_global_norm 函數快捷地縮放整體網絡梯度𝑾的範數。

在網絡訓練時,梯度裁剪一般在計算出梯度後,梯度更新之前進行。

對於梯度彌散現象,可以通過增大學習率、減少網絡深度、添加 Skip Connection 等一系列的措施抑制。

增大學習率𝜂可以在一定程度防止梯度彌散現象,當出現梯度彌散時,網絡的梯度∇𝜃 L 接近於 0,此時若學習率𝜂也較小,如η = 1e − 5,則梯度更新步長更加微小。通過增大學 習率,如令𝜂 = 1e − 2,有可能使得網絡的狀態得到快速更新,從而逃離梯度彌散區域。

對於深層次的神經網絡,梯度由最末層逐漸向首層傳播,梯度彌散一般更有可能出現 在網絡的開始數層。在深度殘差網絡出現之前,幾十上百層的深層網絡訓練起來非常困 難,前面數層的網絡梯度極容易出現梯度離散現象,從而使得網絡參數長時間得不到更 新。深度殘差網絡較好地克服了梯度彌散現象,從而讓神經網絡層數達到成百上千。一般 來說,減少網絡深度可以減輕梯度彌散現象,但是網絡層數減少後,網絡表達能力也會偏 弱,需要用戶自行平衡。

循環神經網絡除了訓練困難,還有一個更嚴重的問題,那就是短時記憶(Short-term memory)

循環神經網絡在處理較長的句子時,往往只能夠理解有限長度內的信 息,而對於位於較長範圍類的有用信息往往不能夠很好的利用起來。我們把這種現象叫做 短時記憶。

LSTM網絡

1997 年,瑞士人工智能科學家 Jürgen Schmidhuber 提出了長短 時記憶網絡(Long Short-Term Memory,簡稱 LSTM)。LSTM 相對於基礎的 RNN 網絡來 說,記憶能力更強,更擅長處理較長的序列信號數據,LSTM 提出後,被廣泛應用在序列 預測、自然語言處理等任務中,幾乎取代了基礎的 RNN 模型。

在 LSTM 中,有兩個狀態向量𝒄和 ,其中𝒄作爲 LSTM 的內部狀態向量,可以理解爲 LSTM 的內存狀態向量 Memory,而 表示 LSTM 的輸出向量。相對於基礎的 RNN 來說, LSTM 把內部 Memory 和輸出分開爲兩個變量,同時利用三個門控:輸入門(Input Gate)、 遺忘門(Forget Gate)和輸出門(Output Gate)來控制內部信息的流動。

門控機制可以理解爲控制數據流通量的一種手段,類比於水閥門:當水閥門全部打開 時,水流暢通無阻地通過;當水閥門全部關閉時,水流完全被隔斷。在 LSTM 中,閥門開 和程度利用門控值向量𝒈表示,如圖 11.15 所示,通過𝜎(𝒈)激活函數將門控制壓縮到[0,1] 之間區間,當𝜎(𝒈) = 0時,門控全部關閉,輸出𝒐 = 0;當𝜎(𝒈) = 1時,門控全部打開,輸 出𝒐 = 𝒙。通過門控機制可以較好地控制數據的流量程度。

嗯,有一堆公式,基本瞭解了LSTM網絡的作用原理。

LSTMCell 的用法和 SimpleRNNCell 基本一致,區別在於 LSTM 的狀態變量 List 有兩 個,即[ 𝑡,𝒄𝑡],需要分別初始化,其中List第一個元素爲 𝑡,第二個元素爲𝒄𝑡。調用cell 完成前向運算時,返回兩個元素,第一個元素爲cell的輸出,也就是 𝑡,第二個元素爲 cell的更新後的狀態List:[ 𝑡,𝒄𝑡]。首先新建一個狀態向量長度h=64的LSTMCell,其中 狀態向量𝒄𝑡和輸出向量 𝑡的長度都爲h,

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics
x = tf.random.normal([2,80,100])
xt = x[:, 0, :]  #所以這個xtshape是(2,100)
cell = layers.LSTMCell(64) # 創建 LSTM Cell
# 初始化狀態和輸出 List,[h,c]
state = [tf.zeros([2,64]),tf.zeros([2,64])]
# 查看返回元素的 id
id(out),id(state[0]),id(state[1])

out.shape
TensorShape([2, 64])

通過在時間戳上展開循環運算,即可完成一次層的前向傳播,寫法與基礎的 RNN 一 樣。

通過 layers.LSTM 層可以方便的一次完成整個序列的運算。首先新建 LSTM 網絡層。

經過 LSTM 層前向傳播後,默認只會返回最後一個時間戳的輸出,如果需要返回每個時間 戳上面的輸出,需要設置 return_sequences=True 標誌。

# 創建 LSTM 層時,設置返回每個時間戳上的輸出
layer = layers.LSTM(64, return_sequences=True)
# 前向計算,每個時間戳上的輸出自動進行了 concat,拼成一個張量 
out = layer(x)

out.shape
TensorShape([2, 80, 64])

對於多層神經網絡,可以通過 Sequential 容器包裹多層 LSTM 層,並設置所有非末層 網絡 return_sequences=True,這是因爲非末層的 LSTM 層需要上一層在所有時間戳的輸出作爲輸入。因爲需要,所以要有保留?

# 和 CNN 網絡一樣,LSTM 也可以簡單地層層堆疊
net = keras.Sequential([
    layers.LSTM(64, return_sequences=True), # 非末層需要返回所有時間戳輸出
    layers.LSTM(64) ])
# 一次通過網絡模型,即可得到最末層、最後一個時間戳的輸出 out = net(x)

GRU網絡:

LSTM 具有更長的記憶能力,在大部分序列任務上面都取得了比基礎的 RNN 模型更好 的性能表現,更重要的是,LSTM 不容易出現梯度彌散現象。但是 LSTM 結構相對較複雜,計算代價較高,模型參數量較大。因此,科學家們嘗試簡化 LSTM 內部的計算流程, 特別是減少門控數量。

研究發現,遺忘門是 LSTM 中最重要的門控 [2],甚至發現只有遺 忘門的簡化版網絡在多個基準數據集上面優於標準 LSTM 網絡。在衆多的簡化版 LSTM 中,門控循環網絡(Gated Recurrent Unit,簡稱 GRU)是應用最廣泛的 RNN 變種之一。GRU 把內部狀態向量和輸出向量合併,統一爲狀態向量 ,門控數量也減少到 2 個:復位門 (Reset Gate)和更新門(Update Gate)。

所以GRU網絡就只有一個輸出網絡

 

在 TensorFlow 中,也有 Cell 方式和層方式實現 GRU 網絡。GRUCell 和 GRU 層的使用方法和之前的 SimpleRNNCell、LSTMCell、SimpleRNN 和 LSTM 非常類似。首 先是 GRUCell 的使用,創建 GRU Cell 對象,並在時間軸上循環展開運算。

所以在這裏就學習完成了LSTM以及GRU網絡。

然後這裏開始了實戰

前面我們介紹了情感分類問題,並利用 SimpleRNN 模型完成了情感分類問題的實戰, 在介紹完更爲強大的 LSTM 和 GRU 網絡後,我們將網絡模型進行升級。得益於 TensorFlow 在循環神經網絡相關接口的格式統一,在原來的代碼基礎上面只需要修改少量 幾處,便可以完美的升級到 LSTM 模型或 GRU 模型。

 

在情感分類任務時,Embedding 層是從零開始訓練的。實際上,對於文本處理任務來 說,領域知識大部分是共享的,因此我們能夠利用在其它任務上訓練好的詞向量來初始化 Embedding 層,完成領域知識遷移。基於預訓練的 Embedding 層開始訓練,少量樣本時也 能取得不錯的效果。

 

 

 

 

 

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