Prompt Engineering vs. Blind Prompting

Mitchell Hashimoto

提示工程 vs. 盲目提示

原文由 Mitchell Hashimoto 发布,订阅该博客

“提示工程”这一概念随着语言模型的发展而出现,用来描述通过提示从语言模型中有效提取信息的过程,通常是为了在实际应用中使用。

如今许多自称在做提示工程的人,其实只是在做盲目提示。1 “盲目提示”是我用来描述这样一种方法的术语:以粗糙的试错方式创建提示,几乎不做测试或完全不做测试,对提示技巧也只有非常浅层的了解。盲目提示不是提示工程。

也有很多人质疑,提示工程是否真的能被称为“工程”,还是仅仅是追逐风口的人所鼓吹的“巫术”。我认为,在大多数情况下这种质疑源于这样一个事实:我看到的许多自称在讲提示工程的推文和博客文章,充其量也只比盲目提示高出薄薄一层。

在这篇博文中,我将论证提示工程是一门真正可以基于实验方法论来培养的技能。我会用一个贴近实际的例子,一步步演示如何通过提示工程来解决一个能为应用带来实际价值的问题。

整篇文章都将偏向于以文本输出为前提,因为文本输出是我使用语言模型时的主要场景。某些技术——比如测试技术——并不能一对一地照搬到图像等其他类型的输出上。不过,本文中的所有内容对于多模态输入都是适用的。


什么是提示?

如果你已经非常熟悉“提示”这个术语或明白什么是“提示词”,可以跳过本节。

对于语言模型(如果你不熟悉这个术语,可以把它想象成 ChatGPT),“提示词”就是用户向模型提供的输入。在 ChatGPT 中,你可以把它理解为你输入文字的那个文本框。语言模型随后会“推断”出对提示词的“补全”。例如,如果我在 ChatGPT 中输入“4 + 3 = ”,它很可能会回复“7”。在这个例子中,“4 + 3 = ”就是提示词,而“7”就是补全结果。

“提示”就是利用提示词来从模型中提取所需信息的行为。这种提取信息的方式之所以有吸引力,是因为你不需要大规模的离线训练集,不需要离线访问模型,而且即使对非工程师来说也感觉很直观。提示只是调优模型的一种方法

最后,“提示工程”描述的是一门更严谨的学科(如本文后续将展示的),旨在利用提示来为实际应用构建可靠的功能。它与 ChatGPT 式的提示不同,因为通过提示工程生成的提示通常是为了在大量、多样的场景中反复使用,从而可靠地为应用解决某个特定问题。


问题

首先,你必须有一个想要解决的问题。这个问题可以用来评估提示是否是最好的解决方案,或者是否存在更合适的替代方案。工程的出发点不是为了用某种方法而用方法,而是基于它是正确方法这一信念。

在这个例子中,我们假设自己是一家开发日历客户端的公司。我们希望用户能够用自然语言输入日程。例如:

Dinner with Alice next Tuesday at Taco Bell

CorpConf on 11/4

1:1 with Bob tomorrow at 10 AM

语言模型可能是将这种自然语言输入转化为结构化输出、进而在我们的应用中加以利用的一个不错的解决方案。

显然也存在其他可能的解决方案。我们可以利用一组正则表达式和字符串搜索来查找常见短语(on <Day of Week>tomorrowtodaynext week等)。

语言模型也有其自身的优势:它们或许能更好地处理其他语言,或更好地处理拼写错误或语法错误,又或者至少在正则表达式失效时能作为一个不错的兜底方案。无论如何,继续探索将提示作为潜在解决方案是有足够前景的。


演示集

接下来,我们需要准备一个演示集。演示集包含预期的输入和对应的预期输出。这个集合将服务于多个目标:

  1. 它将用于衡量我们提示的准确率。通过使用单个演示的输入,我们可以断言是否得到了预期的输出。

  2. 它明确了我们期望的提示输入和输出是什么样子,让我们作为工程师能够判断这种形态是否适合我们要解决的问题。

  3. 如果我们选择使用少样本提示,就可以将该演示集的一个子集用作示例。对于不熟悉“少样本”这一术语的读者,少样本是一种除了提示之外还提供示例的提示方式。参见关于少样本与零样本提示的概览

上面第(2)点极其重要。我们需要对预期的输入和预期的输出有一个大致的了解,因为在这两端的通常都是需要确保数据符合特定格式、并期望以特定格式返回的软件。这与典型的软件工程并无二致——我们将问题分解为若干具有输入/输出约定的函数。

我们可以利用之前的例子,将其扩展为完整的演示:

Q: Dinner with Alice next Tuesday at Taco Bell
A: next Tuesday

Q: CorpConf on 11/4
A: 11/4

Q: 1:1 with Bob tomorrow at 10 AM
A: tomorrow

关于演示集大小的说明:在这篇博文中,我们只有三个演示。在实际中,你可能至少需要十几个。演示越多,测试就越充分,但由于 token 的消耗,成本也会越高。当规模达到一定程度时,微调语言模型往往会更经济。

上述演示中有两个重要的决策。对于任何提示问题,你都需要做出类似的决策。

第一,我们只提取一条信息。你可能会想让模型一次性提取整个日程,比如事件名称、参与者、时间、地点等,并输出为可直接使用的漂亮 JSON 或其他格式。模型也许能做到。但在着手一个新问题时,我建议先将其分解为单一问题。这样问题更易于处理,也能为你提供一个基线准确率,用来衡量多输出方案是否真的值得。

第二,我们的输出没有经过转换。我们没有试图把所有内容都转换成日期,也没有试图确保所有内容的大小写都正确,等等。我们做的只是字面上的文本抽取。有许多非常成熟的确定性库可以将“next Tuesday”这类字符串以极高的准确率转换为时间戳。这不是我们需要语言模型来替我们做的事。所以,我们只需提取出粗略的日期形式(“next Tuesday”、“11/4”、“tomorrow”),然后用传统的编程方法来解决将其转换为时间戳的问题,因为这是一个简单的问题。输出越简单,就越容易获得更高的准确率。

最后,简要谈谈输出解码大语言模型会以各种方式补全你的提示:它可能是一个完整的句子,可能会加句号,可能会大写,等等。你应该确定自己希望从大语言模型得到的输出有多完美,以及在验证演示集之前愿意做多少归一化处理。

例如,如果我在做文本抽取,我通常认为对输出进行去首尾空格、去句号并全部转小写是合理的。如果我在做更复杂的事情,比如生成 JSON,我可能会以某种确定的顺序和风格解析并重新编码 JSON,以便比较是确定性的。诸如此类。

我的建议是:让大语言模型的输出尽可能简单、灵活,并在应用中执行一些归一化操作。一开始不要试图强迫大语言模型输出完全精确的格式。过早地在模型层面做过多的“输出塑形”,会让你难以区分大语言模型执行核心任务(如本例中的信息抽取)的能力与其格式化输出的能力。


候选提示

接下来我们要想出一些候选提示。候选提示是指我们认为可能会引发语言模型产生我们期望行为的提示。我们会准备多个候选,因为不太可能一开始就选出最佳提示。

为了保持入门级别,本文将手动构思提示。要想高效,提示工程师在构建提示时应该运用一些基础知识。例如,陈述式通常比防御式更好。清晰简洁通常比重复冗长更好。在构建少样本提示时,标签的均匀分布很重要,展示完整的标签集合也很重要,等等。在选择示例时,通常表现最好的是那些大语言模型很可能答错的示例,有研究表明示例按从短到长排序时往往效果最好,等等。

此处需要引用!很抱歉,我没有为这些建议标注实验研究的出处。实话说,我懒得去翻我读过的那些论文(每一点往往都有多篇)。如果你选择不相信我,也没关系,更重要的是,关于提示技巧及其有效性的实验研究是存在的。但我保证这些不是我编造的,尽管其中某些可能随着现代模型的发展已经过时。

单就这些技巧就可以另写一篇专门的文章,这也不是本文的目标。本文的目标是展示高层次的端到端流程,并表明从大语言模型中提取价值是存在工程方法的

在这一点上,目标是想出一些不错的零样本提示。零样本提示可以转化为少样本提示,进而再转化为思维链提示。而每一种又可以进一步转化为批量提示,等等。因此,由于零样本是尝试的基础要求,我们先聚焦于此。

以下是我想出的三个候选提示:

Identify the date or day mentioned in the given text and provide it as the output.

Identify the date or day mentioned in the given event description.

Determine the date or day from each input and provide the output accordingly as a single word or date.

它们都是合理的提示。对于任何受过教育的人来说,每个提示很可能都会产生非常高的准确率。但语言模型不是人,所以我们不能自动期望它们有同等的表现。我之前已经展示过非常合理的提示也可能表现得一塌糊涂。所以我们的下一步是通过测试和度量来为决策提供依据。


提示测试

有了候选提示集和演示集,我们现在就可以度量准确率了。我发现目前最好的方法是使用类似 LangChain 这样的库来构建一个简单的 Python 脚本。在测试时,我通常会遍历每个演示,并使用如下的提示模板:

{{prompt}}. Q: {{input}} A:

我总是先测试零样本。我想得到一个基线准确率。在此之后,你可以再测试少样本,并比较不同候选以及不同提示类型。依此类推。

这必须针对每个模型分别进行。即使是同一个提示,在更强大的模型上也不一定能保证有相同或更高的准确率;准确率可能会下降2

提示测试最基础的结果应该是一张如下所示的表格。你可能还会有额外的维度,比如模型(例如 GPT-3.5 vs GPT-4)。

           | Zero-Shot | Few-Shot | ... |
-----------------------------------------
Prompt 1   | 64        | 68       | ... |
-----------------------------------------
Prompt ... | 44        | 52       | ... |
-----------------------------------------
Prompt N   | 23        | 22       | ... |

这张表在 Y 轴上展示候选提示,在 X 轴上展示使用这些提示的提示类型。数值是正确答案占比的准确率。继续以日历应用为例,“提示 1”作为零样本提示,配合一个演示可能看起来像这样:

Identify the date or day mentioned in the given text and provide it as the output. Q: CorpConf on 11/4 A:

而我们期望的答案是“11/4”。少样本版本可能看起来像这样:

Identify the date or day mentioned in the given text and provide it as the output.

Q: Dinner with Alice next Tuesday at Taco Bell. A: next Tuesday

Q: 1:1 with Bob tomorrow at 10 AM. A: tomorrow

Q: CorpConf on 11/4. A:

给有经验的提示者的说明:少样本示例中并没有写类似“模仿下面的示例”这样的话。实验研究表明这并不能可靠地提高准确率,所以我喜欢先在不加它的情况下进行测试,以节省 token。其次,少样本示例的示范中从未展示“MM/DD”这种抽取形式,这是不规范的。在真正的少样本设置中,展示所有抽取样式可能很重要(Zhao 等,2021)。

对于某些类型的问题,比如分类问题,你可以使用混淆矩阵(Strobelt 等,2022)来可视化其他标签的概率,并据此判断你的标签集是否可以进一步调优。

除了准确率,你还需要度量使用的 token 数、请求数等。在选择最终提示时,所有这些都必须纳入考量。


选择提示

最后,你要选择一个候选提示集成到应用中。这不一定是最准确的提示。这是一个基于所用模型、所需 token 和所呈现准确率的成本与准确率权衡。

例如,你可能会发现少样本变体表现最好,但它在测试集上只准确了 4%,却需要多 200% 的 token(对于当前基于 API 计费的模型来说,成本实际上翻了一倍)。你可能会为你的业务判断,少 4% 的准确率换取一半的成本是值得的。

或者,你也可能决定回去测试其他提高准确率的方法。例如,你可以在较低成本的模型上尝试自洽解码策略(Wang 等,2022),看看是否能足够提高准确率。有时,在低成本模型上使用更多 token,会比在高成本模型上使用少量 token 节省更多费用。例如,GPT-4 目前比 GPT-3.5 贵约 15 倍。这意味着你实际上有 15 倍的 token 预算来提升 GPT-3.5 提示的准确率(当然还需注意速率限制等)。

在本文的例子中,我们可能会选择表格中 Prompt 1 的零样本版本,因为它有 64% 的准确率,且可能需要显著更少的 token。也许我们认为 64% 的准确率已经足以至少为我们的日历应用填充一个日程模板。就这个问题而言,我认为我们实际上可以做得远好于 64%,但这只是本文中使用的数字。

最重要的是,你拥有了做出明智决策所需的数据。


信任但要验证,并持续改进

由于生成式 AI 的概率性,你的提示很可能存在一些问题。即使你在测试集上的准确率是 100%,也可能存在会产生错误输出的未知输入。因此,你应该信任但要验证,并将验证失败的案例加入到演示集中,以便开发新的提示并提高准确率。

验证在很大程度上取决于具体问题。对于我们日历应用的例子,我们可能想明确地询问用户:“这个日程正确吗?”如果他们回答“否”,就记录下自然语言输入以供人工审查。或者,我们或许可以做得更好,自动追踪用户在自动信息抽取后手动修改的任何日程3

再举一个例子,如果我们的提示正在生成代码(比如正则表达式或某种编程语言文本),我们至少可以尝试解析它。解析永远不应该成为安全隐患4,而且它至少能提供最基础的验证,即至少语法是正确的。同样,如果验证失败,我们可以记录输入和输出,扩充演示集,并开发更好的提示。

验证也有助于防范对抗性提示。对抗性提示本身就是一个完整的话题,我不会在这篇文章中展开。


继续前行……

这篇博文展示了开发提示如何——在我看来——可以成为一种工程实践。它描述了一种系统化的方法来识别问题、形成解决方案、验证这些解决方案,并通过持续改进来完善它们。

将本文中的方法与“盲目提示”相比,后者依赖轶事经验和普遍的试错来得出某种提议的解决方案,并且往往没有构建适当的系统化基础设施,以便随着时间的推移可靠地迭代提示。

我想指出,这篇博文是非常基础的。本文中有多处可以用已经广为人知的更高级技术来改进。此外,我没有涵盖诸如对抗性提示等重要主题。举一个具体的例子,有更科学的方法来为少样本提示选择最佳示例,但我希望尽可能将本文保持在基础层面。如果你想学习一些更高级的技巧,Lilian Weng 的《提示工程》提供了一个非常棒的概览。

此外,每个人都在迅速转向更高阶的大语言模型集成:提示链、智能体等。有人认为,像这样的未来创新以及更多创新将使人工提示变得过时。无论这是否属实,我是一个相信从“第一性原理”5出发学习是有价值的人,我认为学习本文这样的提示技巧只会提升我运用更高阶语言模型技术的能力。我也相信,像这样基础的提示仍然能够让更高阶的概念表现得更好。

脚注

  1. 也许是很多“吵闹”的人。我遇到并向许多真正将良好工程实践应用于该领域的出色的提示工程师学到了很多。遗憾的是,我在 Twitter 等平台上看到的许多噪音往往并非如此。

  2. 又一个需要引用的地方。同样,我犯懒了,但这是基于我在多篇论文中读到的实验研究。如果你选择不相信我,那就自己测试一下吧。

  3. 这里显然有隐私方面的影响。我只是举个例子,该技术是否合适取决于真实场景。

  4. YAML。😐

  5. 我意识到真正的“第一性原理”会是更底层的。我在这里使用“第一性原理”这个短语,是取其常见的比喻义,指某个任意的底层起点,在此之上可以构建更多知识。

本文章由 muse-spark-1.2-contributor 进行翻译

评论