如何用不同解碼方法實現Transformer語言生成

近年來,隨著在數百萬個網頁上訓練的大型transformer-based語言模型的興起,人們對開放式語言生成的興趣越來越濃厚。

條件開放式語言生成的成果令人印象深刻,除了改進的transformer架構和大量的無監督訓練數據,好的解碼方法也起到了重要的作用。

這篇博客文章簡要概述了不同的解碼策略,更重要的是展示瞭如何輕鬆使用流行的transformer 庫實現它們!


原文如下:

自迴歸語言生成基於一個假設,即一個單詞序列的概率分佈可以分解為條件下一個單詞分佈的乘積:

如何用不同解碼方法實現Transformer語言生成

W0W0 是初始上下文字序列。單詞序列的長度TT通常是即時確定的,並且與時間步長t=T對應,EOS令牌是根據P(wt∣w1:t−1,W0)生成的。

在PyTorch和Tensorflow >= 2.0中,GPT2、XLNet、OpenAi-GPT、CTRL、TransfoXL、XLM、Bart、T5都可以使用自迴歸語言生成!

我們將介紹目前最著名的解碼方法,主要有貪心搜索(greedy search)、集束搜索(beam search)、Top-K採樣和Top-p採樣。

快速安裝transformers 並加載模型。我們將在Tensorflow 2.1中使用GPT2進行演示,但是PyTorch的API是1對1的。


!pip install -q git+https://github.com/huggingface/transformers.git

!pip install -q tensorflow==2.1

importtensorflow astf

fromtransformers importTFGPT2LMHeadModel, GPT2Tokenizer

tokenizer = GPT2Tokenizer.from_pretrained("gpt2")

# add the EOS token as PAD token to avoid warnings

model=TFGPT2LMHeadModel.from_pretrained("gpt2", pad_token_id=tokenizer.eos_token_id)


Greedy Search(貪心搜索)


貪心搜索只是在每個timestep t中選擇概率最高的單詞作為下一個單詞: wt = argmaxwP(w∣w1:t-1)。下面的示意圖展示了貪心搜索:

如何用不同解碼方法實現Transformer語言生成

算法從單詞“the”開始,選擇概率最高的下一個單詞“nice”,以此類推,最終生成的單詞序列是“the”、“nice”、“woman”,總體概率為0.5×0.4=0.2

接下來,我們將在上下文中使用GPT2生成單詞序列(“I”、“enjoy”、“walking”、“with”、“my”、“cute”、“dog”)。來看看貪心搜索是如何在transformers中使用的:

# encode context the generation is conditioned on

<code>input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='tf')/<code>
<code> /<code>
<code># generate text until the output length (which includes the context length) reaches 50/<code>
<code>greedy_output = model.generate(input_ids, max_length=50)/<code>
<code> /<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>print(tokenizer.decode(greedy_output[0], skip_special_tokens=True))/<code>
<code>
<code>I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with my dog. I'm not sure if I'll ever be able to walk with my dog./<code>
<code> /<code>

I'm not sure if I'll

我們使用GPT2生成了第一個簡短文本。根據上下文生成的單詞是合理的,但是模型很快就會開始重複!一般來說,這是語言生成中一個非常普遍的問題,在貪心和波束搜索中似乎更是如此。


貪心搜索的主要缺點是,它錯過了隱藏在低概率單詞後面的高概率單詞。


如上圖所示:單詞“has”的條件概率高達0.9,隱藏在單詞“dog”後面,而“dog”的條件概率僅次於“dog”,因此貪婪搜索會漏掉單詞序列“The”、“dog”、“has”。

還好,我們有了集束搜索(beam search)來緩解這個問題!

beam search(集束搜索)

集束搜索通過在每個time step保留最可能的num_beams ,並最終選擇總體概率最高的假設,從而降低了丟失隱藏的高概率單詞序列的風險。


讓我們用num_beams = 2進行說明:

如何用不同解碼方法實現Transformer語言生成


