1. 程式人生 > >TensorFlow文字摘要生成 - 基於注意力的序列到序列模型

TensorFlow文字摘要生成 - 基於注意力的序列到序列模型

1 相關背景
維基百科對自動摘要生成的定義是, “使用計算機程式對一段文字進行處理, 生成一段長度被壓縮的摘要, 並且這個摘要能保留原始文字的大部分重要資訊”. 摘要生成演算法主要分為抽取型(Extraction-based)和概括型(Abstraction-based)兩類. 傳統的摘要生成系統大部分都是抽取型的, 這類方法從給定的文章中, 抽取關鍵的句子或者短語, 並重新拼接成一小段摘要, 而不對原本的內容做創造性的修改. 這類抽取型演算法工程上已經有很多開源的解決辦法了, 例如Github上的專案sumy, pytextrank, textteaser等. 本文重點講概括型摘要生成系統的演算法思想和tensorflow實戰, 演算法思想源於A Neural Attention Model for Abstractive Sentence Summarization這篇論文. 本文希望幫助讀者詳細的解析演算法的原理, 再結合github上相關的開源專案textsum講解工程上的實際應用.本文由PPmoney大資料演算法團隊撰寫,PPmoney是國內領先的網際網路金融公司,旗下PPmoney理財總交易額超過700億元。此外,若對TensorFlow的使用技巧和方法感興趣,歡迎閱讀本團隊負責人黃文堅所著的《TensorFlow實戰》。

2 演算法原理
下面對A Neural Attention Model for Abstractive Sentence Summarization這篇文章, 的演算法原理進行講解. 我們將這個模型簡稱為NAM. 主要分為模型訓練(train)和生成摘要(decode)兩部分講解.

2.1 模型訓練(train)
NAM這個模型是純資料驅動, 我們餵給它的訓練集資料是由一系列{正文: 摘要}對組成. 假設正文是x=[x1,...,xM]x=[x1,...,xM], MM是正文詞符的數量, 對應的摘要為y=[y1,...,yN]y=[y1,...,yN], NN是摘要單詞的數量. 
對於給定的資料, 我們希望給定xx生成摘要為yy的概率最大, 即maxθlogp(y|x;θ)maxθlog⁡p(y|x;θ), θθ是模型的引數. 但這個很難求解, 實際中我們用序列化的方式例項化這個目標, 原來的目標函式變為: 
maxθ∑i=0N−1logp(yi+1|x,yc;θ)
maxθ∑i=0N−1log⁡p(yi+1|x,yc;θ)

這裡 yi+1yi+1是要預測的下一個詞, yc≜y[i−C+1,...,i]yc≜y[i−C+1,...,i]是已知的序列, CC是已知序列視窗的長度. 後面會提到, 這個視窗的位置也是注意力關注的位置, 在後面的訓練過程中會根據學習到的權重調整不同位置注意力的概率大小. 這個視窗是隨著ii的迭代來滑動的. 
引數說明: 
yy: 參考摘要所有單詞向量組成的序列 
xx: 正文的所以單詞向量組成的序列 
ii: 當前評估函式所對應的位置 
ycyc: 當前訓練的視窗對應的區域性摘要序列 
yi+1yi+1: 模型要預測的下一個單詞
下面我們舉一個例子來說明訓練的過程: 
 
我們希望根據, 當前區域性摘要序列ycyc和全部的正文資訊xx, 來預測下一個單詞yi+1yi+1. 我們希望模型預測下一個單詞為yi+1yi+1的概率最大, 並且希望所有單詞都儘可能的預測準確, 在公式上表現為∑N−1i=0logp(yi+1|x,yc;θ)∑i=0N−1log⁡p(yi+1|x,yc;θ)最大. 視窗CC會從摘要的起始位置滑動到終止位置, 當i<Ci<C時, ycyc超出摘要的部分用起始符號<s>來補全. 
我們感興趣的分佈p(yi+1|x,yc;θ)p(yi+1|x,yc;θ)是基於輸入語句xx的條件語言模型. 這裡我們直接將原始的分佈, 引數化為一個神經網路. 這個神經網路既包括了一個神經概率語言模型(neural probabilistic language model), 也包括了一個編碼器(這個編碼器就是一個條件摘要模型). 
通過包含編碼器並且聯合訓練這兩個組塊, 我們根據當前ycyc對xx的不同內容投入不同的關注度, 進而的到更好的結果. 模型結構如下圖所示: 


