當前位置:
首頁 > 最新 > 使用PaddleFluid和TensorFlow訓練RNN語言模型

使用PaddleFluid和TensorFlow訓練RNN語言模型

專欄介紹:Paddle Fluid 是用來讓用戶像 PyTorch 和 Tensorflow Eager Execution 一樣執行程序。在這些系統中,不再有模型這個概念,應用也不再包含一個用於描述 Operator 圖或者一系列層的符號描述,而是像通用程序那樣描述訓練或者預測的過程。

本專欄將推出一系列技術文章,從框架的概念、使用上對比分析 TensorFlow 和 Paddle Fluid,為對 PaddlePaddle 感興趣的同學提供一些指導。

在圖像領域,最流行的 building block 大多以卷積網路為主。上一篇我們介紹了如何在 PaddleFluid 和 TensorFlow 上訓練圖像分類任務。卷積網路本質上依然是一個前饋網路,在神經網路基本單元中循環神經網路是建模序列問題最有力的工具, 有著非常重要的價值。自然語言天生是一個序列,在自然語言處理領域(Nature Language Processing,NLP)中,許多經典模型都基於循環神經網路單元。可以說自然語言處理領域是 RNN 的天下。

這一篇以 NLP 領域的 RNN 語言模型(RNN Language Model,RNN LM)為實驗任務,對比如何使用 PaddleFluid 和 TensorFlow 兩個平台實現序列模型。 這一篇中我們會看到 PaddleFluid 和 TensorFlow 在處理序列輸入時有著較大的差異:PaddleFluid 默認支持非填充的 RNN 單元,在如何組織 mini-batch 數據提供序列輸入上也簡化很多。

如何使用代碼

本篇文章配套有完整可運行的代碼,請從隨時從 github[1]上獲取最新代碼。代碼包括以下幾個文件:

注意:在運行模型訓練之前,請首先進入 data 文件夾,在終端運行sh download.sh下載訓練數據。

在終端運行以下命令便可以使用默認結構和默認參數運行 PaddleFluid 訓練 RNN LM。

在終端運行以下命令便可以使用默認結構和默認參數運行 TensorFlow 訓練 RNN LM。

背景介紹

one-hot和詞向量表示法

計算機如何表示語言是處理 NLP 任務的首要問題。這裡介紹將會使用到的 one-hot 和詞向量表示法。

one-hot 表示方法:一個編碼單元表示一個個體,也就是一個詞。於是,一個詞被表示成一個長度為字典大小的實數向量,每個維度對應字典里的一個詞,除了該詞對應維度上的值是 1,其餘維度都是 0。

詞向量表示法:與 one-hot 表示相對的是 distributed representation ,也就是常說的詞向量:用一個更低維度的實向量表示詞語,向量的每個維度在實數域 RR 取值。

在自然語言處理任務中,一套好的詞向量能夠提供豐富的領域知識,可以通過預訓練獲取,或者與最終任務端到端學習而來。

循環神經網路

循環神經網路(Recurrent Neural Network)是一種對序列數據建模的重要單元,模擬了離散時間(這裡我們只考慮離散時間)動態系統的狀態演化。「循環」 兩字刻畫了模型的核心:上一時刻的輸出作為下一個時刻的輸入,始終留在系統中如下面的圖 1 所示,這種循環反饋能夠形成複雜的歷史。自然語言是一個天生的序列輸入,RNN 恰好有能力去刻畫辭彙與辭彙之間的前後關聯關係,因此,在自然語言處理任務中佔有重要的地位。

▲圖1. 最簡單的RNN單元

RNN 形成「循環反饋」 的過程是一個函數不斷複合的過程,可以等價為一個層數等於輸入序列長度的前饋神經網路,如果輸入序列有 100 個時間步,相當於一個 100 層的前饋網路,梯度消失和梯度爆炸的問題對 RNN 尤為嚴峻。

直覺上大於 1 的數連乘越乘越大,極端時會引起梯度爆炸;小於 1 的數連乘越乘越小,極端時會引起梯度消失。梯度消失也會令在循環神經網路中,後面時間步的信息總是會」壓過」前面時間步。如果 t 時刻隱層狀態依賴於 t 之前所有時刻,梯度需要通過所有的中間隱層逐時間步回傳,這會形成如圖 2 所示的一個很深的求導鏈。