在time step 11,除了最可能的假設“ The”,“ woman”之外,集束搜索還跟蹤第二個最可能的假設“ The”、“ dog”。在time step 22,集束搜索發現單詞序列"The", "dog", "has"的概率比“ The”,“ nice”,“ woman”(0.2)高0.36。很好,它發現了示例中最可能的單詞序列!

集束搜索將始終找到比貪心搜索具有更高概率的輸出序列,但不能保證找到最有可能的輸出。

讓我們看看如何在transformers中使用集束搜索。我們設置num_beam > 1和early_stop =True,這樣當所有集束假設到達EOS令牌時,生成就完成了。


<code># activate beam search and early_stopping/<code>
<code>beam_output = model.generate(/<code>
<code>    input_ids,/<code>
<code>    max_length=50,/<code>
<code>    num_beams=5,/<code>
<code>    early_stopping=True/<code>
<code>)/<code>
<code> /<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>print(tokenizer.decode(beam_output[0], skip_special_tokens=True))/<code>
<code>----------------------------------------------------------------------------------------------------/<code>
<code>I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with him again./<code>
<code> /<code>

I'm not sure if I'll ever be able to walk with him again. I'm not sure if I'll


雖然結果可能更流暢,但輸出仍然包含相同單詞序列的重複。


一個簡單的補救方法是引入n-g(也就是n-g),是由Paulus和Klein等人提出的懲罰。最常見的n-grams懲罰是手動設置下一個單詞出現的概率為0,以確保n-grams不會出現兩次。


通過設置no_repeat_ngram_size=2來嘗試一下,這樣就不會出現兩次2-gram:


# set no_repeat_ngram_size to 2

<code>beam_output = model.generate(/<code>
<code>    input_ids,/<code>
<code>    max_length=50,/<code>
<code>    num_beams=5,/<code>
<code>    no_repeat_ngram_size=2,/<code>
<code>    early_stopping=True/<code>
<code>)/<code>
<code> /<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>print(tokenizer.decode(beam_output[0], skip_special_tokens=True))/<code>
<code>----------------------------------------------------------------------------------------------------/<code>
<code>I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with him again./<code>
<code> /<code>
<code>I've been thinking about this for a while now, and I think it's time for me to take a break/<code> 


不錯,看起來好多了!重複不再出現。然而,必須謹慎使用n-grams懲罰。在一篇關於紐約市的文章不應該使用2-gram的懲罰或,否則,全文中城市名稱只會出現一次!


集束搜索的另一個重要特性是,可以比較生成後的頂部集束,並選擇最適合目標的集束。


在transformers中,我們簡單地將參數num_return_sequences設置為應返回的最高分集束的數量。確保num_return_sequences <= num_beam !


# set return_num_sequences > 1

<code>beam_outputs = model.generate(/<code>
<code>    input_ids,/<code>
<code>    max_length=50,/<code>
<code>    num_beams=5,/<code>
<code>    no_repeat_ngram_size=2,/<code>
<code>    num_return_sequences=5,/<code>
<code>    early_stopping=True/<code>
<code>)/<code>
<code> /<code>
<code># now we have 3 output sequences/<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>for i, beam_output in enumerate(beam_outputs):/<code>
<code>  print("{}: {}".format(i, tokenizer.decode(beam_output, skip_special_tokens=True)))/<code>
<code>----------------------------------------------------------------------------------------------------/<code>
<code>0: I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with him again./<code>
<code> /<code>
<code>I've been thinking about this for a while now, and I think it's time for me to take a break/<code>
<code>1: I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with him again./<code>
<code> /<code>
<code>I've been thinking about this for a while now, and I think it's time for me to get back to/<code>
<code>2: I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with her again./<code>
<code> /<code>
<code>I've been thinking about this for a while now, and I think it's time for me to take a break/<code>
<code>3: I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with her again./<code>
<code> /<code>
<code>I've been thinking about this for a while now, and I think it's time for me to get back to/<code>
<code>4: I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with him again./<code>


<code>I've been thinking about this for a while now, and I think it's time for me to take a step/<code>


可以看出,當只使用5個集束時,5個集束假設之間的差別很小。

