Created
Aug 14, 2024 12:12 PM
Favorite
Favorite
Priority
备注
推荐
类型
本文旨在展示这种“提示编程”是如何完成的,以更深入地解释优化过程背后发生的事情。
DSPy的基本概念:签名和模块
它们是 DSPy 中即时编程的构建块。让我们深入了解它们是关于什么的!
签名: 输入/输出说明
签名是 DSPy 提示编程中最基本的构件,是对 DSPy 模块输入/输出行为的声明性规范。签名允许你告诉 LM 它需要做什么,而不是指定我们应该如何要求 LM 去做。
比方说,我们想获得一个句子的情感,传统上我们可能会写这样的提示语:
但在 DSPy 中,我们可以通过定义如下签名来实现同样的目的。签名的最基本形式就是一个用 -> 分隔输入和输出的字符串。
这个预测并不好,但为了便于教学,让我们来看看发出的提示是什么。
我们可以看到,上述提示是由sentence -> sentiment特征组合而成的。但 DSPy 是如何在提示中使用 "Given the fields… "的呢?
通过查看 dspy.Predict() 类,我们可以看到,当我们向其传递签名时,签名将被解析为signature类的属性,并随后组装为提示语。该instructions是 DSPy 库中硬编码的默认指令。
如果除了基本的sentence -> sentiment签名之外,我们还想向 LLM 提供更详细的目标描述,该怎么办?为此,我们需要以基于类的 DSPy 签名的形式提供更详细的签名。
请注意,我们没有明确指示 LLM 应如何获取情感。我们只是描述了手头的任务以及预期输出。
现在,它输出的预测结果要好得多!我们再次看到,我们在定义基于类的 DSPy 签名时所作的描述被组合成了一个提示。
对于简单的任务来说,这样做也许可以,但高级应用可能需要复杂的提示技术,如 Chain of Thought 或 ReAct。在 DSPy 中,这些技术以模块的形式实现
模块: 抽象提示技术
我们可能习惯于通过在提示语中硬编码 "let’s think step by step "等短语来应用 "提示技术"。在 DSPy 中,这些提示技术被抽象为模块。下面我们来看一个将基于类的签名应用到 dspy.ChainOfThought 模块的例子
根据 DSPy 的文档,截至本文撰写之时,DSPy 以模块的形式提供了以下提示技术。请注意,我们在初始示例中使用的 dspy.Predict 也是一个模块,不代表任何提示技术!
  1. dspy.Predict:基本预测器。不修改签名。处理学习的主要形式(即存储指令、演示和更新 LM)。
  1. dspy.ChainOfThought(思维链): 教导 LM 在作出签名响应之前逐步思考。
  1. dspy.ProgramOfThought: 教导 LM 输出代码,其执行结果将决定响应。
  1. dspy.ReAct: 可使用工具执行给定签名的代理。
  1. dspy.MultiChainComparison:多链比较: 可以比较 ChainOfThought 的多个输出结果,从而得出最终预测。
它还有一些函数式模块:
6. dspy.majority: 可以进行基本投票,从一组预测中返回最受欢迎的回应。
连锁模块
另一方面,RAG 怎么办?我们可以将模块串联起来,处理更大的问题!
首先,我们定义一个检索器,在我们的示例中,我们使用 ColBERT 检索器从Wikipedia Abstracts 2017中获取信息
然后,我们定义继承自 dspy.Module 的 RAG 类。它需要两个方法:
  • __init__ 方法将简单地声明它需要的子模块:dspy.Retrieve 和 dspy.ChainOfThought。定义后者是为了实现我们的context, question -> answer签名。
  • forward 方法将描述使用现有模块回答问题的控制流。
然后,我们利用该类进行 RAG
通过检查提示,我们可以看到,从Wikipedia Abstracts 2017中检索到的 3 个段落被插入作为 "思维链 "生成的上下文
上面的例子可能看起来并不多。在最基本的应用中,DSPy 似乎只是做了一些 f-string 无法做到的事情,但它实际上为提示语的编写带来了范式上的转变,因为它为提示语的组成带来了模块化!
DSPy 的强大之处不仅限于模块化,它还可以根据训练样本优化我们的提示,并对其进行系统测试。
优化器: 像机器学习一样训练我们的提示
我们将尝试使用 DSPy 对 RAG 应用程序的提示进行优化。
以 Chain of Thought 为例,除了添加 "让我们一步步思考 "短语外,我们还可以通过一些调整来提高其性能:
  1. 添加合适的示例(又称少量学习)。
  1. 此外,我们还可以引导推理演示,教导 LM 运用适当的推理方法来处理手头的任务。
