解碼器 | 基于 Transformers 的編碼器-解碼器模型

基于 transformer 的編碼器-解碼器模型是?表征學(xué)習(xí)?和?模型架構(gòu)?這兩個(gè)領(lǐng)域多年研究成果的結(jié)晶。本文簡(jiǎn)要介紹了神經(jīng)編碼器-解碼器模型的歷史,更多背景知識(shí),建議讀者閱讀由 Sebastion Ruder 撰寫(xiě)的這篇精彩?博文。此外,建議讀者對(duì)?自注意力 (self-attention) 架構(gòu)?有一個(gè)基本了解,可以閱讀 Jay Alammar 的?這篇博文?復(fù)習(xí)一下原始 transformer 模型。
本文分 4 個(gè)部分:
背景 -?簡(jiǎn)要回顧了神經(jīng)編碼器-解碼器模型的歷史,重點(diǎn)關(guān)注基于 RNN 的模型。
編碼器-解碼器 -?闡述基于 transformer 的編碼器-解碼器模型,并闡述如何使用該模型進(jìn)行推理。
編碼器 -?闡述模型的編碼器部分。
解碼器 -?闡述模型的解碼器部分。
每個(gè)部分都建立在前一部分的基礎(chǔ)上,但也可以單獨(dú)閱讀。這篇分享是最后一部分?解碼器。
解碼器
如?編碼器-解碼器?部分所述,?基于 transformer?的解碼器定義了給定上下文編碼序列條件下目標(biāo)序列的條件概率分布:
pθdec(Y1:m∣X1:n)
根據(jù)貝葉斯法則,在給定上下文編碼序列和每個(gè)目標(biāo)變量的所有前驅(qū)目標(biāo)向量的條件下,可將上述分布分解為每個(gè)目標(biāo)向量的條件分布的乘積:
pθdec(Y1:m∣X1:n)=∏i=1mpθdec(yi∣Y0:i?1,X1:n)
我們首先了解一下基于 transformer 的解碼器如何定義概率分布。基于 transformer 的解碼器由很多?解碼器模塊?堆疊而成,最后再加一個(gè)線性層 (即 “LM 頭”)。這些解碼器模塊的堆疊將上下文相關(guān)的編碼序列?X1:n?和每個(gè)目標(biāo)向量的前驅(qū)輸入?Y0:i?1?(這里?y0?為 BOS) 映射為目標(biāo)向量的編碼序列?Y0:i?1。然后,“LM 頭”將目標(biāo)向量的編碼序列?Y0:i?1?映射到 logit 向量序列?L1:n=l1,…,ln, 而每個(gè) logit 向量li?的維度即為詞表的詞匯量。這樣,對(duì)于每個(gè)?i∈1,…,n,其在整個(gè)詞匯表上的概率分布可以通過(guò)對(duì)?li?取 softmax 獲得。公式如下:
pθdec(yi∣Y0:i?1,X1:n),?i∈1,…,n
“LM 頭” 即為詞嵌入矩陣的轉(zhuǎn)置,?即?Wemb?=[y1,…,yvocab]T?1。直觀上來(lái)講,這意味著對(duì)于所有?i∈0,…,n?1?“LM 頭” 層會(huì)將?yi?與詞匯表?y1,…,yvocab?中的所有詞嵌入一一比較,輸出的 logit 向量?li+1?即表示?yi?與每個(gè)詞嵌入之間的相似度。Softmax 操作只是將相似度轉(zhuǎn)換為概率分布。對(duì)于每個(gè)?i∈1,…,n,以下等式成立:
pθdec(y∣X1:n,Y0:i?1)?=Softmax(fθdec(X1:n,Y0:i?1))?=Softmax(Wemb?yi?1)?=Softmax(li)
總結(jié)一下,為了對(duì)目標(biāo)向量序列?Y1:m?的條件分布建模,先在目標(biāo)向量?Y1:m?1?前面加上特殊的?BOS?向量 (?即?y0),并將其與上下文相關(guān)的編碼序列?X1:n?一起映射到 logit 向量序列?L1:m。然后,使用 softmax 操作將每個(gè) logit 目標(biāo)向量?li?轉(zhuǎn)換為目標(biāo)向量?yi?的條件概率分布。最后,將所有目標(biāo)向量的條件概率?y1,…,ym?相乘得到完整目標(biāo)向量序列的條件概率:
pθdec(Y1:m∣X1:n)=∏i=1mpθdec(yi∣Y0:i?1,X1:n).
與基于 transformer 的編碼器不同,在基于 transformer 的解碼器中,其輸出向量?yi?1?應(yīng)該能很好地表征?下一個(gè)?目標(biāo)向量 (即?yi),而不是輸入向量本身 (即?yi?1)。此外,輸出向量?yi?1?應(yīng)基于編碼器的整個(gè)輸出序列?X1:n。為了滿(mǎn)足這些要求,每個(gè)解碼器塊都包含一個(gè)?單向自注意層,緊接著是一個(gè)?交叉注意層,最后是兩個(gè)前饋層2。單向自注意層將其每個(gè)輸入向量?y′j?僅與其前驅(qū)輸入向量?y′i?(其中?i≤j,且?j∈1,…,n) 相關(guān)聯(lián),來(lái)模擬下一個(gè)目標(biāo)向量的概率分布。交叉注意層將其每個(gè)輸入向量?y′′j?與編碼器輸出的所有向量?X1:n?相關(guān)聯(lián),來(lái)根據(jù)編碼器輸入預(yù)測(cè)下一個(gè)目標(biāo)向量的概率分布。
好,我們?nèi)砸杂⒄Z(yǔ)到德語(yǔ)翻譯為例可視化一下?基于 transformer?的解碼器。

