面向機器智慧的TensorFlow實戰6:迴圈神經網路與自然語言處理

CopperDong發表於2018-05-26

      本章將探討序列模型(sequential model),可對序列輸入進行分類或標記,生成文字序列或將一個序列轉換為另一個序列。RNN提供了一些構件,可以很好地切入全連線層和卷積層的工具集。

1、RNN簡介

       許多真實問題本質上都是序列化的。2006年提出的一種LSTM。RNN能夠很好地完成許多領域的序列任務,如語音識別、語音合成、手寫連體字識別、時間序列預測、影像標題生成以及端到端的機器翻譯等。

        近似任意程式:之前的前饋神經網路只能在固定長度的向量上工作。相比之下,無論輸入還是輸出為可變長向量,或輸入輸出均為可變長向量,RNN都可以應對。

        RNN基本上是一個由神經元和連線權值構成的任意有向圖。輸入神經元的活性值是由輸入資料設定的。輸出神經元也只是資料流圖中的一組可從中讀取預測結果的神經元。其他神經元都被稱為隱含神經元。RNN的當前隱含活性值被稱為狀態。在每個序列的最開始,通常會設定一個值為0的空狀態。

        RNN的狀態依賴於當前輸入和上一個狀態,而後者又依賴於更上一步的輸入和狀態。因此,狀態與序列之前提供的所有輸入都間接相關,從而可理解為工作記憶。

       帶有sigmoid函式的RNN已由Sch和Z於2006年證明是圖靈完備的。這意味著,當給正確的權值時,RNN可完成與任意計算程式相同的計算。然而這只是一個理論性質,因為當給定一項任務時,不存在找到完美權值的方法。儘管如此,利用梯度下降法仍然能夠得到相當好的結果。

        隨時間反向傳播:如何在RNN這種動態系統中將誤差反向傳播並非那麼顯而易見。優化RNN可採取這樣一種技巧,即沿時間軸將其展開,之後就可使用與優化前饋網路相同的方式對RNN進行優化。這樣,為計算誤差相對於各權值的梯度,便可對這個展開的RNN網路運用標準的反向傳播演算法。這種演算法被稱為隨時間反向傳播(Back-Propagation Through Time, BPTT)。該演算法將返回與時間相關的誤差對每個權值(也包括那些聯結相鄰副本的權值)的偏導。為保持聯結權值相同,可採用普通的聯結權值處理方法,即將它們的梯度相加。請注意,這種方式與卷積神經網路中處理卷積濾波器的方式等價。


   序列的編碼與解碼:  常見的對映   


      實現第一個RNN:TensorFlow支援RNN的各種變體,可從tf.nn.rnn_cell模組中找到這些變體的實現。藉助tensorflow.models.rnn中的tf.nn.dynamic_rnn()運算,TensorFlow還為我們實現了RNN動力學。關於引數,dynamic_rnn()接收一個迴圈網路的定義以及若干輸入序列構成的批資料。就目前而言,所有的序列都是等長的。該函式會向資料流圖建立RNN所需的計算,並返回儲存了每個時間步的輸出和隱含狀態的兩個張量。

import tensorflow as tf
from tensorflow.models.rnn import rnn_cell
from tensorflow.models.rnn import rnn

# 輸入資料的維數為batch_size*sequence_length*frame_size
# 不希望限制批次的大小,將第1維的尺寸設為None
sequence_length = ...
frame_size = ...
data = tf.placeholder(tf.float32, [None, sequence_length, frame_size])

num_neurons = 200
network = rnn_cell.BasicRNNCell(num_neurons)