▲圖2. t時刻依賴t時刻之前所有時刻

在許多實際問題中時間步之間相互依賴的鏈條並沒有那麼長,t 時刻也許僅僅依賴於它之前有限的若干時刻。很自然會聯想到:如果模型能夠自適應地學習出一些如圖 3 所示的信息傳播捷徑來縮短梯度的傳播路徑,是不是可以一定程度減梯度消失和梯度爆炸呢?答案是肯定的,這也就是 LSTM 和 GRU 這類帶有 「門控」思想的神經網路單元。

▲圖3. 自適應地形成一些信息傳播的「捷徑」

關於 LSTM 更詳細的介紹請參考文獻[2],這裡不再贅述,只需了解 LSTM/GUR 這些門控循環神經網路單元提出的動機即可。

RNN LM

語言模型是 NLP 領域的基礎任務之一。語言模型是計算一個序列的概率,判斷一個序列是否屬於一個語言的模型,描述了這樣一個條件概率,其中是輸入序列中的 T 個詞語,用 one-hot 表示法表示。

言模型顧名思義是建模一種語言的模型,這一過程如圖 4 所示:

▲圖4. RNN語言模型

RNN LM的工作流程如下:

1. 給定一段 one-hot 表示的輸入序列 ,將它們嵌入到實向量空間,得到詞向量表示 :。

2. 以詞向量序列為輸入,使用 RNN 模型(可以選擇LSTM或者GRU),計算輸入序列到 t 時刻的編碼 ht。

3. softmax 層以 ht 為輸入,預測下一個最可能的詞的概率。

4.,根據和計算誤差信號。

PTB數據集介紹

至此,介紹完 RNN LM 模型的原理和基本結構,下面準備開始分別使用 PaddleFluid 和 TensorFlow 來構建我們的 訓練任務。這裡首先介紹這一篇我們使用 Mikolov 與處理過的 PTB 數據,這是語言模型任務中使用最為廣泛的公開數據之一。 PTB 數據集包含 10000 個不同的詞語(包含句子結束符,以及表示 低頻詞的特殊符號)。

程序結構

這一節我們首先整體總結一下使用 PaddleFluid 平台和 TensorFlow 運行自己的神經網路模型都有哪些事情需要完成。

PaddleFluid

1. 調用 PaddleFluid API 描述神經網路模型。PaddleFluid 中一個神經網路訓練任務被稱之為一段Fluid Program。

2. 定義Fluid Program執行設備:place。常見的有fluid.CUDAPlace(0)和fluid.CPUPlace()

註:PaddleFluid 支持混合設備運行,一些運算(operator)沒有特定設備實現,或者為了提高全局資源利用率,可以為他們指定不同的計算設備。

3. 創建 PaddleFluid 執行器(Executor),需要為執行器指定運行設備。

讓執行器執行fluid.default_startup_program(),初始化神經網路中的可學習參數,完成必要的初始化工作。

5. 定義 DataFeeder,編寫 data reader,只需要關注如何返回一條訓練/測試數據

6. 進入訓練的雙層循環(外層在 epoch 上循環,內層在 mini-batch 上循環),直到訓練結束。

TensorFlow

1. 調用 TensorFlow API 描述神經網路模型。 TensorFlow 中一個神經網路模型是一個 Computation Graph。

2. 創建TensorFlow Session用來執行計算圖。

3. 調用sess.run(tf.global_variables_initializer())初始化神經網路中的可學習參數。

4. 編寫返回每個 mini-batch 數據的數據讀取腳本。

5. 進入訓練的雙層循環(外層在 epoch 上循環,內層在 mini-batch 上循環),直到訓練結束。

如果不顯示地指定使用何種設備進行訓練,TensorFlow 會對機器硬體進行檢測(是否有 GPU), 選擇能夠儘可能利用機器硬體資源的方式運行。

從以上的總結中可以看到,PaddleFluid 程序和 TensorFlow 程序的整體結構非常相似,使用經驗可以非常容易的遷移。