模型整體的網路結構圖(具有一個額外的編碼器單元): 
右側分支: 僅根據當前的序列ycyc預測下一個單詞是yi+1yi+1的概率, EE是詞嵌入, y~′cy~c′ -> hh包括加權和啟用函式的操作. 
左側分支: 使用ycyc和xx生成隱層的下一個輸出, ycyc會對encoder產生影響, 讓encoder更多的關注xx中與ycyc有關的內容. 
聯合輸出: 最終結合右側的神經語言模型和左側attention-based編碼器的輸出, 求下一個詞是yi+1yi+1的概率.

基於注意力模型的編碼器enc31的網路結構圖: 
左側分支: FF是詞嵌入矩陣, x~x~ -> x¯x¯是做了一下平滑處理. 
右側分支: GG是詞嵌入矩陣, 根據當前的y′cyc′, 對x~x~的不同位置投入不同的注意力, 並形成一個加權向量. 
聯合輸出: 此時pp已經攜帶了注意力的資訊, 用pp對平滑後的x¯x¯再做加權, 得到encoder的輸出. 
下面兩幅圖分別是對整體結構和編碼器結構的展開: 
 


感興趣的同學可以結合原文中的公式理解: 
上圖(a)中對應的公式: 
p(yi+1|x,yc;θ)∝exp(Vh+Wenc(x,yc)),yc~=[Eyi−C+1,...,Eyi],h=tanh(Uyc~)
p(yi+1|x,yc;θ)∝exp⁡(Vh+Wenc(x,yc)),yc~=[Eyi−C+1,...,Eyi],h=tanh⁡(Uyc~)

引數是: 
θ=(E,U,V,W),θ=(E,U,V,W), 
E∈RD×VE∈RD×V, 是一個詞嵌入矩陣; 
U∈R(CD)×H,V∈RV×H,W∈RV×HU∈R(CD)×H,V∈RV×H,W∈RV×H, 是權重矩陣. 
上圖(b)中對應的公式:
enc3(x,yc)=pTx¯,p∝exp(x~Pyc~′),x~=[Fx1,...,FxM],yc~′=[Gyi−C+1,...,Gyi],∀i,x¯i=∑q=i−Qi+Qx~i/Q
enc3(x,yc)=pTx¯,p∝exp⁡(x~Pyc~′),x~=[Fx1,...,FxM],yc~′=[Gyi−C+1,...,Gyi],∀i,x¯i=∑q=i−Qi+Qx~i/Q

這裡G∈RD×VG∈RD×V是一個內容的嵌入, P∈RH×(CD)P∈RH×(CD)是一個新的權重矩陣引數, QQ是一個平滑視窗. 
Mini-batch訓練 
這個模型是純資料驅動的, 只要給它{正文: 摘要}訓練集就能完成訓練. 一旦我們已經定義了局部條件模型p(yi+1|x,yc;θ)p(yi+1|x,yc;θ), 我們就能估計引數來最小化摘要集合的負對數似然函式. 假設訓練集由JJ個輸入-摘要對組成(x(1),y(1)),...,(x(J),y(J))(x(1),y(1)),...,(x(J),y(J)). 負對數似然函式作用到摘要的每一個詞, 即 
NLL(θ)=−∑j=1Jlogp(y(j)|x(j);θ)=−∑j=1J∑i=1N−1logp(y(j)i+1|x(j),yc;θ)
NLL(θ)=−∑j=1Jlog⁡p(y(j)|x(j);θ)=−∑j=1J∑i=1N−1log⁡p(yi+1(j)|x(j),yc;θ)

