gpt2生成文本的不同解码策略

代码实测:gpt2-generate-strategies

以下所有的功能都可以用于自回归语言生成。
自回归式语言生成是基于这样一个假设:一个词序列的概率分布可以分解为条件概率(当前步长单词在上一单词存在的条件概率)的乘机:
在这里插入图片描述
W0是句子的第一个词,长度 T 是自定义的并且与时间步长相对应 t=T, EOS 的token是由在这里插入图片描述产生的。
自回归模型有:GPT2, XLNet, OpenAi-GPT, CTRL, TransfoXL, XLM, Bart, T5 等

​### 装包

!pip install -q git+https://github.com/huggingface/transformers.git
!pip install -q tensorflow
or
pip install torch==1.12.1+cpu torchvision==0.13.1+cpu torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cpu
#https://pytorch.org/get-started/previous-versions/

如果装的是tensorflow

import tensorflow as tf
from transformers import TFGPT2LMHeadModel, 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)

如果装的是torch

import torch
from transformers import GPT2LMHeadModel, GPT2Tokenizer


tokenizer = GPT2Tokenizer.from_pretrained("gpt2")

# add the EOS token as PAD token to avoid warnings
model = GPT2LMHeadModel.from_pretrained("gpt2", pad_token_id=tokenizer.eos_token_id)

Greedy Search (贪婪搜索)

贪婪搜索只需选择概率最高的单词作为下一个单词
gpt2生成文本的不同解码策略_第1张图片
一开始的单词‘The’ ,贪婪算法选择下一个最高概率的单词‘nice’,… 最后的完整序列(‘The’,‘nice’,‘woman’)概率:0.5 * 0.4 = 0.2 。
接下来用gpt2 以(“I”,“enjoy”,“walking”,“with”,“my”,“cute”,“dog”)开始使用贪婪算法生成:

# encode context the generation is conditioned on
input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='pt')

# generate text until the output length (which includes the context length) reaches 50
greedy_output = model.generate(input_ids, max_length=50)

print("Output:\n" + 100 * '-')
print(tokenizer.decode(greedy_output[0], skip_special_tokens=True))

在这里插入图片描述
一开始生成的还凑合,后面接开始重复了。
贪婪搜索的主要缺点是它错过了隐藏在低概率单词后面的高概率单词,就像上面的草图中,’has‘ 有0.9 的高概率,但是它在0.4 的低概率单词’dog‘后面。
Beam search(波束搜索)可以缓解上面的问题

Beam search(波束搜索)

Beam search通过在每个时间步长保留最可能的假设的num_Beam并最终选择具有总体最高概率的假设,来降低丢失隐藏的高概率单词序列的风险。
当num_beams=2时:
gpt2生成文本的不同解码策略_第2张图片
保留了两束路径,总体概率(“The”,“dog”,“has”) 是0.39 比(“The”,“nice”,“woman”) 的0.2 更大。波束搜索总是以比贪婪搜索更高的概率找到输出序列,但不能保证找到最有可能的输出。
下面跑一下案例,将num_beams>1和early_stopping设置为True,以便在所有波束假设到达EOS token时完成生成。

# activate beam search and early_stopping
beam_output = model.generate(
    input_ids, 
    max_length=50, 
    num_beams=5, 
    early_stopping=True
)

print("Output:\n" + 100 * '-')
print(tokenizer.decode(beam_output[0], skip_special_tokens=True))

在这里插入图片描述
虽然结果可以说更流畅,但输出仍然包括相同单词和序列重复问题。
一个简单的补救措施是引入n-grams惩罚。最常见的n-gram惩罚是通过手动将下一个单词可能生成一个已经看到的n-gram的概率设置为0来确保没有n-gram出现两次。
通过设置no_rerepeat_ngram_size=2,这样就不会出现两次相同的2-gram

# set no_repeat_ngram_size to 2
beam_output = model.generate(
    input_ids, 
    max_length=50, 
    num_beams=5, 
    no_repeat_ngram_size=2, 
    early_stopping=True
)

print("Output:\n" + 100 * '-')
print(tokenizer.decode(beam_output[0], skip_special_tokens=True))

在这里插入图片描述
重复问题已经好多了,但是但是这种惩罚应该慎用,比如2-gram,许多专有名词比如城市都是两个单词的,使用的2-gram惩罚,文章中本该出现多次的城市名字就只会出现一次。
波束搜索的另一个重要功能是可以在生成后比较top波束,并选择最适合我们目的的生成波束。
只需将参数num_return_sequences设置为应返回的最高得分波束的数量。尽管如此,请确保num_return_sequences<=num_beams!

# set return_num_sequences > 1
beam_outputs = model.generate(
    input_ids, 
    max_length=50, 
    num_beams=5, 
    no_repeat_ngram_size=2, 
    num_return_sequences=5, 
    early_stopping=True
)

# now we have 3 output sequences
print("Output:\n" + 100 * '-')
for i, beam_output in enumerate(beam_outputs):
  print("{}: {}".format(i, tokenizer.decode(beam_output, skip_special_tokens=True)))

gpt2生成文本的不同解码策略_第3张图片
返回的5个波束结果差别不大,当把参数调大,然后根据后面自定义的条件删选满意的结果。
在开放式生成中,最近提出了波束搜索可能不是最佳选择的几个原因:

  • 波束搜索在所需生成长度或多或少可预测的任务中可以很好地工作,如在机器翻译或摘要中。但对于所需输出长度可能变化很大的开放式生成,例如对话和故事生成,情况并非如此。
  • 我们已经看到波束搜索严重受到重复生成的影响。这在故事生成中尤其难以用n-gram或其他惩罚来控制,因为要在强制“不重复”和重复相同n-gram的循环之间找到一个良好的权衡需要大量的微调。
  • 高质量的人类语言不遵循高概率下一个单词的分布。换句话说,作为人类,我们希望生成的文本给我们带来惊喜,而不是无聊/可预测。通过概率图很好地表明了这一点,该模型将给出人类文本与波束搜索的结果。
    gpt2生成文本的不同解码策略_第4张图片

停止无聊,引入随机性

Sampling(采样)

在最基本的形式中,采样意味着根据条件概率分布随机选择下一个单词:
在这里插入图片描述
以上面的例子,下图可视化了采样时的语言生成。
gpt2生成文本的不同解码策略_第5张图片
很明显,使用采样的语言生成不再是确定性的。
‘car’ 在’The‘的条件概率下采样,‘drives’ 在’The car‘ 的条件概率下采样。

将do_sample设置为True,并通过Top_K=0停用Top-K采样。固定住随机种子random_seed=2023,便于对比。

# set seed to reproduce results. Feel free to change the seed though to get different results
#tf.random.set_seed(2023)
torch.manual_seed(2222)
# activate sampling and deactivate top_k by setting top_k sampling to 0
sample_output = model.generate(
    input_ids, 
    do_sample=True, 
    max_length=50, 
    top_k=0
)

print("Output:\n" + 100 * '-')
print(tokenizer.decode(sample_output[0], skip_special_tokens=True))

在这里插入图片描述
一开始还凑合,后面就逻辑不通了。(相同的随机种子,tf 和 torch 生成的结果却不一样)
有个小技巧是通过降低softmax的temperature可以是概率分布更尖锐(高概率变得更高,低概率变得更低)(transformers 的好像不能大于一,大于一了不受控制?自定义的可以大于一,更具发散性和多样性)
gpt2生成文本的不同解码策略_第6张图片
’nice‘又原来的0.5变成了0.75,’car’3原来的0.2变成了0.02。
将temperature设置为0.7看一下:

# set seed to reproduce results. Feel free to change the seed though to get different results
#tf.random.set_seed(0)
torch.manual_seed(0)
# use temperature to decrease the sensitivity to low probability candidates
sample_output = model.generate(
    input_ids, 
    do_sample=True, 
    max_length=50, 
    top_k=0, 
    temperature=0.7
)

print("Output:\n" + 100 * '-')
print(tokenizer.decode(sample_output[0], skip_special_tokens=True))

在这里插入图片描述
输出也更连贯了!虽然应用temperature可以使分布不那么随机,当设置温度=0时,温度缩放采样变得等于贪婪解码,并且将遭受与以前相同的问题。

Top-K Sampling

在Top-K采样中,对K个最有可能的下一个单词进行滤波(保留),并仅在这K个下一个词之间重新分配概率权重。GPT2采用了这种采样方案。
将上例中用于两个采样步骤的单词范围从3个单词扩展到10个单词,以更好地说明Top-K采样。
gpt2生成文本的不同解码策略_第7张图片
设置top_k=6,在两个采样步骤中,我们将采样池限制为6个单词。尽管第一时间步骤涵盖了大约2/3的候选词,但是在第二时间步骤已经可以剔除掉一些奇怪的词了:(“not",“the",“small",“told")
设置Top_K=50试一下:

# set seed to reproduce results. Feel free to change the seed though to get different results
#tf.random.set_seed(0)
torch.manual_seed(0)
# set top_k to 50
sample_output = model.generate(
    input_ids, 
    do_sample=True, 
    max_length=50, 
    top_k=50
)

print("Output:\n" + 100 * '-')
print(tokenizer.decode(sample_output[0], skip_special_tokens=True))

在这里插入图片描述
Top-K抽样的一个问题是,它不能动态地调整从下一个单词概率分布中过滤出来的单词数量(每次都是k个)。
这可能是有问题的,因为一些单词可能是从非常尖锐的分布(上图中右侧的分布)中采样的,而另一些单词则是从更平坦的分布(图中左侧的分布)采样的。比如(“down”,“a”)都已经不合适了,而且有可能浪费计算资源。

Top-p (nucleus) sampling

在Top-p中,不是只从最可能的K个单词中采样,而是从累积概率超过概率p的最小可能的单词集中选择。然后,概率权重在这组单词之间重新分布。这样,单词集的大小(也称为该集中的单词数量)可以根据下一个单词的概率分布动态地增加和减少。
gpt2生成文本的不同解码策略_第8张图片
设置top_p =0.92,Top-p采样选取最少的单词(连续单词相乘概率超过0.92),第一步中有9个单词在‘The’的条件概率超过0.92,第二步在‘The car’ 的条件概率下只有三个单词超过0.92。
设置0<top_p<1来测试Top-p采样:

# set seed to reproduce results. Feel free to change the seed though to get different results
#tf.random.set_seed(0)
torch.manual_seed(0)
# deactivate top_k sampling and sample only from 92% most likely words
sample_output = model.generate(
    input_ids, 
    do_sample=True, 
    max_length=50, 
    top_p=0.92, 
    top_k=0
)

print("Output:\n" + 100 * '-')
print(tokenizer.decode(sample_output[0], skip_special_tokens=True))

在这里插入图片描述
结果有点意思了。虽然在理论上,Top-p似乎比Top-K更优雅,但这两种方法在实践中都很有效。Top-p也可以与Top-K组合使用,这可以避免排名很低的单词,同时允许一些动态选择。

最后,为了获得多个独立采样的输出,我们可以再次设置参数num_return_sequences > 1:

# set seed to reproduce results. Feel free to change the seed though to get different results
#tf.random.set_seed(0)
torch.manual_seed(0)

# set top_k = 50 and set top_p = 0.95 and num_return_sequences = 3
sample_outputs = model.generate(
    input_ids,
    do_sample=True, 
    max_length=50, 
    top_k=50, 
    top_p=0.95, 
    num_return_sequences=3
)

print("Output:\n" + 100 * '-')
for i, sample_output in enumerate(sample_outputs):
  print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True)))

gpt2生成文本的不同解码策略_第9张图片

其他参数

  • min_length可用于强制模型在达到min_ length之前不生成EOS token(=不结束句子)。这在摘要中使用得很频繁,如果用户想要更长的输出,通常会很有用。
  • repetition_penalty可以用于惩罚已经生成或属于上下文的单词。它可以非常有效地防止重复,但似乎对不同的模型和用例非常敏感: Issues
  • attention_mask可以用于遮盖padded的tokens
  • pad_token_id, bos_token_id, eos_token_id:如果默认情况下模型没有这些token,用户可以手动选择其他令token id来表示它们。

Conclusion

最近有更多的证据表明,贪婪搜索和波束搜索的明显缺陷——主要是生成重复的单词序列——是由模型(尤其是模型的训练方式)引起的,而不是解码方法。top-K和top-p采样也会产生重复的单词序列。Welleck et al. (2019)中,作者表明,根据人类评估,在调整模型的训练目标时,波束搜索可以生成比Top-p采样更流畅的文本。开放式语言生成没有一刀切的方法,所以必须在自己的特定用例中最有效多尝试。

参考:
generate function
how-to-generate
better-language-models
Introducing a Conditional Transformer Language Model for Controllable Generation
How To Make Custom AI-Generated Text With GPT-2
fine-tuning-GPT2

你可能感兴趣的:(NLP,深度学习,python,人工智能)