構建網路結構及運行訓練

載入訓練數據

PaddleFluid

定義 輸入data layers

1. 定義 data layer 的核心是指定輸入 Tensor 的形狀(shape)和類型。

2. RNN LM 使用 one-hot 作為輸入,一個詞用一個和字典大小相同的向量表示,每一個位置對應了字典中的 一個詞語。one-hot 向量僅有一個維度為 1, 其餘全部為 0。因此為了節約存儲空間,通常都直接用一個整型數表示給出詞語在字典中的 id,而不是真的創建一個和詞典同樣大小的向量 ,因此在上面定義的 data layer 中word和lbl的形狀都是 1,類型是int64。

3. 需要特別說明的是,實際上word和lbl是兩個[batch_size x 1]的向量,這裡的batch size是指一個 mini-batch 中序列中的總詞數。對序列學習任務, mini-batch 中每個序列長度 總是在發生變化,因此實際的batch_size只有在運行時才可以確定。batch size總是一個輸入 Tensor 的第 0 維,在 PaddleFluid 中指定 data layer 的 shape 時,不需要指定batch size的大小,也不需要考慮佔位。框架會自動補充佔位符,並且在運行時 設置正確的維度信息。因此,上面的兩個 data layer 的 shape 都只需要設置第二個維度,也就是 1。

LoD Tensor和Non-Padding的序列輸入

與前兩篇文章中的任務相比,在上面的代碼片段中定義 data layer 時,出現了一個新的lod_level欄位,並設置為 1。這裡就要介紹在 Fluid 系統中表示序列輸入的一個重要概念 LoDTensor。

那麼,什麼是 LoD(Level-of-Detail) Tensor 呢?

1. Tensor 是 nn-dimensional arry 的推廣,LoDTensor 是在 Tensor 基礎上附加了序列信息。

2. Fluid 中輸入、輸出,網路中的可學習參數全部統一使用 LoDTensor(n-dimension array)表示,對非序列數據,LoD 信息為空。一個 mini-batch 輸入數據是一個 LoDTensor。

3. 在 Fluid 中,RNN 處理變長序列無需 padding,得益於 LoDTensor表示

4. 可以簡單將 LoD 理解為:std::vector。

下圖是 LoDTensor 示意圖(圖片來自 Paddle 官方文檔):

▲圖5. LoD Tensor示意圖

LoD 信息是附著在一個 Tensor 的第 0 維(也就是 batch size 對應的維度),來對一個 batch 中的數據進一步進行劃分,表示了一個序列在整個 batch 中的起始位置。

LoD 信息可以嵌套,形成嵌套序列。例如,NLP 領域中的段落是一種天然的嵌套序列,段落是句子的序列,句子是詞語的序列。

LoD 中的 level 就表示了序列信息的嵌套:

圖 (a) 的 LoD 信息[0, 5, 8, 10, 14]:這個 batch 中共含有 4 條序列。

圖 (b) 的 LoD 信息[[0, 5, 8, 10, 14] /*level=1*/, [0, 2, 3, 5, 7, 8, 10, 13, 14] /*level=2*/]:這個 batch 中含有嵌套的雙層序列。

有了 LoDTensor 這樣的數據表示方式,用戶不需要對輸入序列進行填充,框架會自動完成 RNN 的並行計算處理。

如何構造序列輸入信息

明白了 LoD Tensor 的概念之後,另一個重要的問題是應該如何構造序列輸入。在 PaddleFluid 中,通過 DataFeeder 模塊來為網路中的 data layer 提供數據,調用方式如下面的代碼所示:

觀察以上代碼,需要用戶完成的僅有:編寫一個實現讀取一條數據的 python 函數:train_data。train_data的代碼非常簡單,我們再來看一下它的具體實現[3]:

在上面的代碼中:

1.train_data是一個 python generator ,函數名字可以任意指定,無需固定。

2.train_data打開原始數據數據文件,讀取一行(一行既是一條數據),返回一個 python list,這個 python list 既是序列中所有時間步。具體的數據組織方式如下表所示(其中,f 代表一個浮點數,i 代表一個整數):

