文本生成策略¶
文本生成是许多自然语言处理任务的核心,例如开放式文本生成、摘要、翻译等。它还在各种混合模式应用中发挥作用,这些应用以文本作为输出,例如语音转文本和视觉转文本。一些可以生成文本的模型包括 GPT2、XLNet、OpenAI GPT、CTRL、TransformerXL、XLM、Bart、T5、GIT 和 Whisper。
查看一些使用 generate() 方法为不同任务生成文本输出的示例:
请注意,generate 方法的输入取决于模型的模态。它们由模型的预处理器类返回,例如 AutoTokenizer 或 AutoProcessor。如果一个模型的预处理器创建多种输入,请将所有输入传递给 generate()。你可以在相应的模型文档中了解更多关于单个模型预处理器的信息。
选择输出标记以生成文本的过程称为解码,你可以自定义 generate() 方法将使用的解码策略。修改解码策略不会改变任何可训练参数的值。然而,它可以对生成输出的质量产生显著影响。它可以帮助减少文本中的重复,并使其更加连贯。
本指南描述了:
- 默认生成配置
- 常见的解码策略及其主要参数
- 将自定义生成配置与你在 🤗 Hub 上微调的模型一起保存和共享
默认文本生成配置¶
模型的解码策略在其生成配置中定义。当在 pipeline() 内部使用预训练模型进行推理时,模型调用 PreTrainedModel.generate() 方法,该方法在幕后应用默认生成配置。当没有与模型一起保存自定义配置时,也使用默认配置。
当你显式加载模型时,可以通过 model.generation_config 检查其附带的生成配置:
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("distilbert/distilgpt2")
model.generation_config
打印 model.generation_config 只会显示与默认生成配置不同的值,而不会列出任何默认值。
默认生成配置将输出与输入提示的大小限制为最多 20 个标记,以避免遇到资源限制。默认解码策略是贪婪搜索,这是最简单的解码策略,它选择具有最高概率的标记作为下一个标记。对于许多任务和小输出大小,这工作得很好。然而,当用于生成更长的输出时,贪婪搜索可能会开始产生高度重复的结果。
自定义文本生成¶
你可以通过将参数及其值直接传递给 generate 方法来覆盖任何 generation_config:
my_model.generate(**inputs, num_beams=4, do_sample=True)
即使默认解码策略主要适用于你的任务,你仍然可以调整一些东西。一些常见的调整参数包括:
max_new_tokens:要生成的最大标记数。换句话说,输出序列的大小,不包括提示中的标记。作为使用输出长度作为停止标准的替代方案,你可以选择在完整生成超过一定时间时停止生成。要了解更多信息,请查看 StoppingCriteria。num_beams:通过指定大于 1 的光束数,你实际上是从贪婪搜索切换到光束搜索。这种策略在每个时间步评估多个假设,并最终选择整个序列具有最高概率的假设。这有助于识别以较低概率初始标记开始的具有较高概率的序列,这些序列会被贪婪搜索忽略。可视化它如何工作 这里。do_sample:如果设置为True,此参数将启用解码策略,例如多项式采样、光束搜索多项式采样、Top-K 采样和 Top-p 采样。所有这些策略都从整个词汇的概率分布中选择下一个标记,并具有各种策略特定的调整。num_return_sequences:每个输入要返回的序列候选数。此选项仅适用于支持多个序列候选的解码策略,例如光束搜索和采样的变体。像贪婪搜索和对比搜索这样的解码策略返回单个输出序列。
保存自定义解码策略与你的模型¶
如果你想要分享你的微调模型以及特定的生成配置,你可以:
- 创建一个 GenerationConfig 类实例
- 指定解码策略参数
- 使用 GenerationConfig.save_pretrained() 保存你的生成配置,确保其
config_file_name参数为空 - 将
push_to_hub设置为True以将你的配置上传到模型的仓库
from transformers import AutoModelForCausalLM, GenerationConfig
model = AutoModelForCausalLM.from_pretrained("my_account/my_model")
generation_config = GenerationConfig(
max_new_tokens=50, do_sample=True, top_k=50, eos_token_id=model.config.eos_token_id
)
generation_config.save_pretrained("my_account/my_model", push_to_hub=True)
你还可以在单个目录中存储多个生成配置,利用 GenerationConfig.save_pretrained() 中的 config_file_name 参数。稍后你可以使用 GenerationConfig.from_pretrained() 实例化它们。如果你想要为单个模型存储多个生成配置(例如,一个用于创意文本生成的采样,一个用于使用光束搜索的摘要),这将非常有用。你必须拥有在模型上添加配置文件的正确 Hub 权限。
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer, GenerationConfig
tokenizer = AutoTokenizer.from_pretrained("google-t5/t5-small")
model = AutoModelForSeq2SeqLM.from_pretrained("google-t5/t5-small")
translation_generation_config = GenerationConfig(
num_beams=4,
early_stopping=True,
decoder_start_token_id=0,
eos_token_id=model.config.eos_token_id,
pad_token=model.config.pad_token_id,
)
# 提示:添加 `push_to_hub=True` 以推送到 Hub
translation_generation_config.save_pretrained("/tmp", "translation_generation_config.json")
# 你可以使用命名生成配置文件来参数化生成
generation_config = GenerationConfig.from_pretrained("/tmp", "translation_generation_config.json")
inputs = tokenizer("translate English to French: Configuration files are easy to use!", return_tensors="pt")
outputs = model.generate(**inputs, generation_config=generation_config)
print(tokenizer.batch_decode(outputs, skip_special_tokens=True))
流式传输¶
generate() 支持流式传输,通过其 streamer 输入。streamer 输入与任何具有以下方法的类实例兼容:put() 和 end()。在内部,put() 用于推送新标记,end() 用于标记文本生成的结束。
流式传输类的 API 仍在开发中,将来可能会发生变化。
实际上,你可以为所有目的制作自己的流式传输类!我们也有基本流式传输类可供你使用。例如,你可以使用 TextStreamer 类将 generate() 的输出流式传输到你的屏幕,一次一个单词:
from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer
tok = AutoTokenizer.from_pretrained("openai-community/gpt2")
model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2")
inputs = tok(["An increasing sequence: one,"], return_tensors="pt")
streamer = TextStreamer(tok)
# 尽管返回通常的输出,但流式传输器还会将生成的文本打印到 stdout。
_ = model.generate(**inputs, streamer=streamer, max_new_tokens=20)
水印¶
generate() 支持通过随机标记一部分标记为“绿色”来对生成的文本进行水印处理。在生成时,“绿色”标记的 logits 将有一个小的“偏差”值添加,从而有更高的生成概率。可以通过计算文本中“绿色”标记的比例并估计人类生成的文本中获得该数量“绿色”标记的统计可能性来检测水印文本。这种水印策略在论文 “On the Reliability of Watermarks for Large Language Models” 中提出。有关水印内部工作原理的更多信息,建议参考该论文。
水印可用于 transformers 中的任何生成模型,并且不需要额外的分类模型来检测水印文本。要触发水印,可以直接将带有必要参数的 WatermarkingConfig 传递给 .generate() 方法,或者将其添加到 GenerationConfig。稍后可以使用 WatermarkDetector 检测水印文本。
WatermarkDetector 在内部依赖于“绿色”标记的比例,以及生成的文本是否遵循着色模式。这就是为什么建议在提示文本比生成文本长得多时去掉提示文本。当一批中的某个序列比其他行长得多导致其他行被填充时,这也可能产生影响。此外,检测器必须使用与生成时相同的 watermark 配置参数进行初始化。
让我们生成一些带水印的文本。在下面的代码片段中,我们将偏差设置为 2.5,这是将添加到“绿色”标记的 logits 的值。生成水印文本后,我们可以直接将其传递给 WatermarkDetector 以检查文本是否由机器生成(对于机器生成的输出 True,否则输出 False)。
from transformers import AutoTokenizer, AutoModelForCausalLM, WatermarkDetector, WatermarkingConfig
model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2")
tok = AutoTokenizer.from_pretrained("openai-community/gpt2")
tok.pad_token_id = tok.eos_token_id
tok.padding_side = "left"
inputs = tok(["This is the beginning of a long story", "Alice and Bob are"], padding=True, return_tensors="pt")
input_len = inputs["input_ids"].shape[-1]
watermarking_config = WatermarkingConfig(bias=2.5, seeding_scheme="selfhash")
out = model.generate(**inputs, watermarking_config=watermarking_config, do_sample=False, max_length=20)
detector = WatermarkDetector(model_config=model.config, device="cpu", watermarking_config=watermarking_config)
detection_out = detector(out, return_dict=True)
detection_out.prediction
解码策略¶
generate() 参数的某些组合,以及最终的 generation_config,可用于启用特定的解码策略。如果你是这方面的新手,我们建议阅读 这篇博客文章,其中说明了常见解码策略的工作原理。
在这里,我们将展示控制解码策略的参数,并说明如何使用它们。
选择给定的解码策略不是你影响 generate() 结果的唯一方式。解码策略主要基于 logits,即下一个标记的概率分布,因此选择一个好的 logits 操作策略可以大有帮助!换句话说,除了选择解码策略之外,操纵 logits 是你可以采取的另一个维度。流行的 logits 操作策略包括 top_p、min_p 和 repetition_penalty — 你可以在 GenerationConfig 类中查看完整列表。
贪婪搜索¶
generate 默认使用贪婪搜索解码,因此你不需要传递任何参数来启用它。这意味着参数 num_beams 设置为 1 并且 do_sample=False。
from transformers import AutoModelForCausalLM, AutoTokenizer
prompt = "I look forward to"
checkpoint = "distilbert/distilgpt2"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
inputs = tokenizer(prompt, return_tensors="pt")
model = AutoModelForCausalLM.from_pretrained(checkpoint)
outputs = model.generate(**inputs)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
对比搜索¶
对比搜索解码策略在 2022 年的论文 A Contrastive Framework for Neural Text Generation 中提出。它展示了在生成非重复且连贯的长输出方面的优越结果。要了解对比搜索如何工作,请查看 这篇博客文章。启用和控制对比搜索行为的主要参数是 penalty_alpha 和 top_k:
from transformers import AutoTokenizer, AutoModelForCausalLM
checkpoint = "openai-community/gpt2-large"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint)
prompt = "Hugging Face Company is"
inputs = tokenizer(prompt, return_tensors="pt")
outputs = model.generate(**inputs, penalty_alpha=0.6, top_k=4, max_new_tokens=100)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
多项式采样¶
与总是选择具有最高概率的标记作为下一个标记的贪婪搜索不同,多项式采样(也称为祖先采样)根据模型给出的整个词汇的概率分布随机选择下一个标记。每个具有非零概率的标记都有被选中的机会,从而减少了重复的风险。
要启用多项式采样,请设置 do_sample=True 和 num_beams=1。
from transformers import AutoTokenizer, AutoModelForCausalLM, set_seed
set_seed(0) # 为了可重复性
checkpoint = "openai-community/gpt2-large"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint)
prompt = "Today was an amazing day because"
inputs = tokenizer(prompt, return_tensors="pt")
outputs = model.generate(**inputs, do_sample=True, num_beams=1, max_new_tokens=100)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
from transformers import AutoModelForCausalLM, AutoTokenizer
prompt = "It is astonishing how one can"
checkpoint = "openai-community/gpt2-medium"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
inputs = tokenizer(prompt, return_tensors="pt")
model = AutoModelForCausalLM.from_pretrained(checkpoint)
outputs = model.generate(**inputs, num_beams=5, max_new_tokens=50)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
光束搜索多项式采样¶
顾名思义,这种解码策略结合了光束搜索和多项式采样。你需要指定大于 1 的 num_beams 并设置 do_sample=True 以使用此解码策略。
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, set_seed
set_seed(0) # 为了可重复性
prompt = "translate English to German: The house is wonderful."
checkpoint = "google-t5/t5-small"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
inputs = tokenizer(prompt, return_tensors="pt")
model = AutoModelForSeq2SeqLM.from_pretrained(checkpoint)
outputs = model.generate(**inputs, num_beams=5, do_sample=True)
tokenizer.decode(outputs[0], skip_special_tokens=True)
多样化光束搜索解码¶
多样化光束搜索解码策略是光束搜索策略的扩展,它允许生成更多样化的光束序列集以供选择。要了解其工作原理,请参阅 Diverse Beam Search: Decoding Diverse Solutions from Neural Sequence Models。这种方法有三个主要参数:num_beams、num_beam_groups 和 diversity_penalty。多样性惩罚确保输出在组之间是不同的,并且在每个组内使用光束搜索。
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
checkpoint = "google/pegasus-xsum"
prompt = (
"The Permaculture Design Principles are a set of universal design principles "
"that can be applied to any location, climate and culture, and they allow us to design "
"the most efficient and sustainable human habitation and food production systems. "
"Permaculture is a design system that encompasses a wide variety of disciplines, such "
"as ecology, landscape design, environmental science and energy conservation, and the "
"Permaculture design principles are drawn from these various disciplines. Each individual "
"design principle itself embodies a complete conceptual framework based on sound "
"scientific principles. When we bring all these separate principles together, we can "
"create a design system that both looks at whole systems, the parts that these systems "
"consist of, and how those parts interact with each other to create a complex, dynamic, "
"living system. Each design principle serves as a tool that allows us to integrate all "
"the separate parts of a design, referred to as elements, into a functional, synergistic, "
"whole system, where the elements harmoniously interact and work together in the most "
"efficient way possible."
)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
inputs = tokenizer(prompt, return_tensors="pt")
model = AutoModelForSeq2SeqLM.from_pretrained(checkpoint)
outputs = model.generate(**inputs, num_beams=5, num_beam_groups=5, max_new_tokens=30, diversity_penalty=1.0)
tokenizer.decode(outputs[0], skip_special_tokens=True)
本指南说明了启用各种解码策略的主要参数。generate 方法还有更高级的参数,可以让你对 generate 方法的行为有更进一步的掌控。有关可用参数的完整列表,请参阅 API 文档。
投机解码¶
投机解码(也称为辅助解码)是对上述解码策略的修改,它使用辅助模型(理想情况下是一个更小的模型)来生成几个候选标记。主模型然后在单个前向传递中验证候选标记,从而加快解码过程。如果 do_sample=True,则使用 投机解码论文 中引入的带有重新采样的标记验证。辅助解码假设主模型和辅助模型具有相同的分词器,否则,请参阅通用辅助解码。
目前,只有贪婪搜索和采样支持辅助解码,并且辅助解码不支持批量输入。要了解更多关于辅助解码的信息,请查看 这篇博客文章。
通用辅助解码¶
通用辅助解码(UAD)添加了对主模型和辅助模型具有不同分词器的支持。要使用它,只需使用 tokenizer 和 assistant_tokenizer 参数传递分词器(见下文)。在内部,主模型输入标记被重新编码为辅助模型标记,然后在辅助编码中生成候选标记,这些候选标记又被重新编码为主模型候选标记。然后按照上述方式继续验证。重新编码步骤涉及将标记 id 解码为文本,然后使用不同的分词器对文本进行编码。由于重新编码标记可能导致分词差异,UAD 找到源编码和目标编码之间的最长公共子序列,以确保新标记包含正确的提示后缀。
要启用辅助解码,请设置 assistant_model 参数为模型。
from transformers import AutoModelForCausalLM, AutoTokenizer
prompt = "Alice and Bob"
checkpoint = "EleutherAI/pythia-1.4b-deduped"
assistant_checkpoint = "EleutherAI/pythia-160m-deduped"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
inputs = tokenizer(prompt, return_tensors="pt")
model = AutoModelForCausalLM.from_pretrained(checkpoint)
assistant_model = AutoModelForCausalLM.from_pretrained(assistant_checkpoint)
outputs = model.generate(**inputs, assistant_model=assistant_model)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
如果主模型和辅助模型具有不同的分词器,请使用通用辅助解码。
from transformers import AutoModelForCausalLM, AutoTokenizer
prompt = "Alice and Bob"
checkpoint = "google/gemma-2-9b"
assistant_checkpoint = "double7/vicuna-68m"
assistant_tokenizer = AutoTokenizer.from_pretrained(assistant_checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
inputs = tokenizer(prompt, return_tensors="pt")
model = AutoModelForCausalLM.from_pretrained(checkpoint)
assistant_model = AutoModelForCausalLM.from_pretrained(assistant_checkpoint)
outputs = model.generate(**inputs, assistant_model=assistant_model, tokenizer=tokenizer, assistant_tokenizer=assistant_tokenizer)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
当使用辅助解码与采样方法时,你可以使用 temperature 参数来控制随机性,就像在多项式采样中一样。然而,在辅助解码中,降低温度可能有助于提高延迟。
from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
set_seed(42) # 为了可重复性
prompt = "Alice and Bob"
checkpoint = "EleutherAI/pythia-1.4b-deduped"
assistant_checkpoint = "EleutherAI/pythia-160m-deduped"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
inputs = tokenizer(prompt, return_tensors="pt")
model = AutoModelForCausalLM.from_pretrained(checkpoint)
assistant_model = AutoModelForCausalLM.from_pretrained(assistant_checkpoint)
outputs = model.generate(**inputs, assistant_model=assistant_model, do_sample=True, temperature=0.5)
tokenizer.batch_decode(outputs, skip_special_tokens=True)
或者,你也可以设置 prompt_lookup_num_tokens 以触发基于 n-gram 的辅助解码,而不是基于模型的辅助解码。你可以在这里阅读更多关于它的信息 这里。
DoLa 解码¶
Decoding by Contrasting Layers (DoLa) 是一种对比解码策略,用于提高 LLM 的事实性和减少幻觉,如 ICLR 2024 年的论文 DoLa: Decoding by Contrasting Layers Improves Factuality in Large Language Models 所述。
DoLa 通过对比从最终层与早期层获得的 logits 之间的差异来实现,从而放大特定于 transformers 层的事实知识。
要在调用 model.generate 函数时激活 DoLa 解码,请执行以下两个步骤:
- 设置
dola_layers参数,它可以是字符串或整数列表。- 如果设置为字符串,则可以是
low或high之一。 - 如果设置为整数列表,则应该是介于 0 到模型总层数之间的层索引列表。第 0 层是词嵌入,第 1 层是第一个 transformers 层,依此类推。
- 如果设置为字符串,则可以是
- 建议设置
repetition_penalty = 1.2以减少 DoLa 解码中的重复。
以下示例展示了使用 32 层 LLaMA-7B 模型的 DoLa 解码。
from transformers import AutoTokenizer, AutoModelForCausalLM, set_seed
import torch
tokenizer = AutoTokenizer.from_pretrained("huggyllama/llama-7b")
model = AutoModelForCausalLM.from_pretrained("huggyllama/llama-7b", torch_dtype=torch.float16)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model.to(device)
set_seed(42)
text = "On what date was the Declaration of Independence officially signed?"
inputs = tokenizer(text, return_tensors="pt").to(device)
# Vanilla greddy decoding
vanilla_output = model.generate(**inputs, do_sample=False, max_new_tokens=50)
tokenizer.batch_decode(vanilla_output[:, inputs.input_ids.shape[-1]:], skip_special_tokens=True)
# DoLa decoding with contrasting higher part of layers (layers 16,18,...,30)
dola_high_output = model.generate(**inputs, do_sample=False, max_new_tokens=50, dola_layers='high')
tokenizer.batch_decode(dola_high_output[:, inputs.input_ids.shape[-1]:], skip_special_tokens=True)
# DoLa decoding with contrasting specific layers (layers 28 and 30)
dola_custom_output = model.generate(**inputs, do_sample=False, max_new_tokens=50, dola_layers=[28,30], repetition_penalty=1.2)
tokenizer.batch_decode(dola_custom_output[:, inputs.input_ids.shape[-1]:], skip_special_tokens=True)
理解 dola_layers 参数¶
dola_layers 代表早期层选择中的候选层,如 DoLa 论文中所述。选定的早期层将与最终层进行对比。
将 dola_layers 设置为 'low' 或 'high' 将分别选择较低或较高的层进行对比。
- 对于
N层模型,其中N <= 40层,range(0, N // 2, 2)和range(N // 2, N, 2)的层分别用于'low'和'high'层。 - 对于
N > 40层的模型,range(0, 20, 2)和range(N - 20, N, 2)的层分别用于'low'和'high'层。 - 如果模型有绑定的词嵌入,我们将跳过词嵌入(第 0 层)并从第 2 层开始,因为从词嵌入中的早期退出将成为恒等函数。
- 将
dola_layers设置为整数列表,用于层索引,以手动指定要对比的层。例如,设置dola_layers=[28,30]将对比最终层(第 32 层)与第 28 层和第 30 层。
论文建议,对于像 TruthfulQA 这样的简答题任务,对比 'high' 层;而对于所有其他长答案推理任务,例如 GSM8K、StrategyQA、FACTOR 和 VicunaQA,对比 'low' 层。不建议将 DoLa 应用于像 GPT-2 这样的小模型,因为论文附录 N 中的结果显示效果不佳。