我們通過使用mini-batch和隨機梯度下降最小化NLL.
2.2 Beam Search生成摘要(decode)
我們現在回到生成摘要的問題. 回顧前面, 我們的目標是找到: 
y∗=argmaxy∈Y∑i=0N−1logp(yi+1|x,yc;θ)
y∗=arg⁡maxy∈Y∑i=0N−1log⁡p(yi+1|x,yc;θ)

YY是長度為NN的序列yy組成的集合, 如果字典中的單詞數量是VV的話, 我們要生成的這個摘要就有VNVN種可能性. 因為我們這裡已經做了處理, 只根據前面的CC個已經預測出的單詞ycyc來預測下一個詞yi+1yi+1. 這樣演算法複雜度變成了O(NVC)O(NVC). 但是即使是這樣, 這個演算法也太複雜了. 
使用維特比譯碼需要O(NVC)O(NVC).複雜度獲得精確的解. 然而在實際中VV太大使得問題難解. 一個替代方法是使用貪婪解來近似獲得argmax, 只保證每次前進的一小步是概率最大的. 
在精確解和貪婪解方法之間取一個折中, 就是beam-search束搜尋解碼器(Algorithm1), 它在保持全量字典VV的同時, 在輸出摘要的每一個位置上將自己限制在KK個潛在的假設內. 這種beam-search方法在神經機器翻譯模型NMT也很常用. Beam search演算法展示如下: 
 
引數說明: 
NN: 摘要的長度 
KK: beam的尺寸 
VV: 字典裡所有單詞的數量 
CC: 關注的詞序列的長度
Beam search案例
下面舉一個簡單的例子來說明beam search演算法的執行過程. 在這個例子裡, 摘要長度N=4N=4, beam的大小K=6K=6, 注意力視窗大小C=2C=2, 模型最理想的結果是‘i am a chinese’. Beamsearch的每一次迭代都從字典VV裡找KK個最大的可能. 
 
Step1: 預測前CC個詞的時候視窗溢位的部分需要進行padding操作, 預測第1個詞的時候我們選出KK個詞符. 
 
Step2: 預測第2個詞的時候, 我們選出新的K個詞符, 對應K條備選路徑. 前一階段概率低的路徑和詞符, 被拋棄掉. 
 
Step3: 重複前面的過程. 
 
Step4: 每次beam search不一定能選出不同的K個詞, 但是每次beam search都找到最優的前K個路徑, 路徑可以有重疊. 
 
Step5: 迭代N次, 最終選出可能性最大的一條詞序列路徑 
 
下面是對Beam Search演算法的詳細分析, 對原文的Algorithm 1逐條進行解釋.

Beam Search演算法分析
π[0]π[0]是可以用規定好的起始符號<s>來初始化. 在訓練和生成摘要時, 視窗QQ和CC沿著文字滑動如果超出範圍, 用起始符號<s>做padding.
如果模型是abstraction-based, 輸出yy的備選集合是整個字典, 如果希望摘要的單詞全部從原文中抽取, 那麼詞典由輸入正文xx的所有單詞構成.
我們會設定一個最大輸出長度NN, 演算法會進行NN輪迭代. 
現已有KK個假設, 每一個假設都對應一條路徑; 對每一個假設, 我們從字典SS(有VV個單詞)中選出KK個單詞作為備選.
在字典中尋找, 搜尋其他單詞, 如果計算的到的state值比當前集合中的任意一個大, 就把它保留下來.
當每一個假設都遍歷完整個字典SS, 就會產生K×KK×K條路徑, 我們在這些路徑中選擇概率最大的KK個路徑作為下一次迭代的基礎.(每一條路徑都保留了之前i−1i−1個節點對應的單詞)
當NN次迭代進行完後, 我們只剩下了KK條路徑, 最後在從這其中選出1條概率最大的即可.
路徑所經歷的所有節點即為摘要的單詞. 如果這中間遇到了停止符<e>, 摘要就是從<s>到<e>, 如果沒有<e>出現, 摘要的最大長度就是NN.
Beam Search的運算複雜度從O(NVC)O(NVC)變成了O(KNV)O(KNV), 因為V>>NV>>N和KK, 加速效果非常顯著. 束搜尋依據已經計算好的路徑以及當前的VV個備選值, 計算出最優的KK的值. 最新的KK個最優值都保留著相應路徑上之前的所有的節點.