3.paddle.batch()介面用來構造 mini-batch 輸入,會調用train_data將數據讀入一個 pool 中,對 pool 中的數據進行 shuffle,然後依次返回每個 mini-batch 的數據。

TensorFlow

TensorFlow 中使用佔位符 placeholder 接收 訓練數據,可以認為其概念等價於 PaddleFluid 中的 data layer。同樣的,我們定義了如下兩個 placeholder 用於接收當前詞與下一個詞語:

1. placeholder 只存儲一個 mini-batch 的輸入數據。與 PaddleFluid 中相同,_inputs這裡接收的是 one-hot 輸入,也就是該詞語在詞典中的 index,one-hot 表示 會進一步通過此詞向量層的作用轉化為實值的詞向量表示。

2. 需要注意的是,TensorFlow 模型中網路輸入數據需要進行填充,保證一個 mini-batch 中序列長度 相等。也就是一個 mini-batch 中的數據長度都是max_seq_length,這一點與 PaddleFluid 非常不同。

通常做法 是對不等長序列進行填充,在這一篇示例中我們使用一種簡化的做法,每條訓練樣本都按照max_sequence_length來切割,保證一個 mini-batch 中的序列是等長的。

於是,_input的shape=[batch_size, max_sequence_length]。max_sequence_length即為 RNN 可以展開長度。

構建網路結構

PaddleFluidRNN LM

這裡主要關注最核心的 LSTM 單元如何定義:

PaddleFluid 中的所有 RNN 單元(RNN/LSTM/GRU)都支持非填充序列作為輸入,框架會自動完成不等長序列的並行處理。當需要堆疊多個 LSTM 作為輸入時,只需利用 Python 的for循環語句,讓一個 LSTM 的輸出成為下一個 LSTM 的輸入即可。在上面的代碼片段中有一點需要特別注意:PaddleFluid 中的 LSTM 單元是由fluid.layers.fc+fluid.layers.dynamic_lstm共同構成的。

▲圖6. LSTM計算公式

TensorFlow RNN LM

這裡主要關注最核心的 LSTM 單元如何定義:

tf.contrib.rnn.MultiRNNCell: 用來 wrap 一組序列調用的 RNN 單元的 wrapper。

但是,dynamic_rnn可以讓不同 mini-batch 的 batch size 長度不同,但同一次迭代一個 batch 內部的所有數據長度仍然是固定的。

運行訓練

運行訓練任務對兩個平台都是常規流程,可以參考上文在程序結構一節介紹的流程,以及代碼部分:PaddleFluid vs. TensorFlow,這裡不再贅述。

總結

這一篇我們第一次接觸 PaddleFluid 和 TensorFlow 平台的序列模型。了解 PaddleFluid 和 TensorFlow 在接受序列輸入,序列處理策略上的不同。序列模型是神經網路模型中較為複雜的一類模型結構,可以衍生出非常複雜的模型結構。

不論是 PaddleFluid 以及 TensorFlow 都實現了多種不同的序列建模單元,如何選擇使用這些不同的序列建模單元有很大的學問。到目前為止平台使用的一些其它重要主題:例如多線程多卡,如何利用混合設備計算等我們還尚未涉及。接下來的篇章將會繼續深入 PaddleFluid 和 TensorFlow 平台的序列模型處理機制,以及更多重要功能如何在兩個平台之間實現。

參考文獻

[1]. 本文配套代碼

https://github.com/JohnRabbbit/TF2Fluid/tree/master/03_rnnlm

[2].Understanding LSTM Networks

http://colah.github.io/posts/2015-08-Understanding-LSTMs/

[3].train_data具體實現

https://github.com/JohnRabbbit/TF2Fluid/blob/master/03_rnnlm/load_data_fluid.py

關於PaperWeekly

PaperWeekly 是一個推薦、解讀、討論、報道人工智慧前沿論文成果的學術平台。如果你研究或從事 AI 領域,歡迎在公眾號後台點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 PaperWeekly 的精彩文章:

DeepMind論文解讀:讓機器更深入地理解文本

TAG:PaperWeekly |