通过符号程序搜索提升prompt工程

原文地址:supercharging-prompt-engineering-via-symbolic-program-search

通过自动探索​​大量提示变体来找到更好的提示

2024 年 4 月 22 日

众所周知,LLMs的成功在很大程度上仍然取决于我们用正确的指导和例子来提示他们的能力。随着新一代LLMs变得越来越强大,提示已经变得足够复杂,足以被视为课程本身。这些提示程序很像菜谱——都有一套遵循和转换原材料的指令,无论是数据还是成分。

因此,提示工程类似于改进配方。家庭厨师通常会坚持整体食谱,但会做出一些小改变—例如在面食中省略大蒜或添加欧芹。像DSPy这样的框架在优化上下文示例时遵循这个整体范例。然而,专业级厨师以食谱为灵感,经常完全重新诠释菜肴的成分。例如,他们可能将面食中的意大利面视为淀粉成分,并可能将其替换为新鲜制作的面疙瘩以获得类似的成分。

是什么让专业级厨师能够如此富有创意地工作?他们以抽象的方式思考食谱,就像上面的意大利面例子一样。手动提示工程类似于专业级烹饪。它可以获得令人印象深刻的结果,但需要大量的时间和知识。我们真正想要的是手动提示工程的创造力,但又不需要付出努力。

抽象提示的力量

假设我们想改进一个用于标注说话人回答的提示。我们最终会用许多不同的输入来运行它,但现在先插入一个具体的输入:

Instructions: Does Speaker 2's answer mean yes or no?
Output labels: no, yes
Input: Speaker 1: "You do this often?" Speaker 2: "It's my first time."
Output:

暂且假设我们有一个抽象的提示表示法,它可以抽出不同的组成部分,并且易于操作。也许可以这样:

5

有了它,你就可以自动完成在及时原型开发过程中必须进行的大量(半)手工修补工作。进行诸如转述之类的小编辑只是一个开始。想试试思维链推理吗?添加一段 “让我们逐步思考”。将数据格式改为 JSON 格式如何?只需更改 InputData 参数的 formatattribute 即可。你还可以探索:

  • 从单一示例到批量注释
  • 在 RAG 场景中更改检索器和排序功能
  • 重新排列某些段落的顺序
  • 压缩说明的某些部分
  • ETC

从本质上讲,你可以输入你最喜欢的提示工程启发式。这种抽象的提示表示法让我们能够真正发挥创意,自动探索大量可能的提示。

将提示转化为抽象程序

要表示抽象提示符,我们首先要将其转换为非符号提示符程序,将其分解为单独的组件,以 Python 类的形式实现:

class Component:def __init__(self, **kwargs): pass
class Metaprompt(Component): pass
class Paragraph(Component): pass
class InputData(Component): passprompt = Metaprompt(children=[Paragraph(text="Instructions: "),Paragraph(id="instructions",text="Does Speaker 2's answer mean yes or no?",),Paragraph(id="labels", text="Output labels: yes, no"),InputData(),Paragraph(text="Output: "),]
)

到目前为止,一切顺利。这与 DSpy 所做的工作类似,但更为通用,因为我们还表示了提示符的内部结构。

接下来,我们要把它变成一个符号提示程序,这样就可以进行任意修改(这也超出了静态 DSPy 程序的范围)。pyGlove 将 Python 类转化为可操作的符号对象,其属性在实例化后仍可完全编辑。

有了 pyGlove,我们只需添加 pg.symbolize 装饰器即可:

import pyglove as pg
@pg.symbolize
class Component:def __init__(self, **kwargs): pass

现在,我们可以通过大量的说明符来查询和修改提示程序,就像使用 DOM 树一样。比方说,我们想把上面的程序转化为下面的程序:

6

请注意,我们现在问的是 "回答是否表示是?",而不是提供 "是 "和 "否 "的输出标签。为此,我们需要 (i) 更改指令文本,(ii) 删除第三个节点。有了 pyGlove,这就很容易了:

prompt.rebind({'children[1].text': 'Does the response mean yes?'})'children[1].text': 'Does the response mean yes?'})
prompt.rebind({'children[2]': pg.MISSING_VALUE})
print(prompt)

打印输出确认我们成功了:

Metaprompt(children = [0 : Paragraph(text = 'Instructions: '),1 : Paragraph(id = 'instructions',text = 'Does the response mean yes?'),2 : InputData(),3 : Paragraph(text = 'Output: ')]
)

就是这样!从根本上说,pyGlove 给了我们一种处理 Python 类(和函数)的方法,就像处理源代码一样,而且开销很小。现在,我们有了灵活且易于操作的表示法,让我们来使用它们吧。