3 TensorFlow程式實戰
NAM模型的程式最早是由facebook開源的torch版本的程式. 最近谷歌開源了TensorFlow版本的摘要生成程式textsum, Github上的專案. textsum的核心模型就是基於注意力的seq2seq(sequence-to-sequence)模型, textsum使用了LSTM和深度雙向RNN. 
Github上的textsum首頁給出了此專案在Bazel環境下的執行方式. 如果你不想通過Bazel執行, 你可以直接在seq2seq_attention.py中設定執行引數. 設定完引數後, 直接執行python seq2seq_attention.py即可. 引數設定如下圖所示: 
 
除了上述專案執行時所需的必要引數, 模型引數也在seq2seq_attention.py中設定, 如下圖所示, 包括學習率, 最小學習率(學習率會衰減但不會低於最小學習率), batch size, train模式encoder的RNN層數, 輸入正文詞彙數上限, 輸出摘要詞彙數上限, 最小長度限制, 隱層節點數, word embedding維度, 梯度擷取比例, 每一個batch隨機分類取樣的數量. 
 
git專案textsum給的toy資料集太小, vocab也幾乎不可用(一些常見的單詞都沒有覆蓋到). 如果希望獲得好的效果, 需要自己整理可用的資料集. 
主要檔案說明: 
- seq2seq_attention.py: 主程式, 選擇程式的執行模式, 設定引數, 建立模型, 啟動tensorflow 
- seq2seq_attention_model.py: 建立attention-based seq2seq model, 包括演算法的encoder, decoder和attention模組, 都在Seq2SeqAttentionModel中完成. 
- seq2seq_attention_decode.py: 讀取資料, 呼叫beam_search解碼 
beam_search.py: beam search演算法的核心程式

textsum程式解析
Google開源的textsum專案的具體演算法是基於Hinton 2014年的Grammar as a Foreign Language這篇論文, 下面給出textsum工程中attention-based seq2seq模型的整體結構圖, 圖中所使用的名字與程式中的變數名一致, Seq2SeqAttentionModel是一個類, 定義在seq2seq_attention_model.py中; attention_decoder是一個函式, 定義在/tensorflow/contrib/legacy_seq2seq/python/ops/seq2seq.py中. 
為了方便理解, 簡單解釋一下圖中出現的符號, 
 
第一個符號表示從x1,x2到y的線性變換, 紅色變數是訓練過程要學習出來的. 
 
attention機制比較複雜也比較重要, 我們對這部分細化一下來看. attention decoder結構圖如下: 
 
下圖是對attention模組的細化: 
 
符號說明: 


為什麼attention這個模組會起到效果呢? 因為attention模組會根據decoder當前時刻的LSTM單元的狀態, 來調整對attention_states(encoder輸出)的注意力. Attention_states不同位置獲得的關注不一樣. 這樣我們就更大程度地, 關注了原文中, 對當前輸出更為有用的資訊, 輸出結果也就更準確了. Attention模組輸出結果和decoder模組原本的輸出聯合起來, 得到最終的輸出結果.

相關連結:
演算法原理部分的論文: 
https://arxiv.org/abs/1509.00685
textsum開源程式連結: 
https://github.com/tensorflow/models/tree/master/textsum
textsum中使用的演算法原理論文: 
https://arxiv.org/abs/1412.7449