手动完成这项工作非常耗时,而且无法推广到不同的问题,但有了 DSPy,这项工作就能自动完成。让我们深入了解一下!
准备工作
1:加载测试数据: 与机器学习一样,为了训练我们的提示,我们需要准备训练数据集和测试数据集。起初,这一单元需要运行 20 分钟左右。
检查我们的数据集,它基本上是一组问答对
2 为可观测性设置 Phoenix: 为了便于理解优化过程,我们启动 Phoenix 来观察我们的 DSPy 应用程序,它是一般 LLM 可观察性的好工具!
提示优化
然后,我们就可以看看这次优化的目的了!要 "训练 "我们的提示符,我们需要三样东西:
  1. 训练集。我们将使用训练集中的 20 个问答示例。
  1. 一个验证指标。在这里,我们使用本地的 dspy.evaluate.answer_exact_match,它可以检查预测答案是否与正确答案完全匹配(虽然有疑问,但用于演示已经足够)。对于实际应用,你可以定义自己的评估标准
  1. 特定优化器(原提词器)。DSPy 库包含许多优化策略,你可以在这里查看。在我们的示例中,我们使用了 BootstrapFewShot。与其在这里长篇大论,不如随后用代码来演示。
现在我们来训练我们的提示。
在使用 compiled_rag 回答问题之前,我们先来看看训练过程(又称编译)的幕后过程。我们通过浏览器访问 http://localhost:6006/ 来启动 Phoenix 控制台
notion image
在我的运行中,我使用 RAG 类进行了 14 次调用,在每次调用中,我们都会向 LM 提出一个问题,以获得预测结果。
请参阅我笔记本中的结果汇总表,从这 14 个样本中得出了 4 个正确答案,从而达到了我们的 max_bootstrapped_demos 参数并停止了调用。
但 DSPy 发出了哪些提示来获取引导演示呢?下面是问题 #14 的提示。我们可以看到,当 DSPy 尝试生成一个引导演示时,它会从我们的训练集中随机添加样本,以进行短时学习。
是时候对compiled_rag 进行测试了!在这里,我们提出一个在汇总表中回答错误的问题,看看这次能否得到正确答案。
现在我们得到了正确答案!
让我们再次检查发出的提示。请注意编译后的提示与引导过程中使用的提示有什么不同。除了少数几个例子外,提示中还添加了从正确预测中引导出的上下文-问题-推理-答案演示,从而提高了 LM 的能力。
因此,下面的内容基本上就是 BootstrapFewShot 在编译过程中的幕后情况:
notion image
上述例子与我们通常使用机器学习所做的仍有差距: 即使 boostrapping 也许有用,但我们还没有证明它能提高响应的质量。
理想情况下,就像传统的机器学习一样,我们应该定义几个候选模型,看看它们在测试集上的表现如何,然后选择一个性能得分最高的模型。这就是我们接下来要做的!
正式示例: 使用 LLM 进行提示比较
本例的目的
我们将评估在使用 LM(GPT 3.5 Turbo)的情况下,针对 HotpotQA 数据集(以 CC BY-SA 4.0 许可发布)执行 RAG 的 "最佳提示"(以模块和优化器组合表示)。
评估的模块有:
  • Vanilla:单跳 RAG,根据检索到的上下文回答问题,不含 "让我们一步步思考 "等关键短语
  • COT:带有思维链的单跳 RAG
  • ReAct: 带有 ReAct 提示的单跳 RAG
  • BasicMultiHop:带有思维链的双跳 RAG
候选优化器是:
  • 无: 除签名外无其他说明
  • 带标记的少量实例: 只需从提供的标记 Q/A 对中构建少量示例
  • 引导少量示例: 正如我们演示的那样,为模块的每个阶段自生成完整的演示。只需使用生成的演示(如果它们通过了度量标准),而无需进一步优化。对于 Vanilla 来说,它就等同于 "标记的少量演示"(Labeled few-shot)。
至于评估指标,我们再次使用精确匹配作为测试集的标准(dspy.evaluate.metrics.answer_exact_match)。
比较
让我们开始吧!首先,我们定义模块
然后为我们的候选模型定义排列组合
现在我们准备开始评估,大约需要 20 分钟完成
下面是评估结果。我们可以看到,使用 BootstrapFewShot 优化器的 COT 模块性能最佳。分数代表测试集的正确答案百分比(根据精确匹配判断)。
notion image
不过,在结束演练之前,我们不妨对结果进行更深入的研究: 使用 BootstrapFewShot 的 Multihop 本应比使用 BootstrapFewShot 的 COT 拥有更多相关上下文,但其性能却更差。这很奇怪!
调试并微调我们的提示
现在前往 Phoenix Console 看看发生了什么。我们随机选择一个问题William Hughes Miller was born in a city with how many inhabitants ?,并检查 COT、ReAct、BasicMultiHop 和 BoostrapFewShot 优化器是如何得出答案的。你可以在搜索栏中输入以下内容进行过滤:"""William Hughes Miller was born in a city with how many inhabitants ?""" in input.value
notion image
以下是我运行过程中 3 个模型提供的答案:
  • 使用 BootstrapFewShot 进行多跳:The answer will vary based on the specific city of William Hughes Miller’s birthplace.
  • 使用 BootstrapFewShot 进行反应:Kosciusko, Mississippi
  • COT 与 BootstrapFewShot:The city of Kosciusko, Mississippi, has a population of approximately 7,402 inhabitants.
根据 2010 年人口普查,正确答案为 7,402 人。使用 BootstrapFewShot 的 ReAct 和使用 BootstrapFewShot 的 COT 都提供了相关答案,但使用 BootstrapFewShot 的 Multihop 却没有提供答案。
查看 Phoenix 中使用 BootstrapFewShot 的 Multihop 的执行跟踪,LM 似乎无法理解签名中指定的 search_query 的预期结果。
notion image
因此,我们修改了签名,并用下面的代码重新进行了评估
notion image
现在,我们看到所有模型的得分都有所提高,带有 LabeledFewShot 的 Multihop 和不带示例的 Multihop 现在表现最好!这表明,尽管 DSPy 尝试优化提示,但通过在签名中阐明目标,仍然涉及到一些提示工程。
现在,最好的模型可以产生与我们的问题完全匹配的结果!
由于最佳提示符是带有标签的多跳,该提示符不包含引导式上下文-问题-推理-答案演示。因此,引导不一定会带来更好的性能,我们需要科学地证明哪一个才是最佳提示。
不过,这并不意味着使用 BootstrapFewShot 的 Multihop 总体性能更差。只是对于我们的任务来说,如果我们使用 GPT 3.5 Turbo 引导演示(质量可能有问题)和输出预测,那么我们最好不进行引导,只保留少数几个实例。
这就引出了一个问题: 是否有可能使用更强大的 LM,比如 GPT 4 Turbo(又称教师)来生成演示,同时保留 GPT 3.5 Turbo(又称学生)等更便宜的模型来进行预测?
"教师 "增强引导能力
答案是肯定的,正如下面的单元格所示,我们将使用 GPT 4 Turbo 作为教师。
notion image
不过,使用 GPT-4 Turbo 作为教师并不能显著提高模型的性能。不过,我们还是值得看看它对我们的提示的影响。以下是使用 GPT 3.5 生成的提示信息
这是使用 GPT-4 Turbo 作为教师生成的提示。请注意,这里的 "推理 "表达得更好!
结论
目前,我们经常依赖于人工提示工程,最多只能将其抽象为 f-字符串。此外,在进行 LM 比较时,我们经常会提出一些不够明确的问题,比如 "不同的 LM 在某个问题上的比较结果如何"(借用斯坦福 NLP 论文中的说法)。
但正如上述例子所示,有了 DSPy 模块化、可组合的程序和优化器,我们现在就有能力回答 "使用优化器 Y 编译模块 X 时,它们在某个问题上的对比结果如何",这是一个定义明确、可重复运行的问题,从而降低了人工智能中巧妙构建提示符的作用。
文章来源:https://medium.com/towards-data-science/prompt-like-a-data-scientist-auto-prompt-optimization-and-testing-with-dspy-ff699f030cb7
标签:
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
Loading...