在開放式的生成中,關於集束可能搜索不是最好選擇的問題,有幾個原因最近被提出來了。

集束搜索可以很好地完成任務——在機器翻譯或摘要中,期望生成的長度或多或少是可預測的。但這並不是開放式的生成,期望輸出的長度可能會有很大的變化,例如對話和故事生成。

我們已經看到,集束搜索嚴重地受到重複生成的影響,這在故事生成過程中更加難以控制,因為要在強制的“不重複”和重複的n-grams 之間找到一個平衡需要大量的微調。

正如Ari Holtzman等人所指出的,高質量的人類語言並不遵循高概率的單詞分佈。


換句話說,作為人類,我們希望生成的文本能給我們帶來驚喜,而不是枯燥/可預測的。作者通過繪製概率圖很好地展示了這一點,一個模型將給出人類文本和集束搜索的結果。


如何用不同解碼方法實現Transformer語言生成


因此,讓我們引入一些隨機性。

Sampling(採樣)


在最基本的形式中,採樣是指根據其條件概率分佈隨機選擇下一個單詞wt:


如何用不同解碼方法實現Transformer語言生成


以上面的示例為例,下圖可視化了採樣時的語言生成。


如何用不同解碼方法實現Transformer語言生成

很明顯,使用抽樣的語言生成不再是確定性的。單詞“car”由條件概率分佈P(w∣“The”)進行採樣,然後由P(w∣“The”,“car”)進行採樣。

在transformer中,我們設置do_sample=True,並通過top_k=0停止使用Top-K採樣(稍後會詳細介紹)。在接下來的代碼中,我們將修復random_seed=0,以便進行演示。你可以隨意更改random_seed來使用模型。

<code>tf.random.set_seed(0)/<code>

# set seed to reproduce results. Feel free to change the seed though to get different results

<code># activate sampling and deactivate top_k by setting top_k sampling to 0/<code>
<code>sample_output = model.generate(/<code>
<code>    input_ids,/<code>
<code>    do_sample=True,/<code>
<code>    max_length=50,/<code>
<code>    top_k=0/<code>
<code>)/<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>print(tokenizer.decode(sample_output[0], skip_special_tokens=True))/<code>
<code>----------------------------------------------------------------------------------------------------/<code>
<code>I enjoy walking with my cute dog. He just gave me a whole new hand sense."/<code>
<code> /<code>
<code>But it seems that the dogs have learned a lot from teasing at the local batte harness once they take on the outside./<code>
<code>"I take/<code>
<code>"I take/<code>


文字似乎還不錯,但通過仔細觀察,它並不是很連貫。3-grams new hand和local batte harness非常怪異。在對單詞序列進行採樣時,模型通常會產生不連貫的亂碼,這是一個大問題。


一個訣竅是通過降低softmax溫度,使分佈P(w∣w1:t−1)變得更清晰(增加高概率單詞的可能性,降低低概率單詞的可能性)。


對我們的示例應用溫度的說明如下所示。


如何用不同解碼方法實現Transformer語言生成


步驟t=1的條件下的單詞分佈變得更加清晰,幾乎沒有機會選擇單詞“car”。


讓我們看看如何通過設置來冷卻庫中的分佈


# set seed to reproduce results. Feel free to change the seed though to get different results

<code>tf.random.set_seed(0)/<code>
<code># use temperature to decrease the sensitivity to low probability candidates/<code>
<code>sample_output = model.generate(/<code>
<code>    input_ids,/<code>
<code>    do_sample=True,/<code>
<code>    max_length=50,/<code>
<code>    top_k=0,/<code>
<code>    temperature=0.7/<code>
<code>)/<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>print(tokenizer.decode(sample_output[0], skip_special_tokens=True))/<code>
<code>----------------------------------------------------------------------------------------------------/<code>
<code>I enjoy walking with my cute dog, but I don't like to be at home too much. I also/<code>


n-grams減少了,輸出也更連貫了!雖然應用溫度可以使分佈的隨機性降低,但在其限制下,當將溫度$ \\設置為0$時,按比例進行的溫度採樣就等於貪心解碼,會出現和以前一樣的問題。