等一下。我们现在可能有了表示和修改提示的方法,但还缺少一个自动优化提示的过程。

一旦厨师们了解了菜谱的抽象概念和组成部分,他们就会尝试多种变体,改进口味、成本或表现形式,直到感觉合适为止。要对提示抽象进行同样的处理,我们需要一种搜索算法、一个目标以及一组标注样本,这样才能知道我们是否取得了进展。

使用 SAMMO 调整指令

为了说明 SAMMO 的核心工作流程,我们现在将展示如何调整上面提示示例中的指令部分。一旦我们完成了这个玩具示例,我们就可以讨论更高级的应用,如 RAG 优化或压缩。

关键步骤如下:

  1. 确定起始提示
  2. 准备好数据--几百个带标签的示例就足够了。
  3. 确定目标
  4. 选择一组突变体
  5. 运行优化

步骤 1:定义开始提示

这一步我们在上文已经做得差不多了。SAMMO 期望使用一个函数,因此我们必须将其封装为一个函数。如果你想存储额外的信息,可以用 Callable 代替。我们还将把它封装在一个输出组件中以运行它。

def starting_prompt():instructions = MetaPrompt(Paragraph(text="Instructions: "),Paragraph(id="instructions",text="Does Speaker 2's answer mean yes or no?",),Paragraph(id="labels", text="Output labels: yes, no"),InputData(),Paragraph(text="Output: "),)return Output(instructions.with_extractor())

步骤 2:准备好数据

SAMMO 使用名为 DataTable 的简单数据结构将输入与输出(标签)配对。这将有助于我们进行评估和记账。

mydata = DataTable.from_records(records, # list of {"input": <>, "output": <>}# list of {"input": <>, "output": <>}constants={"instructions": default_instructions}, 
)

步骤 3:确定目标

我们感兴趣的是优化精确度,因此这就是我们下面要实现的目标:

def accuracy(y_true: DataTable, y_pred: DataTable) -> EvaluationScore:y_true = y_true.outputs.normalized_values()y_pred = y_pred.outputs.normalized_values()n_correct = sum([y_p == y_t for y_p, y_t in zip(y_pred, y_true)])return EvaluationScore(n_correct / len(y_true))

步骤 4:选择一组突变体

在这里,你可以尽情发挥自己的创造力。你可以实现自己的运算符来生成新的提示变体,也可以简单地依赖 SAMMO 提供的预置变异运算符。

在下文中,我们将采用后者,从一些标注的示例中混合使用转述和诱导指令,基本上实现了自动提示工程(APE)。

mutation_operators = BagOfMutators(starting_prompt=StartingPrompt(d_train),InduceInstructions({"id": "instructions"}, d_train),"id": "instructions"}, d_train),Paraphrase({"id": "instructions"}),
)

步骤 5:运行优化

runner = OpenAIChat(model_id="gpt-3.5-turbo-16k","gpt-3.5-turbo-16k",api_config={"api_key": YOUR_KEY},cache="cache.tsv",
)
prompt_optimizer = BeamSearch(runner, mutation_operators, accuracy, depth=6)
transformed = prompt_optimizer.fit_transform(d_train)

介绍性提示示例实际上取自 BigBench 含义任务,我们将用它来运行本实验。如果使用 100 个样本进行训练和测试,并以 48 个候选评估为预算进行优化,你会发现 SAMMO 将起始提示的准确率从 0.56 提高到了 0.77 - 提高了 37.5%。哪些指令效果最好?

...
Paragraph("Consider the dialogue, context, and background ""Consider the dialogue, context, and background ""information provided to determine the most suitable output label",id="instructions",
)
...

有趣的是,不同的 LLM 喜欢的指令大相径庭。如上所述,GPT-3.5 最喜欢通用指示。在相同的训练和预算设置下,SAMMO 选择的 Llama-2 最佳提示在指令部分使用了空字符串:

...
Paragraph("","",id="instructions",
)
...

实际操作: RAG 调整

现在,我们将展示如何将 RAG 管道转换为符号程序,并使用 SAMMO 对其进行调整。我们将使用语义解析作为应用任务,将用户查询转化为特定领域语言 (DSL) 结构,例如,查询某些数据库或调用外部 API。

为了创建起始提示,我们列出了所有运算符的列表,使用基于嵌入的检索器获取五个少量示例,然后指示 LLM 以与示例相同的格式输出答案。

