今天我們就正式開始模型設計,我把模型設計為一個類,即MySeq2Seq類,初始化為各種可能參數,然後我把encoder和decoder作為該類的兩個函數。

在正式設計之前我們先討論一下encoder-decoder模型(後面會繼續討論)。宏觀的敘述已經有很多了,但是具體的細節的討論似乎不是很多,這裡我們以經典RNN結構為例,主要關注encoder-decoder模型的細節。

假如我們有一句話,這裡我們將其表示為用數字表示的序列(文本經過字典映射),如[1, 2, 3, 4, 5]我們都知道RNN會對每個數字進行處理,生成一個對應的字元,balabala。而今天我們關注這個處理的細節。

想知道處理的細節就要關注RNN內部

上圖是最簡單的RNN模型,x為輸入向量,U為權重矩陣,s為隱藏層的值,W也為權重矩陣,V為權重矩陣,O為輸出向量。

計算公式為:

g,f為激活函數

反覆迭代

上面就是展開的RNN計算過程的數學表達式。

按照公式來說,我直接輸入一個數字進入RNN理論上也是可以的(後面可能會試一下)。大不了是1維向量嘛,但是一般我們會用一個向量來表示一個數字,或者說是一個字元(詞)。為什麼呢?因為單獨的一個數字無法表示該字元(詞)的特徵以及與其他字元(詞)的聯繫。換句話說,我們需要構造一種能夠存儲字元(詞)的若干個特徵的表達方式(雖然這個特徵具體是什麼我們也不知道,但這樣做就行了)。這時就需要詞嵌入(word embedding)了,具體詞嵌入這裡就不講了,默認大家都會。我們可以考慮使用訓練好的word2vec或是其他模型來將一個字母或是單詞或是漢字或是詞表示成長度固定的向量的形式,不過也可以直接隨機化一個矩陣,按照字元(詞)在先前構造的詞表中的索引號選取矩陣中對應的行作為向量化結果。所以這個矩陣的長(行數)應該和詞表的長度相同,寬(詞向量維度)可以自由設定。理論上來說,這個寬決定了詞向量存儲字元(詞)的特徵的能力,就像一個碗,碗越大,能盛的面就越多(甚至面會越長越寬)。

所以,上面說了一大堆,其實就一句話,RNN每次接收的是一個向量,表示一個字元。然後就向量,矩陣相互作用,相愛相殺,得到隱藏層,輸入層。但是,這裡有一個我一直很不明白的問題,在tensorflow中,RNN的隱藏層和輸入層的結果是一樣的???雖然tensorflow的設計確實有可能這麼無聊,但是我還是選擇思考一下為什麼?下面是我思考的結果:

其實不管RNN,CNN,LSTM,還是GRU,它們本質上是什麼?神經網路?廢話,神經網路用來幹嘛的?抽取特徵,編碼啊。從深度神經網路的定義來看,它們都是屬於隱藏層的部分,它們最大的作用就是將神經網路的輸入進行編碼,得到各種size的矩陣或者說是張量,我們可以將它們的輸出理解為充分表達了輸入數據的特徵的矩陣/張量。但是我們回過頭來,神經網路輸入深度學習,深度學習屬於機器學習,機器學習幹嘛的?分類/回歸/標記,所以最終我們還是要將文本生成歸到分類,回歸等任務裡面。所以到了這裡,大家應該能夠明白我想說什麼了吧。文本生成本質上應該是一個分類任務,這些類別就是你構建的詞表。所以在你完成特徵抽取後,你還需要一個全連接層幫你實現分類的任務,這個全連接層會將RNN的結果通過矩陣作用變為一個長度和詞表長度一樣的向量,這個向量的每個元素的值表示詞表上對應的詞是這次輸出的結果的可能性。所以,從真正意義上來說,經過V矩陣作用的輸出並不屬於RNN

但是,這個和tensorflow中隱藏層與輸出層是一樣的有什麼關係呢?在回答這個問題之前我先敘述一下encoder-decoder的工作過程,而在談encoder-decoder基本原理之前打算敘述一下charRNN,為什麼呢?因為RNN的圖

會給大家誤導,我需要理清一下概念。上面的RNN的圖更適合在講charRNN的時候列出,如果我們要說encoder-decoder需要的是下面這張圖:

charRNN的工作原理其實很簡單,就是我上面已經說過的,將RNN作為一個特徵提取器,對字元級別(字母,漢字)的輸入進行特徵提取,得到特徵矩陣/張量,然後同樣的通過一個全連接層轉化成分類任務對輸出進行預測,就這樣,輸入一個,輸出一個。