# 為sequence_length步定義模擬RNN的運算
outputs, states = rnn.dynamic_rnn(network, data, dtype=tf.float32)

       梯度消失與梯度爆炸: 上面的模型無法撲捉輸入幀間的長時依賴關係,而這種關係正是NLP任務所需要的。如判別給定輸入序列是否為給定語法的一部分。為完成該任務,網路必須記住其中含有許多後續不相關的幀的序列的第一幀。RNN之所以難於學習這種長時依賴關係,原因在於優化期間誤差在網路中的傳播方式。為了計算梯度,要將誤差在展開後的RNN中傳播。對於長序列而言,這種展開的網路的深度將會非常大,層數非常多,在每一層中,反向傳播演算法都會將來自網路上一層的誤差乘以區域性偏導。如果大多數區域性偏導都遠小於1,則梯度每經過一層都會變小,且呈指數級衰減,從而最終消失。類似地,如果許多偏導都大於1,則會使梯度值急劇增大。

        實際上,在任何深度網路中,該問題都是存在的,而非只有迴圈神經網路中才有這樣的問題。在RNN中,相鄰時間步是聯結在一起的,因此,這樣的權值的區域性偏導要麼都小於1,要麼都大於1,原始RNN中每個權值都會向著相同的方向被縮放。因此,相比於前饋神經網路,梯度消失或梯度爆炸這個問題在RNN中更為突出。

        當梯度的各分量接近於0或無窮大時,訓練分別會出現停滯或發散。此外,由於我們做的是數值優化,因此,浮點精度也會對梯度值產生影響。該問題也被稱為深度學習中的基本問題。在近年來已受到許多研究者的關注。目前最流行的解決方案是一種稱為長短時記憶網路(long-short term memory, LSTM)的RNN架構。

       長短時記憶網路:LSTM是一種特殊形式的RNN,由Hochreiter和Schmidhuber於1997年提出,它是專為解決梯度消失和梯度爆炸問題而設計的。在學習長時依賴關係時它有著卓越的表現,併成為RNN事實上的標準。自從該模型被提出後,相繼提出了LSTM的若干變種,這些變種的實現已包含在TensorFlow中。

       為解決梯度消失和梯度爆炸問題,LSTM架構將RNN中的普通神經元替換為其內部擁有少量記憶的LSTM單元(LSTM Cell)。如同普通RNN,這些單元也被聯結在一起,但它們還擁有有助於記憶許多時間步中的誤差的內部狀態。LSTM的竅門在於這種內部狀態擁有一個固定權值為1的自連線,以及一個線性啟用函式,因此其區域性偏導始終為1 。在反向傳播階段,這個所謂的常量誤差傳輸子能夠在許多時間步中攜帶誤差而不會發生梯度消失或梯度爆炸。


      儘管內部狀態的目的是隨許多時間步傳遞誤差,LSTM架構中負責學習的實際上是環繞門,這些門都擁有一個非線性的啟用函式。在原始的LSTM單元中,有兩種門:一種負責學習如何對到來的活性值進行縮放,而另一種負責學習如何對輸出的活性值進行縮放。因此,這種單元可學習何時包含或忽略新的輸入,以及何時將它表示的特徵傳遞給其他單元。一個單元的輸入會送入使用不同權值的所有門中。

       RNN結構的變種:LSTM的一種比較流行的變種是新增一個對內部迴圈連線進行比例縮放的遺忘門,以允許網路學會遺忘。這樣,內部迴圈連線的區域性偏導就變成了遺忘門的活性值,從而可取為非1的值。當記憶單元上下文非常重要時,這種網路也可以學會將遺忘門保持關閉狀態。將遺忘門的初始值設為1是非常重要的,因為這樣可使LSTM單元從一個記憶狀態開始工作。如今,在幾乎所有的實現中,遺忘門都是預設存在的。在TensorFlow中,可通過指定LSTM層的forget_bias引數對遺忘門的偏置進行初始化,預設初值為1,也建議不要修改這個預設值。

       另一種擴充套件是新增窺視孔連線,以使一些門能夠看到單元的狀態。提出該變種的作者聲稱當任務中涉及精確的時間選擇和間隔時,使用窺視孔連線是有益的。TensorFlow的LSTM層支援窺視孔連線,可通過為LSTM層傳入use_peepholes=True標記將窺視孔連線啟用。

       基於LSTM的基本思想,Chung Junyoung等於2014年提出了門限迴圈單元(Gated Recurrent Unit, GRU)。與LSTM相比,GRU的架構更簡單,而且只需更少的計算量就可得到與LSTM非常相近的結果。GRU沒有輸出門,它將輸入和遺忘門整合為一個單獨的更新門(update gate)。更新門決定了內部狀態與候選活性值的融合比例。候選活性值是依據由重置門(reset gate)和新的輸入確定的部分隱含狀態計算得到的。Tensorflow的GRU層對應GRUCell類,除了該層中的單元數目,它不含任何其他引數。如果引數進一步瞭解GRU,參考文獻:

Jozefowicz, Rafal, Wojciech Zaremba, and Ilya Sutskever. “ An empirical exploration of recurrent network architectures.” Proceedings of the 32nd International Conference on Machine Learning (ICML-15). 2015.


      由於資訊只能在兩層之間向上流動,與規模較大的全連線RNN相比,多層RNN擁有的權值數目更少,而且有助於學習到更多的抽象特徵。

   PTB資料:http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz,本文只關心data資料夾,有三個資料檔案並經過了預處理,包含了10000個不同的詞語和語句結束標記符以及標記稀有詞語的特殊符號<unk>。

    TensorFlow提供了兩個函式來幫助實現資料的預處理。ptb_raw_data函式讀取PTB的原始資料,並將原始資料中的單詞轉化為單詞ID,訓練資料中總共包含了929589個單詞,而這些單詞被組成了一個非常長的序列。這個序列通過特殊的識別符號給出了每句話結束的位置。在這個資料集中,句子結束的識別符號ID為2.

   時間序列預測:預測正弦函式。


相關文章