原文:https://www.tensorflow.org/versions/r1.5/tutorials/seq2seq
(全文99.9%由google翻譯稼虎,自學(xué)使用)
作者:Thang Luong厨钻,Eugene Brevdo脆贵,Rui Zhao(Google Research Blogpost毁枯,Github)
本教程的這個(gè)版本需要TensorFlow Nightly磺陡。 為了使用穩(wěn)定的TensorFlow版本沼本,請考慮其他分支兢交,如tf-1.4薪捍。
如果使用這個(gè)代碼庫進(jìn)行研究,請引用此配喳。
介紹(Introduction)
序列 - 序列(seq2seq)模型(Sutskever et al., 2014, Cho et al., 2014
)在機(jī)器翻譯酪穿,語音識別和文本摘要等各種任務(wù)中取得了巨大的成功。 本教程為讀者提供了seq2seq模型的全面理解晴裹,并展示了如何從頭構(gòu)建一個(gè)有競爭力的seq2seq模型被济。 我們專注于神經(jīng)機(jī)器翻譯(NMT)的任務(wù),它是seq2seq模型的第一個(gè)測試平臺涧团。 包含的代碼是輕量級的只磷,高質(zhì)量的,生產(chǎn)就緒泌绣,并結(jié)合最新的研究思路钮追。 我們通過以下方式達(dá)到這個(gè):
- 使用最近的解碼器/注意力包裝器(decoder / attention wrapper)API,TensorFlow 1.2數(shù)據(jù)迭代器
- 結(jié)合我們強(qiáng)大的專業(yè)知識阿迈,建立循環(huán)和seq2seq模型
- 為建立最好的NMT模型和重現(xiàn)Google’s NMT (GNMT) 系統(tǒng)提供技巧和竅門元媚。
我們相信提供人們可以輕松重現(xiàn)的benchmarks是非常重要的。 因此苗沧,我們提供了完整的實(shí)驗(yàn)結(jié)果刊棕,并在以下公開可用的數(shù)據(jù)集上對模型進(jìn)行預(yù)訓(xùn)練:
- 小規(guī)模:IWSLT Evaluation Campaign提供的英語 - 越南語TED對話語料庫(133K個(gè)句子對)。
- 大規(guī)模:WMT Evaluation Campaign提供的德英平行語料庫(4.5M句子對)崎页。
我們首先建立一些關(guān)于NMT的seq2seq模型的基本知識鞠绰,解釋如何建立和訓(xùn)練一個(gè)vanilla NMT模型。 第二部分將著重于構(gòu)建具有注意力機(jī)制的競爭性NMT模型飒焦。 然后,我們將討論一些技巧和訣竅,以建立最佳NMT模型(包括速度和翻譯質(zhì)量)牺荠,如TensorFlow最佳實(shí)踐(批處理翁巍,分段),雙向RNN休雌,波束搜索(beam search [這個(gè)是啥灶壶?])以及使用GNMT注意力擴(kuò)展到多個(gè)GPU。
基礎(chǔ)知識(Basic)
神經(jīng)機(jī)器翻譯的背景(Background on Neural Machine Translation)
在過去杈曲,傳統(tǒng)的基于短語的翻譯系統(tǒng)通過將源語句拆分成多個(gè)片段然后將其逐句翻譯來執(zhí)行他們的任務(wù)驰凛。 這導(dǎo)致了翻譯產(chǎn)出的不流暢,并不像我們?nèi)祟惙g的那樣担扑。 我們閱讀整個(gè)源句子恰响,理解它的意思,然后產(chǎn)生一個(gè)翻譯涌献。 神經(jīng)機(jī)器翻譯(NMT)模仿的就是這種方式胚宦!
具體而言枢劝,NMT系統(tǒng)首先使用編碼器讀取源句子來構(gòu)建“思想”向量,表示句子含義的數(shù)字序列; 解碼器然后處理句子向量以發(fā)出翻譯卜壕,如圖1所示您旁。這通常被稱為編碼器 - 解碼器體系結(jié)構(gòu)。 NMT以這種方式解決了傳統(tǒng)的基于短語的方法中的局部翻譯問題:它可以捕捉語言的長期依賴性轴捎,例如性別協(xié)議; 語法結(jié)構(gòu)等; 并通過谷歌神經(jīng)機(jī)器翻譯系統(tǒng)(Google Neural Machine Translation systems)展示了更流利的翻譯鹤盒。
NMT模型根據(jù)其確切的體系結(jié)構(gòu)而有所不同。 對于序列數(shù)據(jù)轮蜕,很自然的選擇是大多數(shù)NMT模型所使用的遞歸神經(jīng)網(wǎng)絡(luò)(RNN)昨悼。 編碼器和解碼器通常使用RNN。 但是跃洛,RNN模型在以下方面有所不同:(a)方向性 - 單向或雙向; (b)深度 - 單層或多層; 和(c)類型 - 通常是普通RNN率触,長期短期記憶(LSTM)或門控循環(huán)單元(GRU)。 有興趣的讀者可以在這篇博文中找到關(guān)于RNN和LSTM的更多信息汇竭。
在本教程中葱蝗,我們以單向的深度多層RNN為例,將LSTM作為一個(gè)遞歸單元细燎。 我們在圖2中展示了這樣模型的一個(gè)例子两曼。在這個(gè)例子中,我們建立了一個(gè)模型玻驻,將源句子"I am a student"翻譯成目標(biāo)句子"Je suisétudiant"悼凑。 在高層次上偿枕,NMT模型由兩個(gè)遞歸神經(jīng)網(wǎng)絡(luò)組成:編碼器RNN僅僅消耗輸入的源單詞而不作任何預(yù)測; 另一方面,解碼器在預(yù)測下一個(gè)單詞的同時(shí)處理目標(biāo)語句户辫。
欲了解更多信息渐夸,我們向讀者介紹本教程所基于的Luong (2016)。
安裝教程(Installing the Tutorial)
要安裝本教程,您需要在系統(tǒng)上安裝TensorFlow垫挨。 本教程需要TensorFlow Nightly韩肝。 要安裝TensorFlow,請按照此處的安裝說明進(jìn)行操作棒拂。
一旦安裝了TensorFlow伞梯,您可以運(yùn)行以下命令來下載本教程的源代碼:
git clone https://github.com/tensorflow/nmt/
訓(xùn)練 - 如何建立我們的第一個(gè)NMT系統(tǒng)(Training – How to build our first NMT system)
首先,我們將深入探討用具體的代碼片斷構(gòu)建NMT模型的核心帚屉,我們將通過它更詳細(xì)地解釋圖2谜诫。 我們將數(shù)據(jù)準(zhǔn)備和完整的代碼推遲到以后。 這部分是指文件model.py攻旦。
在底層喻旷,編碼器和解碼器RNNs接收以下輸入:首先是源句子,然后是指示從編碼轉(zhuǎn)換到解碼模式的邊界標(biāo)記"<s>" 牢屋,和目標(biāo)語句且预。 對于訓(xùn)練,我們會給系統(tǒng)提供以下張量烙无,這些張量在時(shí)間上是主要格式锋谐,包含文字索引:
- encoder_inputs [max_encoder_time,batch_size]:源輸入單詞截酷。
- decoder_inputs [max_decoder_time涮拗,batch_size]:目標(biāo)輸入單詞。
- decoder_outputs [max_decoder_time迂苛,batch_size]:目標(biāo)輸出單詞三热,這些是decoder_input向左移動(dòng)一個(gè)時(shí)間步,右邊添加一個(gè)句尾結(jié)束標(biāo)記三幻。
這里為了提高效率就漾,我們一起訓(xùn)練多個(gè)句子(batch_size)媒抠。 測試稍有不同缩膝,所以我們稍后再討論拆魏。
嵌入(Embedding)
鑒于單詞的分類性質(zhì)咱娶,模型必須首先查找源和目標(biāo)的嵌入來檢索相應(yīng)的詞表示(word representations)叔汁。 為了使這個(gè)嵌入層起作用养葵,首先為每種語言選擇一個(gè)詞匯表蜒什。 通常拇泛,選擇詞匯量V荣倾,只有最頻繁的V詞才被視為唯一悯搔。 所有其他單詞都轉(zhuǎn)換為"unknown"標(biāo)記,并獲得相同的嵌入舌仍。 嵌入權(quán)重妒貌,每種語言一套,通常在訓(xùn)練中學(xué)習(xí)铸豁。
# Embedding
embedding_encoder = variable_scope.get_variable(
"embedding_encoder", [src_vocab_size, embedding_size], ...)
# Look up embedding:
# encoder_inputs: [max_time, batch_size]
# encoder_emb_inp: [max_time, batch_size, embedding_size]
encoder_emb_inp = embedding_ops.embedding_lookup(
embedding_encoder, encoder_inputs)
同樣灌曙,我們可以構(gòu)建embedding_decoder和decoder_emb_inp。 請注意节芥,可以選擇使用預(yù)訓(xùn)練詞表示(例如word2vec或Glove vectors)來初始化嵌入權(quán)重在刺。 一般來說,給定大量的訓(xùn)練數(shù)據(jù)头镊,我們可以從頭學(xué)習(xí)這些嵌入蚣驼。
編碼器(Encoder)
一旦檢索出來,嵌入字就作為輸入被輸入到主網(wǎng)絡(luò)中相艇,主網(wǎng)絡(luò)由兩個(gè)多層RNN組成 - 一個(gè)源語言的編碼器和一個(gè)目標(biāo)語言的解碼器颖杏。 這兩個(gè)RNN原則上可以分享相同的權(quán)重; 然而,在實(shí)踐中坛芽,我們經(jīng)常使用兩個(gè)不同的RNN參數(shù)(這些模型在擬合大型訓(xùn)練數(shù)據(jù)集時(shí)效果更好)留储。 編碼器RNN使用零矢量作為其起始狀態(tài),并且如下構(gòu)建:
# Build RNN cell
encoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)
# Run Dynamic RNN
# encoder_outputs: [max_time, batch_size, num_units]
# encoder_state: [batch_size, num_units]
encoder_outputs, encoder_state = tf.nn.dynamic_rnn(
encoder_cell, encoder_emb_inp,
sequence_length=source_sequence_length, time_major=True)
請注意咙轩,為避免浪費(fèi)計(jì)算获讳,句子具有不同的長度,我們通過source_sequence_length告訴dynamic_rnn確切的源句子長度活喊。 由于我們的輸入是時(shí)序重要的丐膝,我們設(shè)置time_major = True。 在這里胧弛,我們只建立一個(gè)單層的LSTM尤误,encoder_cell。 我們將介紹如何構(gòu)建多層LSTM结缚,添加dropout损晤,并在后面的章節(jié)中使用attention。
解碼器(Decoder)
解碼器也需要訪問源信息红竭,一個(gè)簡單的方法就是用編碼器的最后一個(gè)隱藏狀態(tài)encoder_state來初始化它尤勋。 在圖2中喘落,我們將源語詞“student”的隱藏狀態(tài)傳遞給解碼器端。
# Build RNN cell
decoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)
# Helper
helper = tf.contrib.seq2seq.TrainingHelper(
decoder_emb_inp, decoder_lengths, time_major=True)
# Decoder
decoder = tf.contrib.seq2seq.BasicDecoder(
decoder_cell, helper, encoder_state,
output_layer=projection_layer)
# Dynamic decoding
outputs, _ = tf.contrib.seq2seq.dynamic_decode(decoder, ...)
logits = outputs.rnn_output
在這里最冰,這個(gè)代碼的核心部分是BasicDecoder對象瘦棋、解碼器,其接收decoder_cell(類似于encoder_cell)暖哨,幫助器赌朋,以及之前的encoder_state作為輸入。 通過分離出解碼器和幫助器篇裁,我們可以重用不同的代碼庫沛慢,例如,可以用GreedyEmbeddingHelper代替TrainingHelper來進(jìn)行貪婪的解碼达布。 請參閱helper.py团甲。
最后,我們沒有提到projection_layer是一個(gè)稠密的矩陣黍聂,它將頂部隱藏狀態(tài)轉(zhuǎn)化為維數(shù)為V的logit向量躺苦。我們在圖2的頂部說明了這個(gè)過程。
projection_layer = layers_core.Dense(
tgt_vocab_size, use_bias=False)
損失(Loss)
基于上面的logits产还,我們現(xiàn)在準(zhǔn)備計(jì)算我們的訓(xùn)練損失:
crossent = tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=decoder_outputs, logits=logits)
train_loss = (tf.reduce_sum(crossent * target_weights) /
batch_size)
這里匹厘,target_weights是與decoder_outputs相同大小的0-1矩陣。 它將目標(biāo)序列長度之外的值填充0雕沉。
重要說明:值得指出的是集乔,我們用batch_size來分割損失,所以我們的超參數(shù)對batch_size是“不變的”坡椒。 有些人用(batch_size * num_time_steps)來劃分損失扰路,減少了短句的錯(cuò)誤。 更微妙的是倔叼,我們的超參數(shù)(應(yīng)用于前一種方式)不能用于后一種方式汗唱。 例如,如果兩種方法都使用SGD來學(xué)習(xí)1.0丈攒,則后一種方法有效地使用1 / num_time_steps的更小的學(xué)習(xí)速率哩罪。
梯度計(jì)算和優(yōu)化(Gradient computation & optimization)
現(xiàn)在我們已經(jīng)定義了NMT模型的正向傳播。 計(jì)算反向傳播只是幾行代碼的問題:
# Calculate and clip gradients
params = tf.trainable_variables()
gradients = tf.gradients(train_loss, params)
clipped_gradients, _ = tf.clip_by_global_norm(
gradients, max_gradient_norm)
訓(xùn)練RNNs的重要步驟之一是梯度裁剪巡验。 在這里际插,我們在全局范圍內(nèi)進(jìn)行裁剪。 最大值max_gradient_norm通常設(shè)置為5或1的值显设。最后一步是選擇優(yōu)化器框弛。 Adam優(yōu)化器是一個(gè)常用的選擇。 我們也選擇一個(gè)學(xué)習(xí)率捕捂。 learning_rate的值通成悖可以在0.0001到0.001之間; 隨著訓(xùn)練的進(jìn)行可以減少斗搞。
# Optimization
optimizer = tf.train.AdamOptimizer(learning_rate)
update_step = optimizer.apply_gradients(
zip(clipped_gradients, params))
在我們自己的實(shí)驗(yàn)中,我們使用標(biāo)準(zhǔn)SGD(tf.train.GradientDescentOptimizer)慷妙,其學(xué)習(xí)速率逐步降低僻焚,這樣可以獲得更好的性能。 參見 benchmarks膝擂。
未完待續(xù)