我們可以看到解碼器將?Y0:5: “BOS”、“Ich”、“will”、“ein”、“Auto”、“kaufen” (圖中以淺紅色顯示) 和 “I”、“want”、“to”、“buy”、“a”、“car”、“EOS” (?即?X1:7?(圖中以深綠色顯示)) 映射到 logit 向量?L1:6?(圖中以深紅色顯示)。
因此,對(duì)每個(gè)?l1、l2、…、l6?使用 softmax 操作可以定義下列條件概率分布:
pθdec(y∣BOS,X1:7),
pθdec(y∣BOS?Ich,X1:7),?…,?pθdec(y∣BOS?Ich?will?ein?Auto?kaufen,X1:7)
總條件概率如下:
pθdec(Ich?will?ein?Auto?kaufen?EOS∣X1:n)
其可表示為以下乘積形式:
pθdec(Ich∣BOS,X1:7)×…×pθdec(EOS∣BOS?Ich?will?ein?Auto?kaufen,X1:7)
圖右側(cè)的紅框顯示了前三個(gè)目標(biāo)向量?y0、y1、?y2?在一個(gè)解碼器模塊中的行為。下半部分說(shuō)明了單向自注意機(jī)制,中間說(shuō)明了交叉注意機(jī)制。我們首先關(guān)注單向自注意力。
與雙向自注意一樣,在單向自注意中,?query
?向量?q0,…,qm?1?(如下圖紫色所示),?key
?向量?k0,…,km?1?(如下圖橙色所示),和?value
?向量?v0,…,vm?1?(如下圖藍(lán)色所示) 均由輸入向量?y′0,…,y′m?1?(如下圖淺紅色所示) 映射而來(lái)。然而,在單向自注意力中,每個(gè)?query
?向量?qi?僅?與當(dāng)前及之前的?key
?向量進(jìn)行比較 (即?k0,…,ki) 并生成各自的?注意力權(quán)重?。這可以防止輸出向量?y′′j?(如下圖深紅色所示) 包含未來(lái)向量 (yi,其中?i>j?且?j∈0,…,m?1) 的任何信息 。與雙向自注意力的情況一樣,得到的注意力權(quán)重會(huì)乘以它們各自的?value
?向量并加權(quán)求和。
我們將單向自注意力總結(jié)如下:
y′′i=V0:iSoftmax(K0:i?qi)+y′i
請(qǐng)注意,?key
?和?value
?向量的索引范圍都是?0:i?而不是?0:m?1,0:m?1?是雙向自注意力中?key
?向量的索引范圍。
下圖顯示了上例中輸入向量?y′1?的單向自注意力。