Top-K抽樣


Fan等人提出了一種簡單但非常強大的抽樣方案,稱為Top-K抽樣。


在Top-K抽樣中,對K個最有可能的下一個單詞進行過濾,並將概率質量重新分配到下一個單詞中。GPT2採用了這種抽樣方案,這是它成功生成故事的原因之一。


為了更好地說明Top-K抽樣,我們將上面示例中兩個抽樣步驟所使用的單詞範圍從3個單詞擴展到10個單詞。


如何用不同解碼方法實現Transformer語言生成


設置K=6後,在這兩個採樣步驟中,我們將採樣池限制為6個單詞。最有可能的6個單詞,定義為Vtop-K,這隻佔整個概率質量的2 / 3,它包括第二步中幾乎所有的概率質量。儘管如此,我們可以看到,在第二個採樣步驟中,它成功地排除了“not”、“the”、“small”等奇怪的候選項。


來看看Top-K如何在庫中使用,通過設置top_k=50:

<code># set seed to reproduce results. Feel free to change the seed though to get different results/<code>
<code>tf.random.set_seed(0)/<code>
<code># set top_k to 50/<code>
<code>sample_output = model.generate(/<code>
<code>    input_ids,/<code>
<code>    do_sample=True,/<code>
<code>    max_length=50,/<code>
<code>    top_k=50/<code>
<code>)/<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>print(tokenizer.decode(sample_output[0], skip_special_tokens=True))/<code>
<code>----------------------------------------------------------------------------------------------------/<code>
<code>I enjoy walking with my cute dog. It's so good to have an environment where your dog is available to share with you and we'll be taking care of you./<code>
<code>We hope you'll find this story interesting!/<code>
<code>I am from/<code>


一點也不差!該文本可以說是迄今為止最人性化的文本。儘管使用Top-K採樣時需要關注的一個問題是,它不能動態調整從下一個單詞概率分佈P(w∣w1:t-1)中過濾的單詞數量。這可能是有問題的,


因為有些單詞可能是從一個非常明顯的分佈(上圖中右側的分佈)中採樣的,而另一些單詞則是從更平坦的分佈中採樣的(上圖左側的分佈)。

在步驟t=1中,Top-K排除了對“people”、“big”、“house”、“cat” 進行抽樣可能性,這些似乎都是合理的候選者。另一方面,在步驟t=2中,該方法將可能不合適的單詞“down”、“a”包含在單詞樣本庫中。

因此,因此,將樣本池限制為固定大小K可能會危害模型以產生亂序的尖峰分佈,並限制模型用於平面分佈的創造力。這種直覺促使Ari Holtzman等人創建了Top-p或nucleus採樣。


Top-p (nucleus) sampling


在Top-p抽樣中, 不是僅從最有可能的K個單詞中進行抽樣,而是從累積概率超過概率p的最小單詞集進行抽樣,然後將概率質量重新分配到這組單詞中。這樣,單詞集合的大小(也就是集合中單詞的數量)可以根據下一個單詞的概率分佈動態地增加和減少。


如何用不同解碼方法實現Transformer語言生成


在設定p=0.92的情況下,Top-p抽樣選取的最小字數超過p=92的概率質量,定義為Vtop-p。在第一個例子中,它包含了9個最可能的單詞,而在第二個例子中,它只需要選擇前3個單詞就可以超過92%。實際上很簡單!可以看出,它保留了大量的單詞,而下一個單詞的可預測性可能較差,例如P(w∣the);


只有幾個詞時,下一個詞似乎更容易預測,例如P(w∣the”,“car”)。

好了,該用transformers檢查一下了!通過設置0


# set seed to reproduce results. Feel free to change the seed though to get different results

