240709_昇思学习打卡-Day21-文本解码原理–以MindNLP为例
今天做根据前文预测下一个单词,仅作简单记录及注释。
一个文本序列的概率分布可以分解为每个词基于其上文的条件概率的乘积
- 𝑊_0:初始上下文单词序列
- 𝑇: 时间步
- 当生成EOS标签时,停止生成。
MindNLP/huggingface Transformers提供的文本生成方法
Greedy search
在每个时间步𝑡都简单地选择概率最高的词作为当前输出词:
𝑤𝑡=𝑎𝑟𝑔𝑚𝑎𝑥_𝑤 𝑃(𝑤|𝑤(1:𝑡−1))
按照贪心搜索输出序列(“The”,“nice”,“woman”) 的条件概率为:0.5 x 0.4 = 0.2
缺点: 错过了隐藏在低概率词后面的高概率词,如:dog=0.5, has=0.9
环境准备
%%capture captured_output
# 实验环境已经预装了mindspore==2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号
!pip uninstall mindspore -y
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore==2.2.14
!pip uninstall mindvision -y
!pip uninstall mindinsight -y
# 该案例在 mindnlp 0.3.1 版本完成适配,如果发现案例跑不通,可以指定mindnlp版本,执行`!pip install mindnlp==0.3.1`
!pip install mindnlp
# 贪婪搜索生成文本示例
# 导入所需的GPT2模型和分词器
from mindnlp.transformers import GPT2Tokenizer, GPT2LMHeadModel
# 初始化分词器,从预训练模型加载
tokenizer = GPT2Tokenizer.from_pretrained("iiBcai/gpt2", mirror='modelscope')
# 将EOS标记作为PAD标记以避免警告
model = GPT2LMHeadModel.from_pretrained("iiBcai/gpt2", pad_token_id=tokenizer.eos_token_id, mirror='modelscope')
# 对生成条件的上下文进行编码
input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='ms')
# 生成文本,直到输出长度(包括上下文长度)达到50
greedy_output = model.generate(input_ids, max_length=50)
# 打印生成的文本
print("Output:\n" + 100 * '-')
print(tokenizer.decode(greedy_output[0], skip_special_tokens=True))
Beam search
Beam search通过在每个时间步保留最可能的 num_beams 个词,并从中最终选择出概率最高的序列来降低丢失潜在的高概率序列的风险。如图以 num_beams=2 为例:
(“The”,“dog”,“has”) : 0.4 * 0.9 = 0.36
(“The”,“nice”,“woman”) : 0.5 * 0.4 = 0.20
优点:一定程度保留最优路径
缺点:1. 无法解决重复问题;2. 开放域生成效果差
# 导入GPT2模型的分词器和语言模型头
from mindnlp.transformers import GPT2Tokenizer, GPT2LMHeadModel
# 从预训练模型加载分词器
tokenizer = GPT2Tokenizer.from_pretrained("iiBcai/gpt2", mirror='modelscope')
# 将EOS标记作为PAD标记以避免警告信息
model = GPT2LMHeadModel.from_pretrained("iiBcai/gpt2", pad_token_id=tokenizer.eos_token_id, mirror='modelscope')
# 对给定的句子进行编码,准备用于生成
input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='ms')
# 使用束搜索(beam search)生成文本,并设置提前停止条件
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))
print(100 * '-')
# 设置不重复的n-gram大小为2,避免生成的文本中出现重复的短语
beam_output = model.generate(
input_ids,
max_length=50,
num_beams=5,
no_repeat_ngram_size=2,
early_stopping=True
)
# 输出避免重复n-gram的束搜索结果
print("Beam search with ngram, Output:\n" + 100 * '-')
print(tokenizer.decode(beam_output[0], skip_special_tokens=True))
print(100 * '-')
# 设置返回的序列数量大于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
)
# 输出所有生成的候选文本
print("return_num_sequences, Output:\n" + 100 * '-')
for i, beam_output in enumerate(beam_outputs):
print("{}: {}".format(i, tokenizer.decode(beam_output, skip_special_tokens=True)))
print(100 * '-')
Beam search issues
缺点:1. 无法解决重复问题;2. 开放域生成效果差
Repeat problem
n-gram 惩罚:
将出现过的候选词的概率设置为 0
设置no_repeat_ngram_size=2 ,任意 2-gram 不会出现两次
Notice: 实际文本生成需要重复出现
Sample
根据当前条件概率分布随机选择输出词𝑤_𝑡
(“car”) ~P(w∣"The") (“drives”) ~P(w∣"The",“car”)
优点:文本生成多样性高
缺点:生成文本不连续
# 导入MindSpore库以及GPT2模型相关的分词器和语言模型头
import mindspore
from mindnlp.transformers import GPT2Tokenizer, GPT2LMHeadModel
# 加载预训练的GPT2分词器
tokenizer = GPT2Tokenizer.from_pretrained("iiBcai/gpt2", mirror='modelscope')
# 将EOS标记设为PAD标记,以避免运行中的警告信息
model = GPT2LMHeadModel.from_pretrained("iiBcai/gpt2", pad_token_id=tokenizer.eos_token_id, mirror='modelscope')
# 对输入文本进行编码,用于后续的文本生成
input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='ms')
# 设置随机种子以确保实验可复现
mindspore.set_seed(0)
# 启用随机采样模式,并禁用top_k采样策略(通过将top_k设置为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))
Temperature
降低softmax 的temperature使 P(w∣w1:t−1)分布更陡峭
增加高概率单词的似然并降低低概率单词的似然
# 导入MindSpore库及GPT2模型相关的分词器与语言模型头
import mindspore
from mindnlp.transformers import GPT2Tokenizer, GPT2LMHeadModel
# 从预训练模型加载GPT2分词器
tokenizer = GPT2Tokenizer.from_pretrained("iiBcai/gpt2", mirror='modelscope')
# 将结束标记(EOS)作为填充标记(PAD),避免警告信息
model = GPT2LMHeadModel.from_pretrained("iiBcai/gpt2", pad_token_id=tokenizer.eos_token_id, mirror='modelscope')
# 对给定的上下文进行编码,用于文本生成
input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='ms')
# 设置随机种子,保证结果的可复现性
mindspore.set_seed(1234)
# 启用随机采样,关闭top_k采样,设置温度参数调整生成文本的多样性
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))
TopK sample
选出概率最大的 K 个词,重新归一化,最后在归一化后的 K 个词中采样
TopK sample problems
将采样池限制为固定大小 K :
- 在分布比较尖锐的时候产生胡言乱语
- 在分布比较平坦的时候限制模型的创造力
# 导入MindSpore库及GPT2模型相关的分词器和语言模型头
import mindspore
from mindnlp.transformers import GPT2Tokenizer, GPT2LMHeadModel
# 加载预训练的GPT2分词器
tokenizer = GPT2Tokenizer.from_pretrained("iiBcai/gpt2", mirror='modelscope')
# 将EOS标记设为PAD标记,以避免运行过程中的警告
model = GPT2LMHeadModel.from_pretrained("iiBcai/gpt2", pad_token_id=tokenizer.eos_token_id, mirror='modelscope')
# 对输入的上下文进行编码,作为生成文本的条件
input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='ms')
# 设置随机种子,确保实验的可复现性
mindspore.set_seed(0)
# 启用随机采样,并设置top_k采样,限制在前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-P sample
在累积概率超过概率 p 的最小单词集中进行采样,重新归一化
采样池可以根据下一个词的概率分布动态增加和减少
# 导入MindSpore库及GPT2模型相关的分词器和语言模型头
import mindspore
from mindnlp.transformers import GPT2Tokenizer, GPT2LMHeadModel
# 加载预训练的GPT2分词器
tokenizer = GPT2Tokenizer.from_pretrained("iiBcai/gpt2", mirror='modelscope')
# 将EOS标记设为PAD标记,避免警告信息
model = GPT2LMHeadModel.from_pretrained("iiBcai/gpt2", pad_token_id=tokenizer.eos_token_id, mirror='modelscope')
# 对输入文本进行编码,作为生成文本的条件
input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='ms')
# 设置随机种子以确保结果的可复现性
mindspore.set_seed(0)
# 禁用top_k采样,仅从最有可能的92%词汇中进行采样
sample_output = model.generate(
input_ids,
do_sample=True,
max_length=50,
top_p=0.92,
top_k=0
)
# 输出基于top_p采样生成的文本
print("Output:\n" + 100 * '-')
print(tokenizer.decode(sample_output[0], skip_special_tokens=True))
top_k_top_p
# 导入MindSpore库及GPT2模型相关的分词器和语言模型头
import mindspore
from mindnlp.transformers import GPT2Tokenizer, GPT2LMHeadModel
# 加载预训练的GPT2分词器
tokenizer = GPT2Tokenizer.from_pretrained("iiBcai/gpt2", mirror='modelscope')
# 将EOS标记设为PAD标记,避免警告信息
model = GPT2LMHeadModel.from_pretrained("iiBcai/gpt2", pad_token_id=tokenizer.eos_token_id, mirror='modelscope')
# 对输入文本进行编码,作为生成文本的条件
input_ids = tokenizer.encode('I enjoy walking with my cute dog', return_tensors='ms')
# 设置随机种子以确保结果的可复现性
mindspore.set_seed(0)
# 设置top_k为50,top_p为0.95,并要求返回3个不同的序列
sample_outputs = model.generate(
input_ids,
do_sample=True,
max_length=50,
top_k=5,
top_p=0.95,
num_return_sequences=3
)
# 输出基于top_k和top_p采样生成的多条文本
print("Output:\n" + 100 * '-')
for i, sample_output in enumerate(sample_outputs):
print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True)))
打卡图片: