# 十一、訓練 Seq2Seq 模型
在上一章中,我們討論了文檔分類以及文檔分類的一種特殊情況,稱為**情感分類**。 這樣做時,我們不得不談論很多關于向量化的知識。
在本章中,我們將繼續談論解決 NLP 問題,但是除了分類之外,我們將生成新的單詞序列。
我們將在本章介紹以下主題:
* 序列到序列模型
* 機器翻譯
# 序列到序列模型
到目前為止,我們所研究的網絡已經做了一些真正令人驚奇的事情。 但是它們都有一個很大的局限性:它們只能應用于輸出具有固定且眾所周知的大小的問題。
序列到序列模型能夠將輸入序列映射到具有可變長度的輸出序列。
您可能還會看到術語序列到序列,甚至 **Seq2Seq**。 這些都是序列到序列模型的術語。
當使用序列到序列模型時,我們將引入一個序列并交換出一個序列。 這些序列的長度不必相同。 序列到序列模型使我們能夠學習輸入序列和輸出序列之間的映射。
序列到序列模型可能在許多應用中有用,我們接下來將討論這些應用。
# 序列到序列模型的應用
序列到序列模型具有許多實際應用。
也許最實際的應用是**機器翻譯**。 我們可以使用機器翻譯將一種語言的短語作為輸入,并輸出另一種語言的短語。 機器翻譯是我們越來越依賴的一項重要服務。 得益于計算機視覺和機器翻譯的進步,我們可以聽不懂的語言,或者用不懂的語言查看標志,并且幾乎可以立即在智能手機上獲得不錯的翻譯。 序列到序列的網絡確實使我們非常接近道格拉斯·亞當(Douglas Adam)想象的《銀河系漫游指南》中的通天魚。
問答也可以全部或部分通過序列到序列模型來完成,在這里我們可以將問題想象為輸入序列,將答案想象為輸出序列。 回答問題最普遍的應用是聊天。 如果您通過呼叫中心為企業提供支持,則每天會有成千上萬甚至數百萬個問題/答案對通過電話傳遞。 對于序列到序列聊天機器人來說,這是完美的訓練。
我們可以利用這種問答方式的多種細微形式。 每天,我收到大約 34 億封電子郵件。 其中,我可能只需要閱讀 20-30(這是一個分類任務)。 但是,我對這些電子郵件的回復很少新穎。 我幾乎可以肯定地創建一個序列到序列的網絡,該網絡可以為我寫電子郵件,或者至少起草回復。 我認為我們已經開始看到這種行為已經內置在我們最喜歡的電子郵件程序中,并且肯定會出現更加全自動的響應。
序列到序列網絡的另一個重要用途是自動文本摘要。 想象一下一組研究論文或大量期刊文章。 所有這些論文可能都有摘要。 這只是另一個翻譯問題。 給定一些論文,我們可以使用序列到序列網絡生成摘要。 網絡可以學習以這種方式總結文檔。
在本章的后面,我們將實現一個序列到序列的網絡來進行機器翻譯。 不過,在進行此操作之前,讓我們了解一下這種網絡架構是如何工作的。
# 序列到序列模型架構
理解序列到序列模型架構的關鍵是要理解該架構是為了允許輸入序列的長度與輸出序列的長度而變化的。 然后可以使用整個輸入序列來預測長度可變的輸出序列。
為此,網絡被分為兩個獨立的部分,每個部分都包含一個或多個 LSTM 層,這些層負責一半的任務。 如果您想對其操作進行復習,我們在第 9 章“從頭開始訓練 RNN”中討論了 LSTM。 我們將在以下各節中了解這兩個部分。
# 編碼器和解碼器
序列到序列模型由兩個單獨的組件組成,一個編碼器和一個解碼器:
* **編碼器**:模型的編碼器部分采用輸入序列,并返回輸出和網絡的內部狀態。 我們并不在乎輸出。 我們只想保留編碼器的狀態,即輸入序列的內存。
* **解碼器**:然后,模型的解碼器部分將來自編碼器的狀態(稱為**上下文**或**條件**)作為輸入。 然后,根據前一個時間步長的輸出,可以預測每個時間步長的目標序列。
然后,編碼器和解碼器如下圖所示一起工作,獲取輸入序列并生成輸出序列。 如您所見,我們使用特殊字符表示序列的開始和結束。
我們知道,一旦序列字符的結尾(我稱之為`<EOS>`)結束,就停止生成輸出:

盡管此示例涵蓋了機器翻譯,但是序列到序列學習的其他應用卻以相同的方式工作。
# 字符與文本
可以在字符級別或單詞級別建立序列到序列模型。 單詞級序列到序列模型將單詞作為輸入的原子單位,而字符級模型將字符作為輸入的原子單位。
那么,您應該使用哪個呢? 通常,最好的結果是從單詞級模型中獲得的。 就是說,預測序列中最可能出現的下一個單詞需要`softmax`層與問題的詞匯量一樣寬。 這導致了非常廣泛的,高度尺寸的問題。
字符級模型要小得多。 字母表中有 26 個字母,但大約有 171,000 個英文單詞是常用的。
對于本章中提出的問題,我將使用字符級模型,因為我重視您的 AWS 預算。 轉換為單詞非常簡單,其中大部分復雜性都在數據準備中,這是留給讀者的練習。
# 監督強迫
如上圖所示,當預測序列`y[t(n)]`某個位置的輸出時,我們使用`y[t(n-1)]`作為 LSTM 的輸入。 然后,我們使用此時間步驟的輸出來預測`y[t(n+1)]`。
訓練中這樣做的問題是,如果`y[t(n-1)]`錯誤,則`y[t(n)]`將更加錯誤。 錯誤不斷增加的鏈條會使事情變得非常緩慢。
解決該問題的一個顯而易見的解決方案是將每個時間步長的每個序列預測替換為該時間步長的實際正確序列。 因此,我們將使用訓練集中的實際值,而不是對`y[t(n-1)]`使用 LSTM 預測。
通過使用這個概念,我們可以促進模型的訓練過程,這恰好被稱為**監督強迫**。
教師強迫有時會使我們的模型難以可靠地生成訓練中看不到的序列,但總的來說,該技術可能會有所幫助。
# 注意
注意是可以在序列到序列模型中實現的另一種有用的訓練技巧。 注意使解碼器在輸入序列的每個步驟中都能看到隱藏狀態。 這使網絡可以專注于(或關注)特定的輸入,這可以加快訓練速度并可以提高模型的準確率。 注意通常是一件好事。 但是,在撰寫本文時,Keras 尚未內置注意力。盡管如此,Keras 目前確實有一個拉取請求正在等待自定義注意層。 我懷疑很快就會在 Keras 中建立對關注的支持。
# 翻譯指標
知道翻譯是否良好很難。 機器翻譯質量的通用度量標準稱為**雙語評估研究**(**BLEU**),它最初是由 Papineni 等人在[《BLEU:一種自動評估機器翻譯的方法》](http://aclweb.org/anthology/P/P02/P02-1040.pdf)中創建的。 BLEU 是基于 ngram 的分類精度的改進應用。 如果您想使用 BLEU 來衡量翻譯質量,TensorFlow 團隊已經發布了一個腳本,該腳本可以根據給定的地面真實翻譯和機器預測翻譯的語料來計算 BLEU 分數。 您可以在[這里](https://github.com/tensorflow/nmt/blob/master/nmt/scripts/bleu.py)找到該腳本。
# 機器翻譯
`Je ne parle pasfran?ais`,那就是你怎么說我不會說英語的法語。 大約兩年前,我發現自己在巴黎,幾乎不會說法語。 在我去之前,我已經看過一本書,聽過一些 DVD,但是即使經過幾個月的練習,我對法語的掌握還是很可悲的。 然后,在旅途的第一個早晨,我醒來,走進附近的`boulangerie`(法國或法式面包店)吃早餐和早晨咖啡。 我說`Bonjour, parlez-vous anglais?`,他們一點也不講英語,或者也許他們正在享受我的奮斗。 無論哪種方式,當我的早餐取決于我對法語的掌握時,我都會比過去更有動力去爭取`Je voudrais un pain au chocolat`(翻譯:我想要其中一種美味的巧克力面包)。 在最終成本函數(我的胃)的驅動下,我很快學會了在英語序列和法語序列之間進行映射。
在本案例研究中,我們將教計算機講法語。 在幾個小時的訓練中,該模型將比我說法語更好。 考慮一下,這真是太神奇了。 我將訓練一臺計算機來執行我自己無法完成的任務。 當然,也許您確實會說法語,但這并不會給您留下深刻的印象,在這種情況下,我將美國著名演員亞當·桑德勒(Adam Sandler)稱為比利·麥迪遜(Billy Madison):好吧,對我來說很難,所以退縮!
該示例的大部分來自于弗朗索瓦·喬勒(Francois Chollet)的博客文章,標題為[《序列到序列學習的十分鐘介紹》](https://blog.keras.io/a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras.html)。 盡管我懷疑自己是否可以改進這項工作,但我希望使用本示例的目的是花一點點多一點的時間看一下序列到序列的網絡,以使您掌握實現自己的所有知識。
與往常一樣,本章的代碼可以在本書的 Git 存儲庫中的`Chapter11`下找到。 您可以在[這個頁面](http://www.manythings.org/anki/)中找到此示例所需的數據,該文件將存檔許多雙語句子對的數據集,我們將在后面詳細討論。 我要使用的文件是 [fra-eng.zip](http://www.manythings.org/anki/fra-eng.zip) 。 這是英語/法語句子對的集合。 如果需要,您可以輕松選擇其他語言,而無需進行太多修改。
在本案例研究中,我們將構建一個網絡,該網絡可以在給定一些英語句子的情況下學習法語句子。 這將是一個具有老師強迫作用的字符級序列到序列模型。
我希望最終得到的是看起來很像翻譯服務的東西,您可以在網上找到它或下載到手機上。
# 了解數據
我們正在使用的數據是一個文本文件。 每行都有一個英文短語及其法語翻譯,并用一個選項卡分隔,如以下代碼所示:
```py
Ignore Tom. Ignorez Tom.
```
(我不確定`Tom`對數據集的作者做了什么...)
通常,每行英語翻譯都有重復的法語翻譯行。 當有多種常用方法翻譯英語短語時,會發生這種情況。 看下面的代碼例如:
```py
Go now. Va, maintenant.
Go now. Allez-y maintenant.
Go now. Vas-y maintenant.
```
由于我們正在構建一個字符級序列到序列模型,因此需要將數據加載到內存中,然后對每個輸入和輸出在字符級進行熱編碼。 那是困難的部分。 讓我們接下來做。
# 加載數據
加載此數據涉及很多工作。 閱讀本文時,您可能想參考代碼塊。
以下代碼中的第一個`for`循環將遍歷整個輸入文件或調用`load_data()`時指定的一些樣本。 我這樣做是因為您可能沒有 RAM 來加載整個數據集。 多達 10,000 個示例,您可能會獲得良好的結果; 但是,多多益善。
當我們逐行瀏覽輸入文件時,我們一次要執行幾項操作:
* 我們將每個法語翻譯包裝在`'\t'`中,以開始該短語,并在`'\n'`中,以結束它。 這對應于我在序列到序列圖中使用的`<SOS>`和`<EOS>`標簽。 當我們要生成翻譯序列時,這將允許我們使用`'\t'`作為輸入來為解碼器設定種子。
* 我們將每一行分為英語輸入和其各自的法語翻譯。 這些存儲在列表`input_texts`和`target_texts`中。
* 最后,我們將輸入文本和目標文本的每個字符添加到一個集合中。 這些集稱為`input_characters`和`target_characters`。 當需要對短語進行熱編碼時,我們將使用這些集合。
循環完成后,我們會將字符集轉換為排序列表。 我們還將創建名為`num_encoder_tokens`和`num_decoder_tokens`的變量,以保存每個列表的大小。 稍后我們也將需要這些以進行單熱編碼。
為了將輸入和目標輸入矩陣,我們需要像上一章一樣,將短語填充到最長短語的長度。 為此,我們需要知道最長的短語。 我們將其存儲在`max_encoder_seq_length`和`max_decoder_seq_length`中,如以下代碼所示:
```py
def load_data(num_samples=50000, start_char='\t', end_char='\n', data_path='data/fra-eng/fra.txt'):
input_texts = []
target_texts = []
input_characters = set()
target_characters = set()
lines = open(data_path, 'r', encoding='utf-8').read().split('\n')
for line in lines[: min(num_samples, len(lines) - 1)]:
input_text, target_text = line.split('\t')
target_text = start_char + target_text + end_char
input_texts.append(input_text)
target_texts.append(target_text)
for char in input_text:
if char not in input_characters:
input_characters.add(char)
for char in target_text:
if char not in target_characters:
target_characters.add(char)
input_characters = sorted(list(input_characters))
target_characters = sorted(list(target_characters))
num_encoder_tokens = len(input_characters)
num_decoder_tokens = len(target_characters)
max_encoder_seq_length = max([len(txt) for txt in input_texts])
max_decoder_seq_length = max([len(txt) for txt in target_texts])
print('Number of samples:', len(input_texts))
print('Number of unique input tokens:', num_encoder_tokens)
print('Number of unique output tokens:', num_decoder_tokens)
print('Max sequence length for inputs:', max_encoder_seq_length)
print('Max sequence length for outputs:', max_decoder_seq_length)
return {'input_texts': input_texts, 'target_texts': target_texts,
'input_chars': input_characters, 'target_chars':
target_characters, 'num_encoder_tokens': num_encoder_tokens,
'num_decoder_tokens': num_decoder_tokens,
'max_encoder_seq_length': max_encoder_seq_length,
'max_decoder_seq_length': max_decoder_seq_length}
```
加載數據后,我們將在字典中返回所有這些信息,這些信息可以傳遞給一個函數,該函數將對每個短語進行熱編碼。 讓我們接下來做。
# 單熱編碼
在此函數中,我們將使用剛剛構建的字典,并對每個短語的文本進行熱編碼。
一旦完成,我們將剩下三個字典。 它們每個的尺寸為`[文本數 * 最大序列長度 * 標記]`。 如果您停頓一下,回想一下第 10 章“使用單詞嵌入從零開始訓練 LSTM”的更簡單的時間,您會發現這確實與我們在其他 NLP 模型中使用的相同,我們在輸入端完成它。 我們將使用以下代碼定義單熱編碼:
```py
def one_hot_vectorize(data):
input_chars = data['input_chars']
target_chars = data['target_chars']
input_texts = data['input_texts']
target_texts = data['target_texts']
max_encoder_seq_length = data['max_encoder_seq_length']
max_decoder_seq_length = data['max_decoder_seq_length']
num_encoder_tokens = data['num_encoder_tokens']
num_decoder_tokens = data['num_decoder_tokens']
input_token_index = dict([(char, i) for i, char in
enumerate(input_chars)])
target_token_index = dict([(char, i) for i, char in
enumerate(target_chars)])
encoder_input_data = np.zeros((len(input_texts),
max_encoder_seq_length, num_encoder_tokens), dtype='float32')
decoder_input_data = np.zeros((len(input_texts),
max_decoder_seq_length, num_decoder_tokens), dtype='float32')
decoder_target_data = np.zeros((len(input_texts),
max_decoder_seq_length, num_decoder_tokens), dtype='float32')
for i, (input_text, target_text) in enumerate(zip(input_texts,
target_texts)):
for t, char in enumerate(input_text):
encoder_input_data[i, t, input_token_index[char]] = 1.
for t, char in enumerate(target_text):
# decoder_target_data is ahead of decoder_input_data by one
timestep
decoder_input_data[i, t, target_token_index[char]] = 1.
if t > 0:
# decoder_target_data will be ahead by one timestep
# and will not include the start character.
decoder_target_data[i, t - 1, target_token_index[char]] = 1.
data['input_token_index'] = input_token_index
data['target_token_index'] = target_token_index
data['encoder_input_data'] = encoder_input_data
data['decoder_input_data'] = decoder_input_data
data['decoder_target_data'] = decoder_target_data
return data
```
我們在此代碼中創建了三個訓練向量。 在繼續之前,我想確保我們了解以下所有向量:
* `encoder_input_data`是形狀為`number_of_pairs`,`max_english_sequence_length`,`number_of_english_characters`的 3D 矩陣。
* `decoder_input_data`是形狀(`number_of_pairs`,`max_french_sequence_length`,`number_of_french_characters`)的 3d 矩陣。
* `decoder_output_data`與`decoder_input_data`相同,僅向前移了一個時間步。 這意味著`decoder_input_data[:, t+1, :]`等于`decoder_output_data[:, t, :]`。
前面的每個向量都是字符層上整個短語的一個熱編碼表示。 這意味著,如果我們輸入的短語是 Go! 向量的第一步是為文本中每個可能的英文字符包含一個元素。 除`g`設置為 1 以外,其他每個元素都將設置為`0`。
我們的目標是使用`encoder_input_data`和`decoder_input`數據作為輸入特征,訓練序列至序列模型來預測`decoder_output_data`。
終于完成了數據準備,因此我們可以開始構建序列到序列的網絡架構。
# 用于訓練的網絡架構
在此示例中,我們實際上將使用兩種單獨的架構,一種用于訓練,另一種用于推理。 我們將從推理模型訓練中使用訓練過的層。 雖然實際上我們為每種架構使用了相同的部分,但是為了使事情更清楚,我將分別展示每個部分。 以下是我們將用來訓練網絡的模型:
```py
encoder_input = Input(shape=(None, num_encoder_tokens), name='encoder_input')
encoder_outputs, state_h, state_c = LSTM(lstm_units, return_state=True,
name="encoder_lstm")(encoder_input)
encoder_states = [state_h, state_c]
decoder_input = Input(shape=(None, num_decoder_tokens), name='decoder_input')
decoder_lstm = LSTM(lstm_units, return_sequences=True,
return_state=True, name="decoder_lstm")
decoder_outputs, _, _ = decoder_lstm(decoder_input, initial_state=encoder_states)
decoder_dense = Dense(num_decoder_tokens, activation='softmax',
name='softmax_output')
decoder_output = decoder_dense(decoder_outputs)
model = Model([encoder_input, decoder_input], decoder_output)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
```
如果我們將*放大*編碼器,則會看到相當標準的 LSTM。 不同之處在于,我們從編碼器(`return_state=True`)獲取狀態,如果將 LSTM 連接到密集層,通常不會這樣做。 這些狀態是我們將在`encoder_states`中捕獲的狀態。 我們將使用它們為解碼器提供上下文或條件。
在解碼器方面,我們設置的`decoder_lstm`與我們先前構建 Keras 層的方式略有不同,但實際上只是語法略有不同。
看下面的代碼:
```py
decoder_lstm = LSTM(lstm_units, return_sequences=True,
return_state=True, name="decoder_lstm")
decoder_outputs, _, _ = decoder_lstm(decoder_input, initial_state=encoder_states)
```
其功能與以下代碼相同:
```py
decoder_outputs, _, _ = LSTM(lstm_units, return_sequences=True,
return_state=True, name="decoder_lstm")(decoder_input, initial_state=encoder_states)
```
我這樣做的原因在推理架構中將變得顯而易見。
請注意,解碼器將編碼器的隱藏狀態作為其初始狀態。 然后將解碼器輸出傳遞到預測`decoder_output_data`的`softmax`層。
最后,我們將定義訓練模型,我將其創造性地稱為`model`,該模型將`encoder_input_data`和`decoder_input`數據作為輸入并預測`decoder_output_data`。
# 用于推理的網絡架構
為了在給定輸入序列的情況下預測整個序列,我們需要稍微重新安排一下架構。 我懷疑在 Keras 的未來版本中,這將變得更簡單,但是從今天起這是必需的步驟。
為什么需要有所不同? 因為我們沒有推斷的`decoder_input_data`教師向量。 我們現在獨自一人。 因此,我們將必須進行設置,以便我們不需要該向量。
讓我們看一下這種推理架構,然后逐步執行代碼:
```py
encoder_model = Model(encoder_input, encoder_states)
decoder_state_input_h = Input(shape=(lstm_units,))
decoder_state_input_c = Input(shape=(lstm_units,))
decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]
decoder_outputs, state_h, state_c = decoder_lstm(
decoder_input, initial_state=decoder_states_inputs)
decoder_states = [state_h, state_c]
decoder_outputs = decoder_dense(decoder_outputs)
decoder_model = Model(
[decoder_input] + decoder_states_inputs,
[decoder_outputs] + decoder_states)
```
首先,我們從構建編碼器模型開始。 該模型將采用一個輸入序列,并返回我們在先前模型中訓練過的 LSTM 的隱藏狀態。
然后,解碼器模型具有兩個輸入,即`h`和`c`隱藏狀態,這些狀態限制了其從編碼器模型派生的輸出。 我們統稱為`decoder_states_inputs`。
我們可以從上面重用`decoder_lstm`; 但是,這次我們不會丟棄狀態`state_h`和`state_c`。 我們將把它們與目標的`softmax`預測一起作為網絡輸出傳遞。
現在,當我們推斷出一個新的輸出序列時,我們可以在預測第一個字符之后獲得這些狀態,然后將它們通過`softmax`預測傳遞回 LSTM,以便 LSTM 可以預測另一個字符。 我們將重復該循環,直到解碼器生成一個`'\n'`信號為止,該信號已到達`<EOS>`。
我們將很快看一下推理代碼。 現在,讓我們看看如何訓練和序列化此模型集合。
# 放在一起
按照本書的傳統,我將在這里向您展示該模型的整個架構如何融合在一起:
```py
def build_models(lstm_units, num_encoder_tokens, num_decoder_tokens):
# train model
encoder_input = Input(shape=(None, num_encoder_tokens),
name='encoder_input')
encoder_outputs, state_h, state_c = LSTM(lstm_units,
return_state=True, name="encoder_lstm")(encoder_input)
encoder_states = [state_h, state_c]
decoder_input = Input(shape=(None, num_decoder_tokens),
name='decoder_input')
decoder_lstm = LSTM(lstm_units, return_sequences=True,
return_state=True, name="decoder_lstm")
decoder_outputs, _, _ = decoder_lstm(decoder_input,
initial_state=encoder_states)
decoder_dense = Dense(num_decoder_tokens, activation='softmax',
name='softmax_output')
decoder_output = decoder_dense(decoder_outputs)
model = Model([encoder_input, decoder_input], decoder_output)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
encoder_model = Model(encoder_input, encoder_states)
decoder_state_input_h = Input(shape=(lstm_units,))
decoder_state_input_c = Input(shape=(lstm_units,))
decoder_states_inputs = [decoder_state_input_h,
decoder_state_input_c]
decoder_outputs, state_h, state_c = decoder_lstm(
decoder_input, initial_state=decoder_states_inputs)
decoder_states = [state_h, state_c]
decoder_outputs = decoder_dense(decoder_outputs)
decoder_model = Model(
[decoder_input] + decoder_states_inputs,
[decoder_outputs] + decoder_states)
return model, encoder_model, decoder_model
```
請注意,我們將在此處返回所有三個模型。 訓練完訓練模型后,我將使用`keras model.save()`方法序列化這三個方法。
# 訓練
我們終于準備好訓練我們的序列到序列網絡。 以下代碼首先調用我們所有的數據加載函數,創建回調,然后擬合模型:
```py
data = load_data()
data = one_hot_vectorize(data)
callbacks = create_callbacks("char_s2s")
model, encoder_model, decoder_model = build_models(256, data['num_encoder_tokens'], data['num_decoder_tokens'])
print(model.summary())
model.fit(x=[data["encoder_input_data"], data["decoder_input_data"]],
y=data["decoder_target_data"],
batch_size=64,
epochs=100,
validation_split=0.2,
callbacks=callbacks)
model.save('char_s2s_train.h5')
encoder_model.save('char_s2s_encoder.h5')
decoder_model.save('char_s2s_decoder.h5')
```
您會注意到,我以前沒有像通常那樣定義驗證或測試集。 這次,按照博客文章中給出的示例,我將讓 Keras 隨機選擇 20% 的數據作為驗證,這在示例中可以很好地工作。 如果要使用此代碼實際進行機器翻譯,請使用單獨的測試集。
訓練模型適合后,我將保存所有三個模型,并將它們再次加載到為推理而構建的單獨程序中。 我這樣做是為了使代碼保持簡潔,因為推理代碼本身非常復雜。
讓我們來看看這個模型的 100 個周期的模型訓練:

如您所見,我們在第 20 個周期開始過擬合。雖然損失持續減少,但`val_loss`卻在增加。 在這種情況下,模型檢查指向可能無法正常工作,因為在訓練結束之前我們不會序列化推理模型。 因此,理想情況下,我們應該再訓練一次,將訓練的周期數設置為略大于 TensorBoard 中觀察到的最小值。
# 推理
現在我們有了訓練有素的模型,我們將實際生成一些翻譯。
總體而言,推理步驟如下:
1. 加載數據并再次向量化(我們需要字符到索引的映射以及一些轉換進行測試)
2. 使用字符對字典進行索引,我們將創建字符字典的反向索引,因此一旦我們預測了正確的字符,我們就可以從數字返回到字符
3. 選擇一些輸入序列進行翻譯,然后通過編碼器運行,獲取狀態
4. 將狀態和`<SOS>`字符`'\t'`發送到解碼器。
5. 循環,獲取每個下一個字符,直到解碼器生成`<EOS>`或`'\n'`
# 加載數據
我們可以從訓練腳本中導入`load_data`和`one_hot_vectorize`函數,以相同的方式調用這些方法,如以下代碼所示:
```py
data = load_data()
data = one_hot_vectorize(data)
```
# 創建反向索引
解碼器將預測正確字符的索引,該索引將是解碼器的`softmax`輸出的`argmax`。 我們將需要能夠將索引映射到字符。 您可能還記得,數據字典中已經有一個字符到索引的映射,所以我們只需要反轉它即可。 逆轉字典非常簡單,如下所示:
```py
def create_reverse_indicies(data):
data['reverse_target_char_index'] = dict(
(i, char) for char, i in data["target_token_index"].items())
return data
```
然后,我們可以如下調用此函數:
```py
data = create_reverse_indicies(data)
```
# 載入模型
我們可以使用`keras.models.load_model`加載保存在訓練腳本中的模型。 我創建了此助手來完成該任務。 我們將使用以下代碼加載模型:
```py
def load_models():
model = load_model('char_s2s.h5')
encoder_model = load_model('char_s2s_encoder.h5')
decoder_model = load_model('char_s2s_decoder.h5')
return [model, encoder_model, decoder_model]
```
我們可以調用以下函數來加載所有三個模型:
```py
model, encoder_model, decoder_model = load_models()
```
# 翻譯序列
現在,我們準備對一些輸入序列進行采樣并進行翻譯。 在示例代碼中,我們使用前 100 個雙語對進行翻譯。 一個更好的測試可能是在整個空間中隨機抽樣,但是我認為這個簡單的循環說明了這一過程:
```py
for seq_index in range(100):
input_seq = data["encoder_input_data"][seq_index: seq_index + 1]
decoded_sentence = decode_sequence(input_seq, data, encoder_model,
decoder_model)
print('-')
print('Input sentence:', data['input_texts'][seq_index])
print('Correct Translation:', data['target_texts']
[seq_index].strip("\t\n"))
print('Decoded sentence:', decoded_sentence)
```
在這段代碼中,我們將`encoder_input_data`的一個觀察值用作`decode_sequence`的輸入。 `decode_sequence`將傳回解碼器認為正確翻譯的序列。 我們還需要將其傳遞給編碼器和解碼器模型,以便能夠完成其工作。下面的翻譯更加有趣,因為學習的短語未與
有了解碼器預測后,就可以將其與輸入和正確的轉換進行比較。
當然,我們還沒有完成,因為我們還沒有探討`decode_sequence`方法的工作方式。 接下來。
# 解碼序列
解碼器需要執行以下兩項操作:
* 來自編碼器的狀態。
* 輸入信號開始預測的翻譯。 我們將在一個熱向量中向其發送`'\t'`,因為這是我們的`<SOS>`字符。
為了獲得編碼器狀態,我們只需要使用以下代碼將要翻譯的短語的向量化版本發送到編碼器:
```py
states_value = encoder_model.predict(input_seq)
```
為了啟動解碼器,我們還需要一個包含`<SOS>`字符的熱向量。 這段代碼將我們帶到了那里:
```py
target_seq = np.zeros((1, 1, data['num_decoder_tokens']))
target_seq[0, 0, data['target_token_index']['\t']] = 1.
```
現在,我們準備使用以下代碼設置一個解碼器循環,該循環將生成我們的翻譯短語:
```py
stop_condition = False
decoded_sentence = ''
while not stop_condition:
output_tokens, h, c = decoder_model.predict(
[target_seq] + states_value)
sampled_token_index = np.argmax(output_tokens[0, -1, :])
sampled_char = data["reverse_target_char_index"][sampled_token_index]
decoded_sentence += sampled_char
if (sampled_char == '\n' or
len(decoded_sentence) > data['max_decoder_seq_length']):
stop_condition = True
target_seq = np.zeros((1, 1, data['num_decoder_tokens']))
target_seq[0, 0, sampled_token_index] = 1.
states_value = [h, c]
```
首先要注意的是,我們一直循環到`stop_condition = True`。 這在解碼器生成`'\n'`時發生。
第一次通過循環,我使用`<SOS>`向量和我們在循環外部創建的編碼器的狀態調用了`decoder_model`的預測方法。
當然,`output_tokens`將包含解碼器可以預測的每個字符的`softmax`預測。 通過取`output_tokens`的`argmax`,我們將獲得最大`softmax`值的索引。 方便地,我可以使用之前創建的`reverse_target_char_index`將其轉換回關聯的字符,這是一個在索引和字符之間轉換的字典。
接下來,我們將該字符附加到`decode_sequence`字符串。
接下來,我們可以檢查該字符是否為`'\n'`并觸發`stop_condition`為`True`。
最后,我們將創建一個新的`target_seq`,其中包含解碼器生成的最后一個字符,以及一個包含解碼器隱藏狀態的列表。 現在,我們準備再次重復循環。
我們的解碼器將遵循此過程,直到生成解碼序列為止。
# 翻譯示例
只是為了好玩,我在這里提供了一些嘗試的翻譯。 所有這些都來自訓練集的前面,這意味著我正在對`training`數據集進行預測,因此這些轉換可能會使模型看起來比實際更好。
我們的第一版翻譯使您對我們的期望有所了解,并且該網絡做得很好:
輸入句子:`Help!`
正確翻譯:`à l'aide!`
解碼后的句子:`à l'aide!`
后續的翻譯更加有趣,因為學習的短語未與任何訓練短語相關聯。 短語`Vas-tu immédiatement!`轉換為類似`You go immediately`的字詞。這非常相似,甚至可能正確:
輸入句子:`Go on.`
正確的翻譯: `Poursuis.`
解碼后的句子: `Vas-tu immédiatement!`
輸入句子:`Go on.`
正確的翻譯:`Continuez.`
解碼后的句子: `Vas-tu immédiatement!`
輸入句子:`Go on.`
正確的翻譯: `Poursuivez.`
解碼后的句子: `Vas-tu immédiatement!`
當然,有很多方法可以說相同的事情,這使得網絡變得更加困難:
輸入句子:`Come on!`
正確的翻譯: `Allez?!`
解碼后的句子: `Allez?!`
輸入句子:`Come on!`
正確的翻譯: `Allez?!`
解碼后的句子: `Allez?!`
輸入句子:`Come on.`
正確的翻譯:`Viens!`
解碼后的句子: `Allez!`
輸入句子:`Come on.`
正確的翻譯:`Venez!`
解碼后的句子: `Allez!`
# 總結
在本章中,我們介紹了序列到序列模型的基礎知識,包括它們如何工作以及如何使用它們。 希望我們已經向您展示了一個功能強大的工具,可用于機器翻譯,問題解答和聊天應用。
如果您已經做到了,那就好。 您已經看到了很多深度學習的應用,并且發現自己正處于深層神經網絡應用的最先進的鐘形曲線的右邊。
在下一章中,我將向您展示另一個高級主題的示例,即深度強化學習或深度 Q 學習,并向您展示如何實現自己的深度 Q 網絡。
在此之前,請放松!
- TensorFlow 1.x 深度學習秘籍
- 零、前言
- 一、TensorFlow 簡介
- 二、回歸
- 三、神經網絡:感知器
- 四、卷積神經網絡
- 五、高級卷積神經網絡
- 六、循環神經網絡
- 七、無監督學習
- 八、自編碼器
- 九、強化學習
- 十、移動計算
- 十一、生成模型和 CapsNet
- 十二、分布式 TensorFlow 和云深度學習
- 十三、AutoML 和學習如何學習(元學習)
- 十四、TensorFlow 處理單元
- 使用 TensorFlow 構建機器學習項目中文版
- 一、探索和轉換數據
- 二、聚類
- 三、線性回歸
- 四、邏輯回歸
- 五、簡單的前饋神經網絡
- 六、卷積神經網絡
- 七、循環神經網絡和 LSTM
- 八、深度神經網絡
- 九、大規模運行模型 -- GPU 和服務
- 十、庫安裝和其他提示
- TensorFlow 深度學習中文第二版
- 一、人工神經網絡
- 二、TensorFlow v1.6 的新功能是什么?
- 三、實現前饋神經網絡
- 四、CNN 實戰
- 五、使用 TensorFlow 實現自編碼器
- 六、RNN 和梯度消失或爆炸問題
- 七、TensorFlow GPU 配置
- 八、TFLearn
- 九、使用協同過濾的電影推薦
- 十、OpenAI Gym
- TensorFlow 深度學習實戰指南中文版
- 一、入門
- 二、深度神經網絡
- 三、卷積神經網絡
- 四、循環神經網絡介紹
- 五、總結
- 精通 TensorFlow 1.x
- 一、TensorFlow 101
- 二、TensorFlow 的高級庫
- 三、Keras 101
- 四、TensorFlow 中的經典機器學習
- 五、TensorFlow 和 Keras 中的神經網絡和 MLP
- 六、TensorFlow 和 Keras 中的 RNN
- 七、TensorFlow 和 Keras 中的用于時間序列數據的 RNN
- 八、TensorFlow 和 Keras 中的用于文本數據的 RNN
- 九、TensorFlow 和 Keras 中的 CNN
- 十、TensorFlow 和 Keras 中的自編碼器
- 十一、TF 服務:生產中的 TensorFlow 模型
- 十二、遷移學習和預訓練模型
- 十三、深度強化學習
- 十四、生成對抗網絡
- 十五、TensorFlow 集群的分布式模型
- 十六、移動和嵌入式平臺上的 TensorFlow 模型
- 十七、R 中的 TensorFlow 和 Keras
- 十八、調試 TensorFlow 模型
- 十九、張量處理單元
- TensorFlow 機器學習秘籍中文第二版
- 一、TensorFlow 入門
- 二、TensorFlow 的方式
- 三、線性回歸
- 四、支持向量機
- 五、最近鄰方法
- 六、神經網絡
- 七、自然語言處理
- 八、卷積神經網絡
- 九、循環神經網絡
- 十、將 TensorFlow 投入生產
- 十一、更多 TensorFlow
- 與 TensorFlow 的初次接觸
- 前言
- 1.?TensorFlow 基礎知識
- 2. TensorFlow 中的線性回歸
- 3. TensorFlow 中的聚類
- 4. TensorFlow 中的單層神經網絡
- 5. TensorFlow 中的多層神經網絡
- 6. 并行
- 后記
- TensorFlow 學習指南
- 一、基礎
- 二、線性模型
- 三、學習
- 四、分布式
- TensorFlow Rager 教程
- 一、如何使用 TensorFlow Eager 構建簡單的神經網絡
- 二、在 Eager 模式中使用指標
- 三、如何保存和恢復訓練模型
- 四、文本序列到 TFRecords
- 五、如何將原始圖片數據轉換為 TFRecords
- 六、如何使用 TensorFlow Eager 從 TFRecords 批量讀取數據
- 七、使用 TensorFlow Eager 構建用于情感識別的卷積神經網絡(CNN)
- 八、用于 TensorFlow Eager 序列分類的動態循壞神經網絡
- 九、用于 TensorFlow Eager 時間序列回歸的遞歸神經網絡
- TensorFlow 高效編程
- 圖嵌入綜述:問題,技術與應用
- 一、引言
- 三、圖嵌入的問題設定
- 四、圖嵌入技術
- 基于邊重構的優化問題
- 應用
- 基于深度學習的推薦系統:綜述和新視角
- 引言
- 基于深度學習的推薦:最先進的技術
- 基于卷積神經網絡的推薦
- 關于卷積神經網絡我們理解了什么
- 第1章概論
- 第2章多層網絡
- 2.1.4生成對抗網絡
- 2.2.1最近ConvNets演變中的關鍵架構
- 2.2.2走向ConvNet不變性
- 2.3時空卷積網絡
- 第3章了解ConvNets構建塊
- 3.2整改
- 3.3規范化
- 3.4匯集
- 第四章現狀
- 4.2打開問題
- 參考
- 機器學習超級復習筆記
- Python 遷移學習實用指南
- 零、前言
- 一、機器學習基礎
- 二、深度學習基礎
- 三、了解深度學習架構
- 四、遷移學習基礎
- 五、釋放遷移學習的力量
- 六、圖像識別與分類
- 七、文本文件分類
- 八、音頻事件識別與分類
- 九、DeepDream
- 十、自動圖像字幕生成器
- 十一、圖像著色
- 面向計算機視覺的深度學習
- 零、前言
- 一、入門
- 二、圖像分類
- 三、圖像檢索
- 四、對象檢測
- 五、語義分割
- 六、相似性學習
- 七、圖像字幕
- 八、生成模型
- 九、視頻分類
- 十、部署
- 深度學習快速參考
- 零、前言
- 一、深度學習的基礎
- 二、使用深度學習解決回歸問題
- 三、使用 TensorBoard 監控網絡訓練
- 四、使用深度學習解決二分類問題
- 五、使用 Keras 解決多分類問題
- 六、超參數優化
- 七、從頭開始訓練 CNN
- 八、將預訓練的 CNN 用于遷移學習
- 九、從頭開始訓練 RNN
- 十、使用詞嵌入從頭開始訓練 LSTM
- 十一、訓練 Seq2Seq 模型
- 十二、深度強化學習
- 十三、生成對抗網絡
- TensorFlow 2.0 快速入門指南
- 零、前言
- 第 1 部分:TensorFlow 2.00 Alpha 簡介
- 一、TensorFlow 2 簡介
- 二、Keras:TensorFlow 2 的高級 API
- 三、TensorFlow 2 和 ANN 技術
- 第 2 部分:TensorFlow 2.00 Alpha 中的監督和無監督學習
- 四、TensorFlow 2 和監督機器學習
- 五、TensorFlow 2 和無監督學習
- 第 3 部分:TensorFlow 2.00 Alpha 的神經網絡應用
- 六、使用 TensorFlow 2 識別圖像
- 七、TensorFlow 2 和神經風格遷移
- 八、TensorFlow 2 和循環神經網絡
- 九、TensorFlow 估計器和 TensorFlow HUB
- 十、從 tf1.12 轉換為 tf2
- TensorFlow 入門
- 零、前言
- 一、TensorFlow 基本概念
- 二、TensorFlow 數學運算
- 三、機器學習入門
- 四、神經網絡簡介
- 五、深度學習
- 六、TensorFlow GPU 編程和服務
- TensorFlow 卷積神經網絡實用指南
- 零、前言
- 一、TensorFlow 的設置和介紹
- 二、深度學習和卷積神經網絡
- 三、TensorFlow 中的圖像分類
- 四、目標檢測與分割
- 五、VGG,Inception,ResNet 和 MobileNets
- 六、自編碼器,變分自編碼器和生成對抗網絡
- 七、遷移學習
- 八、機器學習最佳實踐和故障排除
- 九、大規模訓練
- 十、參考文獻