大家可以看出來這兩張圖的區別,decoder部分的RNN和unfold的RNN是一樣的,encoder部分的RNN則有區別,區別在於encoder部分的RNN是沒有輸出O的。也就是說encoder它做的事情就是接收一個詞向量,產生一個隱藏層向量,這樣接收完一句話後,得到的隱藏層的向量理論上是包含了一句話的信息,所以我們可以將這個最終的隱藏層向量看做是對整句話的編碼,但是當句子長了之後,這個向量還能記得這麼多嗎?如果不能的話,我可不可以將每次得到隱藏層向量拼接到一起,這樣會不會更可靠一點,我們先不考慮理論上正確與否,我們如果想這樣做,就需要獲取所有步中隱層的信息,但是在tensorflow中,將一句話輸入RNN後得到的隱藏向量只是最後一步的隱層向量,如果我們想要獲取前面的隱層向量,就可以直接使用RNN的輸出結果作為所有的隱層向量。這裡需要強調一下,這裡僅僅是指tensorflow中的RNN,而LSTM和GRU由於其本身的特性,隱層和輸出是不一樣的,需要區別對待。現在已經回答了先前提出的關於TensorFlow中RNN輸入和隱層設計的問題了(當然原因可能還有很多,這裡是我自己的看法),下面我們繼續說一下encoder-decoder模型。

我在上面已經說明charRNN和encoder-decoder的區別,一句話概括,兩者的區別在於編碼對象的粒度不同,charRNN是利用單個RNN對字元進行編碼,encoder-decoder則是利用單個RNN(後面改進的encoder-decoder模型也有多層RNN編碼的)對句子進行編碼。但似乎沒有那麼簡單,因為encoder-decoder模型還有一個decoder,decoder顧名思義,解碼器。它在encoder-decoder模型中一個很重要的作用就是輸出結果。因為解碼本身也是一種編碼,所以decoder無非是利用encoder傳來的隱層向量(將其作為decoder的初始狀態)和decoder的輸入來繼續編碼和encoder相比,decoder也有embedding層,RNN層,只不過多了一個用於輸出詞概率的全連接層。所以encoder-decoder模型可以看做是兩個編碼器進行聯合訓練得到的語言模型。

現在encoder-decoder的工作原理基本上已經講完了,但是如何訓練一個encoder-decoder模型呢?這裡就涉及到更細的問題了。

根據我們的任務,由摘要生成題目,我們考慮一條數據(x, y),x是摘要文本,y是題目文本。

我們將x輸入Encoder中,得到一個隱層向量c,然後將c作為decoder中RNN的初始隱層狀態,這時我們會向decoder中輸入<EOS>起始符號作為生成句子的起點,這個起始符號經過embedding層,RNN,得到decoder的編碼表示,然後將表示向量通過softmax,得到生成的詞的概率向量,下一步是使用我們期望生成的句子(訓練pair中的y)的詞按照同樣的方式處理,每次處理都會產生一個詞的概率向量,我們希望的是,產生的詞的概率向量接近下一步實際輸入的詞的one-hot表示,所以我們會計算生成的詞的概率向量構成的張量與我們期望的詞,也就是整個句子構成的one-hot編碼張量的損失(交叉熵之類的)。然後利用優化演算法減小損失。

舉個栗子:

我們希望Decoder能夠生成這樣一句話「作者有很多兒子」

當decoder接收eos作為起始詞後,經過embedding,rnn,softmax後生成了一個概率向量,這個概率向量表達了詞表中每個詞作為生成詞的概率,因為decoder沒有經過訓練,所以概率最大詞很可能不是「作」,而是其他詞,我們希望的狀況就是這個概率向量就是「作」的one-hot表示,那麼實際生成的概率向量和實際的概率向量之間就存在損失(誤差)。同樣的,我們下一步會把「作」輸入到decoder中,希望經過一系列處理後decoder能夠生成「者」的one-hot,而實際生成的概率向量也是存在偏差,就這樣我們把整個句子輸入到decoder中,就可以得到整個句子的偏差,我們下面就通過優化來減小這個偏差。

模型訓練好之後就是預測,那麼會有如下幾個問題:

1.每一次生成詞的選取(選擇概率最大的,還是按照束搜索方式?)

2.如何去判斷一個模型生成內容的好壞?

這個問題留給大家思考

說到這裡,相信大家對整個流程都比較清楚了,下面就是各部分的具體設計了。


推薦閱讀:
相關文章