參考書
《TensorFlow:實戰Google深度學習框架》(第2版)
我只能說這本書太爛了,看完這本書中關於自然語言處理的內容,代碼全部敲了一遍,感覺學的很絕望,代碼也運行不了。
具體原因,我也寫過一篇博客diss過這本書。可是既然學了,就要好好學呀。為了搞懂自然語言處理,我毅然決然的學習了網上的各位小伙伴的博客。這里是我學習的簡要過程,和代碼,以及運行結果。大家共勉。
參考鏈接:
目錄
學習過程:
0. 數據准備
我用的數據就是參考鏈接里面的數據。即一個TED 演講的中英字幕。
下載地址:
https://wit3.fbk.eu/mt.php?release=2015-01
1. 數據切片
簡單來說,就是,我們得到的文件里面都是自然語言,“今天天氣很好。”這樣的句子。我們首先要做的就是要將這些句子里的每一個字以及標點符號,用空格隔開。所以第一步就是利用工具進行文本切片。(具體方法看鏈接,這里不贅述)
我們要進行處理的文件是下面兩個。
但是在這兩個文件里面除了演講內容中英文之外,還有關於演講主題的一些信息,如下圖。
我用正則表達式的方法(現百度現用)去除了這些介紹部分的文字。英文和中文只需要改變名字和路徑就行了,下面貼代碼:
運行這個程序就能得到兩個文件,分別是去除了介紹文字部分的英文和中文翻譯:
內容如下(我不知道為什么截圖之后圖片變窄了,很難受):
接下來我們需要有那種每個元素都是由空格所分開的(包括所有符號)。在這里,我選取的中文和英文的分詞工具都是stanfordcorenlp,相關知識請參考這篇博客。
很絕望的是,我安裝過程中Python還報錯了:
所以我借鑒知乎上面的回答解決了這個問題。
為什么要選用斯坦福的工具???
明明可以用nltk來對英文進行分詞,用jieba對中文進行分詞,可是當我搜到斯坦福這個自然語言處理工具的時候,我想了想,天啦,這個名字也太酷炫了吧,一看就很復雜啊,中英文用同一個庫進行分詞應該要比較好吧,我要搞定它。然后按照網上的教程,真的很容易的就安裝了,然后很容易的就是實現對英文的分詞了。可是!真的有毒吧!對中文進行分詞的時候,我特么輸出的是['', '', '', '']這種空值,無論怎么解決都沒有辦法,我真的要崩潰了。還好我看到了這篇文章。嗯。。。寫的非常不錯,竟然有我各種百度都沒找到的解決辦法,簡直是太開心了好嗎!可是!
里面寫的 corenlp.py 到底在哪兒阿喂!
我哭了真的!
你們看,里面明明就只有一個叫corenlp.sh的文件好嗎?打開之后里面也並不是知乎里面所說的內容啊!
在這個令人絕望的關頭!我靈光一閃。。。.py!.py!莫不是在python庫文件里面。。。然后我就立馬去python3目錄下找。
我是真的快樂,真的。。。特別是竟然出現了兩個corenlp.py(雖然其中一個是nltk的啦,nltk是可以調用斯坦福的模塊的,所以如果你百度的話,是可以查到如何用nlkt調用Stanfordcorenlp進行中文分詞啊,語義解析啊等等的。我瞄了一眼一看就覺得不適合我哈哈!)
然后就順利的根據上面知乎里的解決辦法,替換了corenlp.py文件里面的某些關鍵字(具體內容點開上面的鏈接就知道啦)。
下面是我寫的中英文分詞的demo。
運行之后得到:
我現在是真的快樂!真的!真的是萬事開頭難,中間難,結尾難!
什么?你竟然不相信我會用jieba和nltk。
運行之后得到:
要記得在使用nltk工具包的時候,要下載對應的語言包,不然就會報錯。也可以預先下載好所有的語言包,可是速度也太慢了吧,我還是用啥下啥好了。真的是巨慢。。。(下載完所有的好像得3、4個G)
那我給出大佬的解決辦法,大家自行下載呀。(這里是punkt庫)
如果想要所有的庫,可以去官網下載,離線解壓就完事兒了。
有了demo之后,我們就可以對中英文數據進行切片了:
處理之后分別得到兩個文件:
里面是已經處理好的句子,各個句子進行了切片處理,每個元素用空格隔開。
2. 數據集的預處理
為了將文本轉化為模型可以讀入的單詞序列,需要將這4000個中文詞匯,10000個英文詞匯分別映射到0~9999之間的整數編號。
我們首先按照詞頻順序確定詞匯表,然后將詞匯表保存到兩個獨立的vocab的文件中。
處理之后分別得到兩個文件:
每個文件的內容如下:
在確定了詞匯表之后,再講訓練文件、測試文件等都根據詞匯文件轉化為單詞編號。每個單詞的編號就是它在詞匯文件的行號。
在這里我不得不說一句!
我是真的看不懂這句代碼
words = ["<sos>"] + line.strip().split() + ["<eos>"]
這句代碼是我寫的,但是在參考書中,還是我參考的別人的博客中。他們的都是下面這句
words = line.strip().split() + ["<eos>"]
書就不拍照了,嫌麻煩,別人的博客,我可以貼圖為證:
我是真的佛了,書里面明明說的清清楚楚,我們講道理
在后面處理機器翻譯數據時,除了"<eos>",還需要將"<unk>"和句子起始符"<sos>"加入詞匯表,並從詞匯表中刪除低頻詞匯。
我真的是找了全文,都沒有看到哪里在每個句子的前面加了“<eos>”的,我才幡然醒悟,不就是在這里加入句子起始符嗎?為啥句子結束符都加了,憑什么不加句子起始符啊。。。我真的是無語了,無話可說,搞不懂這些人的代碼是怎么跑的通的。。。
對不起!上面寫的都是放屁!
我希望所有跟我這樣想的人,都要注意!這里只加<eos>是有道理的!,在下面batching的時候,會說為什么數據預處理的時候只在每個句子的后面加<eos>!
運行之后,就把原來的中英文文件,變成了兩個中英文數字。
3. 數據的batching方法
在PTB的數據中,句子之間有上下文關聯,因此可以直接將句子連接起來成為一個大的段落。
而在機器翻譯的訓練樣本中,每個句子對通常都是作為獨立的數據來訓練的。由於每個句子的長短不一致,因此在將這些句子放到同一個batch時,需要將較短的句子補齊到與同 batch 內最長句子相同的長度。用於填充長度而填入的位置叫做填充(padding)。在TensorFlow中,tf.data.Dataset 的 padded_batch 函數可以解決這個問題。
假設一個數據集中有4句話,分別是 ”A1A2A3A4”,“B1B2”,“C1C2C3C4C5C6C7”和“D1”,將它們加入必要的填充並組成大小為2 的batch后,得到的batch如下圖所示:
循環神經網絡在讀取數據時會將填充位置的內容與其他內容一樣納入計算,因此為了不讓填充影響訓練,可能會影響訓練結果和loss的計算,所以需要以下兩個解決對策:
第一,循環神經網絡在讀取填充時,應當跳過這一位置的計算。以編碼器為例,如果編碼器在讀取填充時,像正常輸入一樣處理填充輸入,那么在讀取"B1B200”之后產生的最后一位隱藏序列就和讀取“B1B2”之后的隱藏狀態不同,會產生錯誤的結果。通俗一點來說就是通過編碼器預測,輸入原始數據+padding數據產生的結果變了。
但是TensorFlow提供了 tf.nn.dynamic_rnn函數來很方便的實現這一功能,解決這個問題。dynamic_rnn 對每一個batch的數據讀取兩個輸入。
①輸入數據的內容(維度為[batch_size, time])
②輸入數據的長度(維度為[time])
對於輸入batch里的每一條數據,在讀取了相應長度的內容后,dynamic_rnn就跳過后面的輸入,直接把前一步的計算結果復制到后面的時刻。這樣可以保證padding是否存在不影響模型效果。通俗來說就是用一個句子的長度也就是time來把控這一點。
並且使用dynamic_rnn時每個batch的最大序列長度不需要相同。例如上面的例子,batch大小為2,第一個batch的維度是2x4,而第二個batch的維度是2x7。在訓練中dynamic_rnn會根據每個batch的最大長度動態展開到需要的層數,其實就是對每個batch本身的最大長度沒有關系,函數會自動動態(dynamic)調整。
第二,在設計損失函數時需要特別將填充位置的損失權重設置為 0 ,這樣在填充位置產生的預測不會影響梯度的計算。下面的代碼使用
tf.data.Dataset.padded_batch來進行填充和 batching,並記錄每個句子的序列長度以用作dynamic_rnn的輸入。與上篇文章PTB的例子不同,這里沒有將所有的數據讀入內存,而是使用Dataset從磁盤動態讀取數據。
沒錯,上面的我是抄的,抄的(別人抄書)的,為什么?
當然是因為我懶啊。
你們看!這里就解釋了上面所說的:為什么在數據預處理部分,只給每個句子的末尾加入<eos>!
# 解碼器需要兩種格式的目標句子:
# 1.解碼器的輸入(trg_input),形式如同"<sos> X Y Z"
# 2.解碼器的目標輸出(trg_label),形式如同"X Y Z <eos>"
# 上面從文件中讀到的目標句子是"X Y Z <eos>"的形式,我們需要從中生成"<sos> X Y Z"
# 形式並加入到Dataset中。
至於為什么?別問!問我就是迷茫!反正我到現在已經被編碼器,解碼器輸入,解碼器輸出給整糊塗了。有沒有懂的大佬給解釋一下!評論區等你!
我懂了!話不多說,一張圖你就能懂!(知乎上的圖,侵刪)
你們看右邊的解碼器!再配上我下面的這個圖(這個是書上的圖,但我從別人的博客上拔下來的哈哈)
解碼器輸入的序列:<sos>, x, y, z
解碼器輸出的序列:x, y, z, <eos>
好,這個問題不用我再多說了吧,還是不懂的朋友,評論區見!
4. Seq2Seq模型的代碼實現
4.1 模型訓練
LSTM 作為循環神經網絡的主體,並在 Softmax 層和詞向量層之間共享參數,增加如下:
增加了一個循環神經網絡作為編碼器(如前面示意圖)
使用 Dataset 動態讀取數據,而不是直接將所有數據讀入內存(這個就是Dataset輸入數據的特點)
每個 batch 完全獨立,不需要在batch之間傳遞狀態(因為不是一個文件整條句子,每個句子之間沒有傳遞關系)
每訓練200步便將模型參數保存到一個 checkpoint 中,以后用於測試。
我一直以來都有一個疑問:詞向量究竟是啥意思,上述代碼中的這段代碼是啥意思?
# 為源語言和目標語言分別定義詞向量
self.src_embedding = tf.get_variable("src_emb", [SRC_VOCAB_SIZE, HIDDEN_SIZE])
self.trg_embedding = tf.get_variable("trg_emb", [TRG_VOCAB_SIZE, HIDDEN_SIZE])
參考:這個鏈接
下面是我對上面問題的理解啊,也不知道對不對,大家多擔待:
- 首先,詞向量有兩種,一種是One-Hot Encoder,也就是以前常用的,當然現在也有用。
- 第二種,就是稠密向量,因為第一種每個詞向量也太大了,如果詞匯表有10000的話,那么對於某個詞來說,它的詞向量就是,一個1x10000的矩陣,其中的一個是1,其他9999都是0。
- 那么這第二種要怎么得到呢?(我的理解啊)就是用第一種方式轉化得到的。第一種方法得到的詞向量作為輸入,然后經過一個10000 x hidden_layer(隱藏層大小)的矩陣,矩陣乘法得到1 x hidden_layer 的稠密矩陣,這個矩陣就是我所說的第二種詞向量。
- 上述是我的理解,也不知道對不對。有大佬指點一下,我們就評論區見唄。
偷的別人博客中的圖,侵刪。。。
然后呢,我們再回到上面疑問的代碼中來。
# 為源語言和目標語言分別定義詞向量
self.src_embedding = tf.get_variable("src_emb", [SRC_VOCAB_SIZE, HIDDEN_SIZE])
self.trg_embedding = tf.get_variable("trg_emb", [TRG_VOCAB_SIZE, HIDDEN_SIZE])
這部分代碼定義的就是第一種詞向量要乘的矩陣,shape是[詞匯表大小,隱藏層]。
# 將輸入和輸出單詞編號轉為詞向量
src_emb = tf.nn.embedding_lookup(self.src_embedding, src_input)
trg_emb = tf.nn.embedding_lookup(self.trg_embedding, trg_input)
這一部分代碼是計算第二種詞向量的過程,雖然用的函數時tf.nn.embedding_lookup,實際上也就是矩陣乘法的意思。(附圖,侵刪)
右邊的就是我們所要求的的第二種詞向量啦。(這里隱藏層是3,我們設置的隱藏層是1024,為什么?別問!問就是不知道!)
第二種詞向量,也就是稠密詞向量,有什么用呢!
如果隱藏層是二維,或者三維的,我們不就能繪制出每個詞向量的圖了嗎!
比如說下面這個二維圖:(網上拿的圖,侵刪)
這不就能反映詞與詞之間的關系了!越接近的詞,詞義也越接近!
另外,在forward函數中定義模型的前向計算圖中的代碼好多根本沒看懂。跑就完事兒了,構造編碼器和解碼器的過程我是真的蒙了。以后有新的想法,弄懂了之后再回來補充好了。
運行出來的結果如下:
一萬年過后。。。(沒錯,中途我還換了個電腦跑程序,跑了大概兩天吧。。。)(這個截圖我也是醉了的。。。)
得到的模型如下:
我保留了最后一個保存的模型(其他步保存的模型也有,但是太大太占位置了,就給刪了,主要也就是用最后這個)
4.2 解碼或推理程序
上面的程序完成了機器翻譯模型的訓練,並將訓練好的模型保存在checkpoint中。
下面是講解怎樣從checkpoint中讀取模型並對一個新的句子進行翻譯。對新輸入的句子進行翻譯的過程也稱為解碼或推理。
在訓練的時候解碼器是可以從輸入讀取到完整的目標訓練句子。
而在解碼或推理的過程中模型只能看到輸入句子,卻看不到目標句子
具體過程:和圖中描述的一樣,解碼器在第一步讀取<sos> 符,預測目標句子的第一個單詞,然后需要將這個預測的單詞復制到第二步作為輸入,再預測第二個單詞,直到預測的單詞為<eos>為止 。 這個過程需要使用一個循環結構來實現 。在TensorFlow 中,循環結構是由 tf.while_loop 來實現的 。
OK!終於大功告成。。。讓我們來運行一下吧!
我哭了。真的。辛辛苦苦一頓操作,怎么就報錯了?我研讀了一下,發現上面說讀取的模型與我們定義的變量名不一致,報錯的變量名是
nmt_model/decoder/rnn/multi_rnn_cell/cell_0/basic_lstm_cell/bias
我去網上找了找解決辦法。。。啥也沒找到。算了,先想辦法看一下模型里面的變量是什么。
運行后:
為什么!!!解碼器和編碼器的參數前面都少了一個nmt_model。。。(有大佬知道嗎,求評論區解釋一下,我現在還沒搞懂。。。)
你們看其他的變量就正常的有nmt_model名。。。
我也是醉了。
然后我就去找有沒有修改已訓練模型變量名字的方法。。。
找了好久總算是讓我找到了!
參考:干貨!如何修改在TensorFlow框架下訓練保存的模型參數名稱
所以我們只需要把那些不知道為什么沒有"nmt_model/"前綴的tensor_name給他們加上這個前綴就OK了,下面貼代碼,我做了一定的修改
運行之后:
完事兒!開心!
我們再看一眼修改后模型的變量名是不是真的改了吧!(跟上面的代碼一樣,該個文件名,運行)
大功告成!!!
好,我們現在運行原來的翻譯程序吧!!!(改成修改后的模型)
CHECKPOINT_PATH = "./new_seq2seq_ckpt"
歐耶!!!翻譯成功!
5 Attention機制(注意力機制)
為什么需要Attention機制(參考鏈接)
最基本的seq2seq模型包含一個encoder和一個decoder,通常的做法是將一個輸入的句子編碼成一個固定大小的state,然后作為decoder的初始狀態(當然也可以作為每一時刻的輸入),但這樣的一個狀態對於decoder中的所有時刻都是一樣的。
attention即為注意力,人腦在對於的不同部分的注意力是不同的。需要attention的原因是非常直觀的,比如,我們期末考試的時候,我們需要老師划重點,划重點的目的就是為了盡量將我們的attention放在這部分的內容上,以期用最少的付出獲取盡可能高的分數;再比如我們到一個新的班級,吸引我們attention的是不是顏值比較高的人?普通的模型可以看成所有部分的attention都是一樣的,而這里的attention-based model對於不同的部分,重要的程度則不同。
其他細節就不贅述了,大家想要深入了解原理的話看參考鏈接。這里我主要弄代碼部分。
加入attention機制后模型訓練代碼(部分內容做了修改):
1. 需要修改的部分主要是把原來編碼器的多層rnn結構變成了一個雙向的rnn:
# 前向
self.enc_cell_fw = tf.nn.rnn_cell.LSTMCell(HIDDEN_SIZE)
# 反向
self.enc_cell_bw = tf.nn.rnn_cell.LSTMCell(HIDDEN_SIZE)
2. forward函數中的"encoder"和"decoder"部分都做了些許的修改。這里就不貼代碼了,大家一看便知。
跑模型實在是太耗時了,我掛了一夜就跑了2800步,旨在學習,就不像上面那樣跑兩天兩夜了。
運行結果:
我們就將就着2800步的模型用起來了。
加入attention機制后模型解碼代碼(部分內容做了修改,這里就不貼哪里修改了,跟訓練模型需要修改的地方相似):
在這里我們要注意,解碼的時候初始狀態並不是之前編碼器最后的隱藏狀態,而是什么都沒有的初始狀態:
init_loop_var = (attention_cell.zero_state(batch_size=1, dtype=tf.float32), init_array, 0)
OK!來吧,運行吧!!!
???我絕望了。。。又跟上面的一樣???我酸了。。。
又是變量名的原因??
行吧,這次我就不用上面的解決辦法了,不改模型了,我要修改解碼代碼中的變量名。。。
1. 首先去掉主函數的模型名,然后下面的代碼縮進。這樣所有的變量名前面都沒有nmt_model了。
2. 然后修改模型類里面初始化函數里面的變量名,給他們加上nmt_model
3. 這樣就滿足了模型中,有的變量名有nmt_model前綴,有的沒有的問題了,而不是直接修改模型里面的。。。(修改模型后meta文件變得特別大,不知道為什么。總之就是絕望。。。)運行!!!
成功!歐耶!完事兒!
這次把句號翻譯出來了,有進步!
6. 總結
在Attention機制這部分,原書里面就給了訓練模型需要修改的部分,也並沒有拿出來說明,哪里修改了,怎么修改的。最可怕的是原書中沒有給出跑出模型后解碼的部分,更別說學習書里面除了語言模型之外的那些模型了,我簡直就是雙眼一抹黑,口吐芬芳。輕嘆一口氣,學習果然還是得靠自己下功夫啊。十分感謝這篇文章給出的完整代碼,給了我從頭到尾體驗Seq2Seq的機會。希望大家在學習過程中也依然保有耐心,永不言棄。
如果有讀者,真的從頭到尾,看完我的文章,真的是非常不容易,祝賀你,你不僅努力勤奮,還很堅強,脾氣一定很好吧哈哈哈。如果有任何問題,歡迎跟我一起探討。
最后附上整個代碼的github地址,歡迎大家Watch,Star,Fork~
https://github.com/TinyHandsome/BookStudy/tree/master/book2/Seq2SeqLearning






