class RagStartingPrompt:def __init__(self, dtrain, examples, embedding_runner):self._examples = examplesself._dtrain = dtrainself._embedding_runner = embedding_runnerdef __call__(self, return_raw=False):structure = [Section("Syntax", self._dtrain.constants["list_of_operators"]),Section("Examples",EmbeddingFewshotExamples(self._embedding_runner, self._examples, 5),),Section("Complete and output in the same format as above",InputData(),),]instructions = MetaPrompt(structure,render_as="markdown",data_formatter=JSONDataFormatter(),)  return Output(instructions.with_extractor(),on_error="empty_result",)

现在我们有了一个符号程序,让我们发挥创意吧。对于突变,我们探索:

  • 不同数量的少量示例
  • 少量示例的不同格式(XML、JSON、逐行格式
  • 是否提供有关 DSL 的附加信息
  • 显示输入输出对或输入输出组

使用这些示例和总共 24 个候选示例运行 SAMMO,我们可以看到一个明显的趋势。以下是四个不同 LLM 的三个不同数据集的测试集准确率。在绝大多数情况下,我们可以看到 SAMMO 可以大幅提升性能,即使是性能最高的 LLM 也不例外。

7

结论

将提示语转换为符号程序是一个非常强大的想法,可以探索可能的提示语和设置的巨大设计空间。就像专业厨师通过解构和重新诠释食谱来进行烹饪创新一样,符号编程也能让我们在自动提示工程中发挥同样的创造力和实验精神。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/6151.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA面试题---WEB部分

网络通讯 TCP与UDP TCP(Transmission Control Protocol 传输控制协议)是一种面向连接(连接导向)的、 可靠的、 基于 IP 的传输层协议。 UDP 是 User Datagram Protocol 的简称&#xff0c;中文名是用户数据报协议&#xff0c;是 OSI 参考模 型中的传输层协议&#xff0c;它是…

【杂谈】记录代码中遇到的问题(持续更新中)

写代码时经常遇到各种各样的bug&#xff0c;有时候很难发现&#xff0c;下面是我自己的一些见解 1.数组开在全局区和栈区所能开到的最大范围是不一样的 main函数里的数组&#xff0c;范围最大可以开到1e5 全局区的函数最大可以开到1e8的范围 2.#define int long long 写#dein…

【Linux】常用命令大揭秘,轻松驾驭终端世界

常见命令大全 概念1.1&#xff1a;开源、闭源的区别1.2&#xff1a;应用场景 发行版XShell3.1&#xff1a;使用XShell登入主机3.2&#xff1a;普通用户的增加、删除3.3&#xff1a;查看账户的信息whoami指令who指令 文件和目录基本命令4.1&#xff1a;指令的周边知识文件路径Li…

css响应式布局左、右上、右中布局

一、布局效果 二、布局代码 <div class"parent"><div class"left">菜单</div><div class"right"><div class"right-top">顶部导航</div><div class"right-content"></div>…

一个C++小程序调试过程记录

Top 20 C Projects With Source Code [2024 Update]https://www.interviewbit.com/blog/cpp-projects/ 这个网页有一些简单的C程序的源码&#xff0c;闲来无事&#xff0c;把第一个程序&#xff08;Bookshop Management System Using C&#xff09;的源码下载了下来。 源文件…

Mybatis进阶(映射关系多对一 )

文章目录 1.需求分析2.应用实例&#xff08;xml配置&#xff09;1.数据表设计2.entity设计&#xff08;不要使用toString会栈溢出&#xff09;1.Pet.java2.User.java 3.编写Mapper1.PetMapper.java2.UserMapper.java 4.编写Mapper.xml1.UserMapper.xml2.PetMapper.xml 5.测试Us…

基于缓存注解的时间戳令牌防重复提交设计

文章目录 一&#xff0c;概述二&#xff0c;实现过程1、引入pom依赖2、定义缓存管理3、时间戳服务类4、模拟测试接口 三&#xff0c;测试过程1&#xff0c; 模拟批量获取2&#xff0c; 消费令牌 四&#xff0c;源码放送五&#xff0c;优化方向 一&#xff0c;概述 API接口由于…

uniapp 异步加载级联选择器(Cascader,data-picke)

目录 Props 事件方法 inputChange事件回调参数说明&#xff1a; completeChange事件回调参数说明&#xff1a; temList 属性Object参数说明 defaultItemList 属性Object参数说明 在template中使用 由于uniapp uni-ui的data-picke 不支持异步作者自己写了一个 插件市场下…

vue3 ——笔记 (表单输入,监听器)

表单输入 在Vue 3中&#xff0c;v-model指令的用法稍有不同于Vue 2。在Vue 3中&#xff0c;v-model指令实际上是一个语法糖&#xff0c;它会自动将value属性和input事件绑定到组件或元素上&#xff0c;以实现双向数据绑定。 在自定义组件中使用v-model时&#xff0c;需要在组…

数组的扩容与缩容

数组的扩容与缩容 文章目录 数组的扩容与缩容数组的扩容内存分析 数组的缩容内存分析内存分析 数组的扩容 数组扩容是指当原有数组的空间不足以容纳更多的元素时&#xff0c;创建一个新的、长度更大的数组&#xff0c;并将原数组中的元素复制到新数组中&#xff0c;然后更新原…

Word文件后缀

Word文件后缀 .docx文件为Microsoft Word文档后缀名&#xff0c;基于XML文件格式 .dotm为Word启用了宏的模板 .dotx为Word模板 .doc为Word97-2003文档&#xff0c;二进制文件格式 参考链接 Word、Excel 和 PowerPoint 的文件格式参考 Learn Microsoft

类和对象【四】运算符重载

文章目录 运算符重载的概念运算符重载&#xff08;函数&#xff09;返回值类型&#xff1a;任意类型函数名&#xff1a;operator已有操作符 运算符重载&#xff08;函数&#xff09;的特点和注意点3个比较特殊的运算符重载赋值运算符&#xff08;&#xff09;重载返回值类型和返…

嵌入式开发四:STM32 基础知识入门

为方便更好的学习STM32单片机&#xff0c;本篇博客主要总结STM32的入门基础知识&#xff0c;重点在于理解寄存器以及存储器映射和寄存器映射&#xff0c;深刻体会STM32是如何组织和管理庞大的寄存器&#xff0c;从而提高开发效率的&#xff0c;为后面的基于标准库的开发做好铺垫…

MySQL 8.4 版本(LTS) 发布,一睹为快

前言 Oracle 前几天发布了 MySQL 8.4 版本(LTS)&#xff0c; 该版本是创新版的第一个长期支持版本。详细规划&#xff0c;请移步 技术译文 | 一文了解 MySQL 全新版本模型 关于 MySQL 的版本发布规划 Oracle MySQL 官方开发团队推出的新版本将过渡到新的 MySQL 版本模型。MyS…

cefsharp实现资源替换如网页背景、移除替换标签、html标识、执行javascript脚本学习笔记(含源码说明)

(一)实现测试(仅供学习参考) 1.1 目标系统页面(登录页)和登录后首页面中2处(一个替换一个移除) 1.2 实现后效果(使用cefsharp自定义浏览器实现以上功能) 1.3 登录后页面替换和移除 系统名称和一个功能菜单li (二)通过分析代码实现脚本编写 2.1 分开处理,设置了…

IDEA 2022.1版本开始,可以直接运行Markdown里的命令行

参照这种格式&#xff1a; shell mvn clean install注意idea支持的版本&#xff1a;是从 2022.1版本开始的。 ps&#xff1a;之前有人写过了&#xff0c;感觉很实用但是蛮多开发者不一定会知道的功能。 参考资料&#xff1a; https://www.cnblogs.com/didispace/p/16144107.h…

pygame鼠标绘制

pygame鼠标绘制 Pygame鼠标绘制效果代码 Pygame Pygame是一个开源的Python库&#xff0c;专为电子游戏开发而设计。它建立在SDL&#xff08;Simple DirectMedia Layer&#xff09;的基础上&#xff0c;允许开发者使用Python这种高级语言来实时开发电子游戏&#xff0c;而无需被…

ES数据存储与查询基本原理

Elasticsearch&#xff08;ES&#xff09;简介 Elasticsearch&#xff08;ES&#xff09;是一个分布式、可扩展、近实时的搜索和分析引擎&#xff0c;它基于Lucene&#xff0c;设计用于云计算中&#xff0c;处理大规模文档检索和数据分析任务&#xff0c;常用于实现内部搜索引…

CMake:静态库链接其他动态库或静态库(九)

1、项目结构 对于下面这样一个项目 把calc模块做成静态或者动态库把sort模块做成静态库然后再sort模块中的*.cpp调用calc模块生成的库即可&#xff08;这样就制作了一个静态库引用动态或者静态库&#xff09;test模块用于测试sort模块中的内容 . ├── calc │ ├── ad…

Codeforces Round 943 (Div. 3) 题解 缺F和G2

A题&#xff1a; Maximize? 题目大意&#xff1a; 给你一个整数 x&#xff0c; 要求你找出任意一个 y &#xff0c;。使得最大。 思路一&#xff1a;暴力 数据范围小&#xff0c;直接暴力。 #include <bits/stdc.h> #define int long long #define endl \n using n…