<code>tf.random.set_seed(0)/<code>
<code># deactivate top_k sampling and sample only from 92% most likely words/<code>
<code>sample_output = model.generate(/<code>
<code>    input_ids,/<code>
<code>    do_sample=True,/<code>
<code>    max_length=50,/<code>
<code>    top_p=0.92,/<code>
<code>    top_k=0/<code>
<code>)/<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>print(tokenizer.decode(sample_output[0], skip_special_tokens=True))/<code>
<code>----------------------------------------------------------------------------------------------------/<code>
<code>I enjoy walking with my cute dog. He will never be the same. I watch him play./<code>
<code>Guys, my dog needs a name. Especially if he is found with wings. /<code>
<code>What was that? I had a lot of/<code>


雖然在理論上,Top-p似乎比Top-K更好,但兩種方法在實踐中都很有效。Top-p也可以與Top-K結合使用,Top-K可以避免排名很低的單詞,同時可以進行一些動態選擇。

最後,要獲得多個獨立採樣的輸出,可以再次設置參數num_return_sequences> 1:


# set seed to reproduce results. Feel free to change the seed though to get different results

<code>tf.random.set_seed(0)/<code>
<code># set top_k = 50 and set top_p = 0.95 and num_return_sequences = 3/<code>
<code>sample_outputs = model.generate(/<code>
<code>    input_ids,/<code>
<code>    do_sample=True,/<code>
<code>    max_length=50,/<code>
<code>    top_k=50,/<code>
<code>    top_p=0.95,/<code>
<code>    num_return_sequences=3/<code>
<code>)/<code>
<code>print("Output:\\n" + 100 * '-')/<code>
<code>for i, sample_output in enumerate(sample_outputs):/<code>
<code>  print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True)))/<code>
<code>----------------------------------------------------------------------------------------------------/<code>
<code>0: I enjoy walking with my cute dog. It's so good to have the chance to walk with a dog. But I have this problem with the dog and how he's always looking at us and always trying to make me see that I can do something/<code>
<code>1: I enjoy walking with my cute dog, she loves taking trips to different places on the planet, even in the desert! The world isn't big enough for us to travel by the bus with our beloved pup, but that's where I find my love/<code>
<code>2: I enjoy walking with my cute dog and playing with our kids," said David J. Smith, director of the Humane Society of the US./<code>
<code>"So as a result, I've got more work in my time," he said./<code>


很好,現在有了所有工具,可以讓模型用transformers來寫故事了!

在開放式語言生成中,top-p和top-K抽樣作為一種特殊的譯碼方法,似乎比傳統的貪心搜索和集束搜索能產生更流暢的文本。


不過,最近有更多的證據表明,貪心搜索和集束搜索的明顯缺陷(主要是產生重複的單詞序列)是由模型(尤其是模型的訓練方式)而不是解碼方法造成的。此外,如Welleck等人所示,top-K和top-p抽樣也會產生重複的單詞序列。

在Welleck等人的研究中,作者表明,根據人類的評估,在適應模型的訓練目標時,集束搜索可以比Top-p抽樣產生更流暢的文本。

開放式語言生成是一個快速發展的研究領域,通常情況下,這裡沒有一種千篇一律的方法,因此人們必須瞭解哪種方法在特定的用例中最有效。

你可以嘗試使用transfomers中的所有不同解碼方法。

簡短介紹瞭如何在transfomers中使用不同的解碼方法以及開放式語言生成的最新趨勢。

附錄

上面沒有提到生成方法的兩個附加參數。在這裡簡要地解釋一下:

  • 在達到min_length之前,min_length可以用來強制模型不產生EOS令牌(=不完成句子)。摘要中經常使用它,但是如果用戶想要更長的輸出,通常可以使用它。

  • repetition_penalty可用於懲罰已經生成的單詞或屬於上下文的單詞。它最初是由Kesker等人(2019年)提出的,也在Welleck等人(2019年)的訓練目標中使用。它可以非常有效地防止重複,但似乎對不同的模型和用例非常敏感。

  • attention_mask可用於屏蔽填充的令牌

  • pad_token_id、bos_token_id、eos_token_id:如果模型默認沒有這些令牌,用戶可以手動選擇其他令牌id來表示它們。

原文鏈接:

https://huggingface.co/blog/how-to-generate

"


分享到:


相關文章: