原文: Automated Prompt Engineering

作者使用 DALL-E 的帮助生成的图片
在过去的几个月里,我一直在努力构建各种基于大型语言模型的应用程序,坦率地说,相当大一部分时间都是专门用来改进提示,以获得我期望的语言模型输出。
有许多时刻,我陷入某种存在主义虚空,问自己是否可能只是一个被美化的提示工程师。鉴于当前与语言模型的互动状态,我仍倾向于得出“还没有”的结论,在大多数夜晚,我都能克服自己的冒名顶替综合症。今天不会深入讨论这个问题。
但我仍经常思考,也许有一天,写提示的过程可以大部分自动化。我认为对这种未来场景的答案取决于揭示提示工程的本质。
尽管互联网上有无数的提示工程指南,但我仍无法确定提示工程是一门艺术还是科学。
一方面,当我根据输出中观察到的情况迭代学习和编辑我的提示时,它感觉像一门艺术。随着时间的推移,您会发现一些微小细节很重要——例如使用“必须”而不是“应该”,或者将指导方针放在提示的末尾而不是中间。根据任务的不同,有太多表达一组指令和指导方针的方式,有时候感觉像是反复试错。
另一方面,有人可能会认为提示只是超参数。最终,语言模型只是将您的提示视为嵌入,并且像所有超参数一样,如果您有一组已建立的训练和测试数据,可以对其进行调整并客观地衡量其性能。我最近看到了 HuggingFace 的机器学习工程师 Moritz Laurer 发布的这篇文章:
每次在您的数据上测试不同的提示时,您就会越来越不确定语言模型是否真的能够泛化到未见过的数据…… 使用单独的验证拆分来调整语言模型的主要超参数(提示)与训练-验证-测试拆分一样重要。唯一的区别在于您不再有训练数据集,这在某种程度上感觉不同,因为没有训练/参数更新。很容易让自己相信语言模型在您的任务上表现良好,而实际上您已经在数据上过拟合了提示。每篇好的“零样本”论文都应澄清他们在最终测试之前使用了验证拆分来找到他们的提示。
经过一番思考,我认为答案介于两者之间。提示工程是一门科学还是一门艺术,实际取决于您试图让语言模型做什么。我们在过去一年中看到语言模型做了许多令人惊叹的事情,但我喜欢将我们的意图归类为两个主要类别:解决与创造。(是的,这是一种过度简化,但请饶过我关于它们可能不是不同概念的哲学辩论,让我们继续吧)
在解决方面,我们让语言模型解决数学问题、分类情感、生成 SQL 代码、翻译文本等。总的来说,我认为这些任务作为一个类别,因为它们可以具有相对明确定义的输入-输出对(因此您会看到许多例子表明它们在少量提示下工作良好),对于这类具有明确定义训练数据的任务,我认为提示工程更倾向于被我描述为一门科学。因此,本文的前半部分将从提示作为超参数的视角出发,专注于探索自动提示工程的研究进展。
在创造方面,语言模型的任务更具主观性和模糊性。写电子邮件、报告、诗歌、摘要。在这个人工智能领域,我们也遇到了更模糊的问题——ChatGPT 在写作中是否缺乏某种人性?(根据我要求它写的成千上万件事情,我的当前看法是肯定的)由于我们经常缺乏对我们希望语言模型做出的反应的更客观的基本事实,因此很难将提示框架化为这类创造任务的超参数。
在这一点上,有些人可能会说,对于创造任务,我们只需要运用常识。老实说,我曾经也是这样认为,直到我试图教我妈妈如何使用 ChatGPT 为她的工作生成电子邮件。在这些情况下,由于提示工程仍然是一个主要是迭代的过程,如何将对语言模型输出的想法转化为改进的提示版本并同时保留泛化性的元素并不总是立即明显(就像前面引用的那句话)。
无论如何,我四处寻找一个工具,可以帮助用户根据生成的示例反馈自动改进提示,但实际上并没有找到什么。因此,我建立了一个原型,只是为了看看可能性是什么。在本文的后半部分,我将分享我尝试根据实时用户反馈自动改进提示的工具。
第一部分 — 语言模型作为解决者:提示工程作为超参数优化
我们中许多人熟悉著名的 Zero-Shot-COT 论文中的“让我们逐步思考”(“Let’s think step by step”)。嗯,Zhou 等人(2022)决定在大型语言模型是人类级提示工程师中进一步探讨。他们改进的版本是?“让我们逐步解决这个问题,以确保我们有正确的答案”。以下是他们的自动提示工程方法的概述:
总结:
- 使用语言模型生成给定输入-输出对的候选指令
- 对于每个候选指令,使用语言模型计算分数,可以基于准确性或所需答案的对数概率
- 根据高分候选生成新的候选,进行迭代
一些有趣的结果:
- 除了展示人类提示工程师和先前提出的算法的表现外,作者指出“令人费解的是,为押韵添加上下文示例……会损害模型性能……因为所选指令过度拟合了零样本学习场景,因此在少样本情况下表现不佳”
- 迭代蒙特卡洛搜索大多会有递减回报,但在原始提议空间不佳时表现良好
然后在 2023 年,一些来自 Google DeepMind 的研究人员提出了提示优化(OPRO)。与前一个例子类似,元提示包含元提示内的输入/输出对示例。这里的关键区别在于,元提示还包含先前提示与解决方案以及训练准确性,以及详细说明元提示不同部分之间关系的辅助指令。
正如作者解释的那样,“我们工作中的每个优化步骤都生成新的提示,旨在基于先前生成的提示的轨迹增加测试准确性”。
来源:大型语言模型作为优化器
在 Zero-Shot-COT 场景中,他们提出了“深呼吸,逐步解决这个问题”作为表现优异的提示。
对此有一些想法:
- “不同优化器语言模型找到的指令风格差异很大:PaLM 2-L-IT 和 text-bison 的指令简洁,而 GPT 的指令则详细而长。” — 我认为这一点值得更多关注。今天许多提示工程指南都是针对 OpenAI 的模型编写的,但随着我们开始采用其他开源/闭源模型,我们应该记住相同的指导原则可能效果不佳。这一点在论文的 5.2.3 部分有所体现,展示了对指令微小变化对性能敏感性的示例。
例如,在 GSM8K 测试集上,使用 PaLM 2-L 评分器,“让我们逐步思考。”的准确率为 71.8,“让我们一起解决问题。”的准确率为 60.5,而“让我们一起逐步解决这个问题。”的准确率仅为 49.4,尽管它是前两个指令的语义组合。这种行为增加了单步指令之间的方差和优化过程中的振荡,并激励我们在每个步骤生成多个指令以提高优化稳定性。
对于那些对使用 APE 感兴趣的人,已经在以下 repo 上提供了一个库。这里有一个示例 notebook,非常易于使用。
来源:来自 APE 示例笔记本的截图
在 APE 和 OPRO 中,关键要求是您已经有训练数据来完成优化,并且数据集需要足够大,以确保优化提示的泛化性。
现在,我想谈谈另一类 LLM 任务,其中可能没有准备好的数据。
第二部分 — LLM 作为创作者:提示工程作为迭代改进
假设您正在尝试想出短篇故事的创意。
问题在于,您根本没有示例可以开始训练模型,而编写示例需要太长时间。而且,是否编写一个“正确”的答案并不清楚,因为可能有许多版本的输出是可以接受的。因此,对于这类任务,使用 APE 等方法自动化提示工程是不切实际/不可能的。
但是,您可能会问,我们为什么需要自动化编写这个提示过程呢?您可以从一个简单的提示开始,比如“为我提供关于 {{issue}}
在 {{country}}
的 3 个短篇故事创意”,将 {{issue}} 替换为‘不平等’,{{country}} 替换为‘新加坡’,观察结果,识别问题,调整提示,希望它有效,并重复这个过程。
那么,在这些情况下,谁会最受益于提示工程呢?正是那些不是经验丰富的提示撰写者的用户,因此,他们在尝试根据通过实验观察到的情况优化自己的提示时经验较少。当我试图教我妈妈如何指导 ChatGPT 完成她工作中的一些任务时,我亲身经历了这一点。
我妈妈可能不擅长将她对 ChatGPT 输出的不满转化为改进的提示,但我意识到,无论提示工程技能如何,我们都非常擅长表达我们看到的问题(即抱怨)。因此,我尝试构建一些东西来实现这种抱怨,并让 LLM 为我改进提示。对我来说,这似乎是对于我们这些试图使用 LLM 执行这些创造性任务的人来说更自然的互动方式。
免责声明,所有这些都只是一个概念验证,所以请随时与我分享如何使其更好!
首先,使用 {{}} 中的变量占位符编写输入提示。应用程序将检测这些占位符,以便您随后填写。在这里,我们使用上面相同的示例,尝试生成关于新加坡不平等问题的故事创意。
接下来,应用程序根据填写的提示生成响应。
我们对响应提出了一些反馈:
我们要求应用程序停止生成更多示例,并提出一个改进后的提示的第一次迭代。请注意下面,提示已经改进和泛化,其中包含一条指示*“描述克服或应对这些挑战的策略…”。这是尽管我的具体反馈是“谈论角色如何克服不平等”*。
然后,我们用改进后的版本替换原始提示,并要求它再次生成关于短篇故事创意。
您还可以选择按“生成下一个示例”,这将允许您基于其他输入变量生成另一个响应。下面,我们生成关于中国裁员问题的一些故事创意:
我们给出了一些反馈:
最后,我们得到了一个进一步改进的提示:
我认为这还不错,从一个简单的单句提示开始,经过不到两分钟的(诚然相当非正式的)反馈,在三次迭代中演变为这个改进后的提示。现在,您只需坐下来批评 LLM 的输出,它将为您进行改进。
在幕后,这是进行所有工作的元提示,根据用户的动态反馈生成新提示。没有花哨的东西,肯定可以改进,但是一个不错的开始。
prompt_improvement_prompt = """
# 上下文 #
您获得了一个原始提示。
原始提示用于生成一些示例响应。对于每个响应,都提供了有关如何改进期望响应的反馈。
您的任务是审查所有反馈,然后返回一个改进后的提示,以解决反馈,使其在提示对 GPT 语言模型进行提示时生成响应更好。
# 指南 #
- 原始提示将包含双花括号内的占位符。这些是您将在示例中看到的输入值。
- 改进后的提示不应超过 200 个单词
- 只返回改进后的提示,不要在之前和之后添加任何其他内容。记住使用双花括号内的相同占位符。
- 在生成改进提示时,不要将整个提示写成一个段落。相反,您应该使用任务描述、指南(以点形式)、和其他适当的部分来编写提示。
- 指南应以点形式呈现,不应重复任务。指南也应相互区分。
- 改进后的提示应以最容易被语言模型理解的正常英语书写。
- 根据提供的反馈,您必须将响应的期望行为重新表述为 `必须`,而不是 `应该` 的建议性陈述。
- 对提示的改进不应过于特定于单个示例。
# 详细信息 #
原始提示是:
```
{original_prompt}
```
这些是提供的示例和每个示例的反馈:
```
{examples}
```
改进后的提示是:
```
"""
通过使用这个工具玩耍时的一些观察:
- 冗长是 GPT4 的一种自然倾向。由于这一点,我想到了两个影响。首先,冗长可能会鼓励过度拟合到特定示例。给予 LLM 太多单词,它将使用这些单词来修复您提供的具体反馈。其次,冗长可能会损害提示的有效性,特别是如果重要的指南淹没在长提示中。 — 我认为第一个问题可以通过确保撰写良好的元提示来鼓励基于反馈的泛化性来克服。但第二个问题更为棘手,在其他用例中,我看到指示在提示变得太长时被忽略。您可以在元提示中施加限制(例如使用像我上面所做的字数限制),但这实际上是任意的,并且对您使用的基础模型非常敏感。
- 改进后的提示有时会忘记基于您多次迭代给出的反馈而进行的改进。克服这一点的一种方法是为其提供更长的改进历史,但这将使改进提示过于冗长。
- 这种方法在初始迭代中的一个优势是,LLM 可能会提供超出您反馈的改进指南。例如,在上面的第一个改进中,它添加了*“提供关于讨论问题的更广泛视角…”*,即使我的反馈只是关于提供统计数据和可信来源。
我尚未部署这个工具,因为我仍在尝试玩转元提示,看看哪种方法最有效,并解决一些 streamlit 实现和错误处理的问题。但即将推出!
总结
有一个专注于为解决任务找到最佳提示的提示工程领域。APE 和 OPRO 只是其中最突出的例子,看到我们可以得到多么优化是令人兴奋的。评估这些技术在不同模型上的效果也很重要,以揭示这些模型的倾向,以及哪种元指令技术有效,所以我认为所有这些都是可以帮助塑造我们对 LLM 实际使用的重要工作。
但对于我们其他人来说,希望将 LLM 用于我们自己的创造性任务,这些方法可能不适用。目前,指南书可以帮助您入门,但真正的试错是无可替代的。因此,在短期内,我认为我们如何有效地进行这个与我们的人类优势(提供反馈)相一致的试验过程是最重要的。让 LLM 做剩下的工作(改进提示)。
参考文献
- https://arxiv.org/abs/2104.08691
- https://arxiv.org/pdf/2211.01910.pdf
- https://arxiv.org/pdf/2309.03409.pdf
- https://arxiv.org/pdf/2205.11916.pdf
- https://medium.com/mantisnlp/automatic-prompt-engineering-part-i-main-concepts-73f94846cacb
- https://github.com/keirp/automatic_prompt_engineer
- https://www.promptingguide.ai/techniques/ape