可以看出?y′′1?只依賴(lài)于?y′0?和?y′1。因此,單詞 “Ich” 的向量表征 (?即?y′1) 僅與其自身及 “BOS” 目標(biāo)向量 (?即?y′0) 相關(guān)聯(lián),而?不?與 “will” 的向量表征 (?即?y′2) 相關(guān)聯(lián)。
那么,為什么解碼器使用單向自注意力而不是雙向自注意力這件事很重要呢?如前所述,基于 transformer 的解碼器定義了從輸入向量序列?Y0:m?1?到其?下一個(gè)?解碼器輸入的 logit 向量的映射,即?L1:m。舉個(gè)例子,輸入向量?y1?= “Ich” 會(huì)映射到 logit 向量?l2,并用于預(yù)測(cè)下一個(gè)輸入向量?y2。因此,如果?y′1?可以獲取后續(xù)輸入向量?Y′2:5的信息,解碼器將會(huì)簡(jiǎn)單地復(fù)制向量 “will” 的向量表征 (?即?y′2) 作為其輸出?y′′1,并就這樣一直傳播到最后一層,所以最終的輸出向量?y1?基本上就只對(duì)應(yīng)于?y2?的向量表征,并沒(méi)有起到預(yù)測(cè)的作用。
這顯然是不對(duì)的,因?yàn)檫@樣的話,基于 transformer 的解碼器永遠(yuǎn)不會(huì)學(xué)到在給定所有前驅(qū)詞的情況下預(yù)測(cè)下一個(gè)詞,而只是對(duì)所有?i∈1,…,m,通過(guò)網(wǎng)絡(luò)將目標(biāo)向量?yi?復(fù)制到?yi?1。以下一個(gè)目標(biāo)變量本身為條件去定義下一個(gè)目標(biāo)向量,即從?p(y∣Y0:i,X)?中預(yù)測(cè)?yi, 顯然是不對(duì)的。因此,單向自注意力架構(gòu)允許我們定義一個(gè)?因果的?概率分布,這對(duì)有效建模下一個(gè)目標(biāo)向量的條件分布而言是必要的。
太棒了!現(xiàn)在我們可以轉(zhuǎn)到連接編碼器和解碼器的層 -?交叉注意力?機(jī)制!
交叉注意層將兩個(gè)向量序列作為輸入: 單向自注意層的輸出?Y′′0:m?1?和編碼器的輸出?X1:n。與自注意力層一樣,?query
?向量?q0,…,qm?1?是上一層輸出向量?Y′′0:m?1?的投影。而?key
?和?value
?向量?k0,…,kn?1、v0,…,vn?1?是編碼器輸出向量?X1:n?的投影。定義完?key
?、value
?和?query
?向量后,將?query
?向量?qi?與?所有?key
?向量進(jìn)行比較,并用各自的得分對(duì)相應(yīng)的?value
?向量進(jìn)行加權(quán)求和。這個(gè)過(guò)程與?雙向?自注意力對(duì)所有?i∈0,…,m?1?求?y′′′i?是一樣的。交叉注意力可以概括如下:
y′′′i=V1:nSoftmax(K1:n?qi)+y′′i
注意,key
?和?value
?向量的索引范圍是?1:n,對(duì)應(yīng)于編碼器輸入向量的數(shù)目。
我們用上例中輸入向量?y′′1?來(lái)圖解一下交叉注意力機(jī)制。

我們可以看到?query
?向量?q1(紫色)源自?y′′1(紅色),因此其依賴(lài)于單詞 "Ich" 的向量表征。然后將?query
?向量?q1?與對(duì)應(yīng)的?key
?向量?k1,…,k7(黃色)進(jìn)行比較,這里的?key
?向量對(duì)應(yīng)于編碼器對(duì)其輸入?X1:n?= "I want to buy a car EOS" 的上下文相關(guān)向量表征。這將 "Ich" 的向量表征與所有編碼器輸入向量直接關(guān)聯(lián)起來(lái)。最后,將注意力權(quán)重乘以?value
?向量?v1,…,v7(青綠色)并加上輸入向量?y′′1?最終得到輸出向量?y′′′1(深紅色)。
所以,直觀而言,到底發(fā)生了什么?每個(gè)輸出向量?y′′′i?是由所有從編碼器來(lái)的?value
?向量(v1,…,v7?)的加權(quán)和與輸入向量本身?y′′i?相加而得(參見(jiàn)上圖所示的公式)。其關(guān)鍵思想是:?來(lái)自解碼器的?qi?的?query
?投影與?來(lái)自編碼器的?kj?越相關(guān),其對(duì)應(yīng)的?vj?對(duì)輸出的影響越大。
酷!現(xiàn)在我們可以看到這種架構(gòu)的每個(gè)輸出向量?y′′′i?取決于其來(lái)自編碼器的輸入向量?X1:n?及其自身的輸入向量?y′′i。這里有一個(gè)重要的點(diǎn),在該架構(gòu)中,雖然輸出向量?y′′′i?依賴(lài)來(lái)自編碼器的輸入向量?X1:n,但其完全獨(dú)立于該向量的數(shù)量?n。所有生成?key
?向量?k1,…,kn?和?value
?向量?v1,…,vn?的投影矩陣?Wkcross?和?Wvcross?都是與?n?無(wú)關(guān)的,所有?n?共享同一個(gè)投影矩陣。且對(duì)每個(gè)?y′′′i,所有?value
?向量?v1,…,vn?被加權(quán)求和至一個(gè)向量。至此,關(guān)于為什么基于 transformer 的解碼器沒(méi)有遠(yuǎn)程依賴(lài)問(wèn)題而基于 RNN 的解碼器有
這一問(wèn)題的答案已經(jīng)很顯然了。因?yàn)槊總€(gè)解碼器 logit 向量?直接?依賴(lài)于每個(gè)編碼后的輸出向量,因此比較第一個(gè)編碼輸出向量和最后一個(gè)解碼器 logit 向量只需一次操作,而不像 RNN 需要很多次。
總而言之,單向自注意力層負(fù)責(zé)基于當(dāng)前及之前的所有解碼器輸入向量建模每個(gè)輸出向量,而交叉注意力層則負(fù)責(zé)進(jìn)一步基于編碼器的所有輸入向量建模每個(gè)輸出向量。
為了驗(yàn)證我們對(duì)該理論的理解,我們繼續(xù)上面編碼器部分的代碼,完成解碼器部分。
1?詞嵌入矩陣?Wemb?為每個(gè)輸入詞提供唯一的?上下文無(wú)關(guān)?向量表示。這個(gè)矩陣通常也被用作 “LM 頭”,此時(shí) “LM 頭”可以很好地完成“編碼向量到 logit” 的映射。
2?與編碼器部分一樣,本文不會(huì)詳細(xì)解釋前饋層在基于 transformer 的模型中的作用。Yun 等 (2017)?的工作認(rèn)為前饋層對(duì)于將每個(gè)上下文相關(guān)向量?x′i?映射到所需的輸出空間至關(guān)重要,僅靠自注意力層無(wú)法完成。這里應(yīng)該注意,每個(gè)輸出詞元?x′?對(duì)應(yīng)的前饋層是相同的。有關(guān)更多詳細(xì)信息,建議讀者閱讀論文。
python
from transformers import MarianMTModel, MarianTokenizerimport torchtokenizer = MarianTokenizer.from_pretrained("Helsinki-NLP/opus-mt-en-de")model = MarianMTModel.from_pretrained("Helsinki-NLP/opus-mt-en-de")embeddings = model.get_input_embeddings()# create token ids for encoder inputinput_ids = tokenizer("I want to buy a car", return_tensors="pt").input_ids# pass input token ids to encoderencoder_output_vectors = model.base_model.encoder(input_ids, return_dict=True).last_hidden_state# create token ids for decoder inputdecoder_input_ids = tokenizer("<pad> Ich will ein", return_tensors="pt", add_special_tokens=False).input_ids# pass decoder input ids and encoded input vectors to decoderdecoder_output_vectors = model.base_model.decoder(decoder_input_ids, encoder_hidden_states=encoder_output_vectors).last_hidden_state# derive embeddings by multiplying decoder outputs with embedding weightslm_logits = torch.nn.functional.linear(decoder_output_vectors, embeddings.weight, bias=model.final_logits_bias)# change the decoder input slightlydecoder_input_ids_perturbed = tokenizer("<pad> Ich will das", return_tensors="pt", add_special_tokens=False).input_idsdecoder_output_vectors_perturbed = model.base_model.decoder(decoder_input_ids_perturbed, encoder_hidden_states=encoder_output_vectors).last_hidden_statelm_logits_perturbed = torch.nn.functional.linear(decoder_output_vectors_perturbed, embeddings.weight, bias=model.final_logits_bias)# compare shape and encoding of first vectorprint(f"Shape of decoder input vectors {embeddings(decoder_input_ids).shape}. Shape of decoder logits {lm_logits.shape}")# compare values of word embedding of "I" for input_ids and perturbed input_idsprint("Is encoding for `Ich` equal to its perturbed version?: ", torch.allclose(lm_logits[0, 0], lm_logits_perturbed[0, 0], atol=1e-3))
輸出:
? ?Shape of decoder input vectors torch.Size([1, 5, 512]). Shape of decoder logits torch.Size([1, 5, 58101]) ? ?Is encoding for `Ich` equal to its perturbed version?: True
我們首先比較解碼器詞嵌入層的輸出維度?embeddings(decoder_input_ids)
?(對(duì)應(yīng)于?Y0:4,這里?<pad>
?對(duì)應(yīng)于 BOS 且 "Ich will das" 被分為 4 個(gè)詞) 和?lm_logits
?(對(duì)應(yīng)于?L1:5) 的維度。此外,我們還通過(guò)解碼器將單詞序列 “<pad>
?Ich will ein” 和其輕微改編版 “<pad>
?Ich will das” 與?encoder_output_vectors
?一起傳遞給解碼器,以檢查對(duì)應(yīng)于 “Ich” 的第二個(gè) lm_logit 在僅改變輸入序列中的最后一個(gè)單詞 (“ein” -> “das”) 時(shí)是否會(huì)有所不同。
正如預(yù)期的那樣,解碼器輸入詞嵌入和 lm_logits 的輸出,?即?Y0:4?和?L1:5?的最后一個(gè)維度不同。雖然序列長(zhǎng)度相同 (=5),但解碼器輸入詞嵌入的維度對(duì)應(yīng)于?model.config.hidden_size
,而?lm_logit
?的維數(shù)對(duì)應(yīng)于詞匯表大小?model.config.vocab_size
。其次,可以注意到,當(dāng)將最后一個(gè)單詞從 “ein” 變?yōu)?“das”,l1=“Ich”?的輸出向量的值不變。鑒于我們已經(jīng)理解了單向自注意力,這就不足為奇了。
最后一點(diǎn),?自回歸?模型,如 GPT2,與刪除了交叉注意力層的?基于 transformer?的解碼器模型架構(gòu)是相同的,因?yàn)榧冏曰貧w模型不依賴(lài)任何編碼器的輸出。因此,自回歸模型本質(zhì)上與?自編碼?模型相同,只是用單向注意力代替了雙向注意力。這些模型還可以在大量開(kāi)放域文本數(shù)據(jù)上進(jìn)行預(yù)訓(xùn)練,以在自然語(yǔ)言生成 (NLG) 任務(wù)中表現(xiàn)出令人印象深刻的性能。在?Radford 等 (2019)?的工作中,作者表明預(yù)訓(xùn)練的 GPT2 模型無(wú)需太多微調(diào)即可在多種 NLG 任務(wù)上取得達(dá)到 SOTA 或接近 SOTA 的結(jié)果。你可以在?此處?獲取所有 ?? transformers 支持的?自回歸?模型的信息。
好了!至此,你應(yīng)該已經(jīng)很好地理解了?基于 transforemr?的編碼器-解碼器模型以及如何在 ?? transformers 庫(kù)中使用它們。
非常感謝 Victor Sanh、Sasha Rush、Sam Shleifer、Oliver ?strand、Ted Moskovitz 和 Kristian Kyvik 提供的寶貴反饋。
附錄
如上所述,以下代碼片段展示了如何為?基于 transformer?的編碼器-解碼器模型編寫(xiě)一個(gè)簡(jiǎn)單的生成方法。在這里,我們使用?torch.argmax
?實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的?貪心?解碼法來(lái)對(duì)目標(biāo)向量進(jìn)行采樣。
python
from transformers import MarianMTModel, MarianTokenizerimport torchtokenizer = MarianTokenizer.from_pretrained("Helsinki-NLP/opus-mt-en-de")model = MarianMTModel.from_pretrained("Helsinki-NLP/opus-mt-en-de")# create ids of encoded input vectorsinput_ids = tokenizer("I want to buy a car", return_tensors="pt").input_ids# create BOS tokendecoder_input_ids = tokenizer("<pad>", add_special_tokens=False, return_tensors="pt").input_idsassert decoder_input_ids[0, 0].item() == model.config.decoder_start_token_id, "`decoder_input_ids` should correspond to `model.config.decoder_start_token_id`"# STEP 1# pass input_ids to encoder and to decoder and pass BOS token to decoder to retrieve first logitoutputs = model(input_ids, decoder_input_ids=decoder_input_ids, return_dict=True)# get encoded sequenceencoded_sequence = (outputs.encoder_last_hidden_state,)# get logitslm_logits = outputs.logits# sample last token with highest probnext_decoder_input_ids = torch.argmax(lm_logits[:, -1:], axis=-1)# concatdecoder_input_ids = torch.cat([decoder_input_ids, next_decoder_input_ids], axis=-1)# STEP 2# reuse encoded_inputs and pass BOS + "Ich" to decoder to second logitlm_logits = model(None, encoder_outputs=encoded_sequence, decoder_input_ids=decoder_input_ids, return_dict=True).logits# sample last token with highest prob againnext_decoder_input_ids = torch.argmax(lm_logits[:, -1:], axis=-1)# concat againdecoder_input_ids = torch.cat([decoder_input_ids, next_decoder_input_ids], axis=-1)# STEP 3lm_logits = model(None, encoder_outputs=encoded_sequence, decoder_input_ids=decoder_input_ids, return_dict=True).logitsnext_decoder_input_ids = torch.argmax(lm_logits[:, -1:], axis=-1)decoder_input_ids = torch.cat([decoder_input_ids, next_decoder_input_ids], axis=-1)# let's see what we have generated so far!print(f"Generated so far: {tokenizer.decode(decoder_input_ids[0], skip_special_tokens=True)}")# This can be written in a loop as well.
輸出:
? ?Generated so far: Ich will ein
在這個(gè)示例代碼中,我們準(zhǔn)確地展示了正文中描述的內(nèi)容。我們?cè)谳斎?“I want to buy a car” 前面加上?BOS?,然后一起傳給編碼器-解碼器模型,并對(duì)第一個(gè) logit?l1?(對(duì)應(yīng)代碼中第一次出現(xiàn) lm_logits 的部分) 進(jìn)行采樣。這里,我們的采樣策略很簡(jiǎn)單: 貪心地選擇概率最高的詞作為下一個(gè)解碼器輸入向量。然后,我們以自回歸方式將采樣得的解碼器輸入向量與先前的輸入一起傳遞給編碼器-解碼器模型并再次采樣。重復(fù) 3 次后,該模型生成了 “Ich will ein”。結(jié)果沒(méi)問(wèn)題,開(kāi)了個(gè)好頭。
在實(shí)踐中,我們會(huì)使用更復(fù)雜的解碼方法來(lái)采樣?lm_logits
。你可以參考?這篇博文?了解更多的解碼方法。
至此,《基于 Transformers 的編碼器-解碼器模型》的四個(gè)部分就全部分享完啦,歡迎大家閱讀其他分享 ??!
英文原文:?https://hf.co/blog/encoder-decoder
原文作者: Patrick von Platen
譯者: Matrix Yao (姚偉峰),英特爾深度學(xué)習(xí)工程師,工作方向?yàn)?transformer-family 模型在各模態(tài)數(shù)據(jù)上的應(yīng)用及大規(guī)模模型的訓(xùn)練推理。
審校/排版: zhongdongy (阿東)