文本生成策略
文本生成对于许多 NLP 任务至关重要,例如开放式文本生成、摘要、翻译等。它还在各种以文本作为输出的混合模态应用中发挥作用,例如语音到文本和视觉到文本。一些可以生成文本的模型包括 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
GenerationConfig {
"bos_token_id": 50256,
"eos_token_id": 50256
}
<BLANKLINE>
打印 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,
... )
>>> # Tip: add `push_to_hub=True` to push to the Hub
>>> translation_generation_config.save_pretrained("/tmp", "translation_generation_config.json")
>>> # You could then use the named generation config file to parameterize generation
>>> 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))
['Les fichiers de configuration sont faciles à utiliser!']
流式传输
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)
>>> # Despite returning the usual output, the streamer will also print the generated text to stdout.
>>> _ = model.generate(**inputs, streamer=streamer, max_new_tokens=20)
An increasing sequence: one, two, three, four, five, six, seven, eight, nine, ten, eleven,
水印
generate()
支持通过随机将一部分标记标记为“绿色”来为生成的文本添加水印。生成“绿色”时,它们的 logits 会添加一个小的“偏差”值,因此有更高的机会被生成。可以通过计算文本中“绿色”标记的比例并估计在人类生成的文本中获得这么多“绿色”标记的统计可能性来检测水印文本。这种水印策略在论文 “On the Reliability of Watermarks for Large Language Models” 中提出。有关水印内部功能的更多信息,建议参考本文。
水印可与 tranformers
中的任何生成模型一起使用,并且不需要额外的分类模型来检测水印文本。要触发水印,请将 WatermarkingConfig 与必要的参数直接传递给 .generate()
方法或将其添加到 GenerationConfig 中。水印文本稍后可以使用 WatermarkDetector 进行检测。
WatermarkDetector 在内部依赖于“绿色”标记的比例以及生成的文本是否遵循着色模式。这就是为什么建议去除提示文本的原因,如果它比生成的文本长得多。当批次中的一个序列长得多导致其他行被填充时,这也可能产生影响。此外,检测器**必须**使用生成时使用的相同水印配置参数进行初始化。
让我们用水印生成一些文本。在下面的代码片段中,我们将偏差设置为 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
array([True, True])
解码策略
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)
['I look forward to seeing you all again!\n\n\n\n\n\n\n\n\n\n\n']
对比搜索
对比搜索解码策略在 2022 年的论文用于神经文本生成的对比框架中提出。它展示了在生成非重复且连贯的长输出方面的优异结果。要了解对比搜索的工作原理,请查看这篇博文。启用和控制对比搜索行为的两个主要参数是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)
['Hugging Face Company is a family owned and operated business. We pride ourselves on being the best
in the business and our customer service is second to none.\n\nIf you have any questions about our
products or services, feel free to contact us at any time. We look forward to hearing from you!']
多项式采样
与始终选择概率最高的标记作为下一个标记的贪婪搜索相反,多项式采样(也称为祖先采样)根据模型给出的整个词汇表上的概率分布随机选择下一个标记。每个概率非零的标记都有被选择的可能性,从而降低了重复的风险。
要启用多项式采样,请设置do_sample=True
和num_beams=1
。
>>> from transformers import AutoTokenizer, AutoModelForCausalLM, set_seed
>>> set_seed(0) # For reproducibility
>>> 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)
["Today was an amazing day because we received these wonderful items by the way of a gift shop. The box arrived on a Thursday and I opened it on Monday afternoon to receive the gifts. Both bags featured pieces from all the previous years!\n\nThe box had lots of surprises in it, including some sweet little mini chocolate chips! I don't think I'd eat all of these. This was definitely one of the most expensive presents I have ever got, I actually got most of them for free!\n\nThe first package came"]
集束搜索解码
与贪婪搜索不同,集束搜索解码在每个时间步长都保留多个假设,并最终选择对整个序列具有最高总体概率的假设。这具有识别以较低概率的初始标记开始的高概率序列的优势,而这些序列会被贪婪搜索忽略。
您可以在此交互式演示中可视化集束搜索解码的工作原理:键入您的输入句子,并使用参数来查看解码光束如何变化。
要启用此解码策略,请指定num_beams
(即要跟踪的假设数量)大于 1。
>>> 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)
['It is astonishing how one can have such a profound impact on the lives of so many people in such a short period of
time."\n\nHe added: "I am very proud of the work I have been able to do in the last few years.\n\n"I have']
集束搜索多项式采样
顾名思义,此解码策略将集束搜索与多项式采样相结合。您需要将num_beams
指定为大于 1,并设置do_sample=True
才能使用此解码策略。
>>> from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, set_seed
>>> set_seed(0) # For reproducibility
>>> 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)
'Das Haus ist wunderbar.'
多样化集束搜索解码
多样化集束搜索解码策略是对集束搜索策略的扩展,它允许生成更多样化的集束序列集以供选择。要了解其工作原理,请参阅多样化集束搜索:从神经序列模型解码多样化解决方案。此方法具有三个主要参数: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)
'The 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'
本指南说明了启用各种解码策略的主要参数。generate
方法还存在更高级的参数,这使您可以进一步控制generate
方法的行为。有关可用参数的完整列表,请参阅API 文档。
推测解码
推测解码(也称为辅助解码)是对上述解码策略的修改,它使用具有相同标记器的辅助模型(理想情况下是一个更小的模型)来生成一些候选标记。然后,主模型在单个前向传递中验证候选标记,从而加快了解码过程。如果do_sample=True
,则使用推测解码论文中引入的带重采样的标记验证。
目前,仅支持使用辅助解码进行贪婪搜索和采样,并且辅助解码不支持批量输入。要详细了解辅助解码,请查看这篇博文。
要启用辅助解码,请使用模型设置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)
['Alice and Bob are sitting in a bar. Alice is drinking a beer and Bob is drinking a']
当使用带采样方法的辅助解码时,您可以使用temperature
参数控制随机性,就像在多项式采样中一样。但是,在辅助解码中,降低温度可能有助于提高延迟。
>>> from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
>>> set_seed(42) # For reproducibility
>>> 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)
['Alice and Bob, a couple of friends of mine, who are both in the same office as']
或者,您也可以将prompt_lookup_num_tokens
设置为触发基于n-gram的辅助解码,而不是基于模型的辅助解码。您可以在此处了解更多信息。
DoLa 解码
Decoding by Contrasting Layers (DoLa) 是一种对比解码策略,用于提高大型语言模型 (LLM) 的事实性和减少幻觉,如 ICLR 2024 的这篇论文所述 DoLa:通过对比层解码提高大型语言模型的事实性。
DoLa 通过对比从最终层获得的 logits 与早期层获得的 logits 之间的差异来实现,从而放大特定部分 Transformer 层中本地化的知识。
在调用model.generate
函数时,请执行以下两个步骤以激活 DoLa 解码
- 设置
dola_layers
参数,它可以是字符串或整数列表。- 如果设置为字符串,它可以是
low
或high
之一。 - 如果设置为整数列表,则它应为模型中 0 到总层数之间的层索引列表。第 0 层是词嵌入,第 1 层是第一个 Transformer 层,依此类推。
- 如果设置为字符串,它可以是
- 建议设置
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)
['\nThe Declaration of Independence was signed on July 4, 1776.\nWhat was the date of the signing of the Declaration of Independence?\nThe Declaration of Independence was signed on July 4,']
# 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)
['\nJuly 4, 1776, when the Continental Congress voted to separate from Great Britain. The 56 delegates to the Continental Congress signed the Declaration on August 2, 1776.']
# 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)
['\nIt was officially signed on 2 August 1776, when 56 members of the Second Continental Congress, representing the original 13 American colonies, voted unanimously for the resolution for independence. The 2']
理解 dola_layers 参数
dola_layers
代表早期层选择中的候选层,如 DoLa 论文中所述。所选的早期层将与最终层进行对比。
将dola_layers
设置为'low'
或'high'
将分别选择要对比的层的较低部分或较高部分。
- 对于具有
N <= 40
层的N
层模型,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 层。
论文建议对比'high'
层以改进诸如 TruthfulQA 之类的简答任务,并对比'low'
层以改进所有其他长答案推理任务,例如 GSM8K、StrategyQA、FACTOR 和 VicunaQA。不建议将 DoLa 应用于 GPT-2 等较小的模型,因为论文附录 N 中显示的结果。