Created
Aug 14, 2024 01:48 PM
Favorite
Favorite
Priority
备注
推荐
🌟🌟🌟🌟🌟
类型
DSPy
notion image
LLMs(大型语言模型)基于数据科学,但我们对提示工程的方法可能会显得不够科学:
  1. 手动的、无法良好泛化的提示工程:LLMs对于每个任务的提示方式高度敏感,因此我们需要手工制作长串的指令和示范。这不仅需要耗费时间的提示编写过程,而且给定的字符串提示可能无法泛化到不同的流程或跨不同的LMs(语言模型)、数据领域甚至输入。处理新问题时,我们通常需要手工制作新的提示。
  1. 缺乏进行测试的框架:与典型的数据科学应用中常用的训练-测试机制不同,后者可以选择最大化某一指标(如AUC)的模型,对于LLMs,我们通过试错来找到最佳的提示,往往没有客观的指标来衡量我们的模型表现如何。因此,无论我们如何尝试改进提示,我们都无法自信地说我们的应用有多可靠。
为了解决这些问题,斯坦福NLP发表了一篇论文,介绍了一种新的提示写作方法:我们不是操纵自由形式的字符串,而是通过模块化编程生成提示。相关的库,称为DSPy,可以在此处找到。
本文旨在展示这种“提示编程”是如何进行的,更深入地解释优化过程背后发生了什么。代码也可以在这里找到。
(说到这个,你可能也会觉得让LLMs输出正确格式的JSON很不科学,我也写了一篇文章关于如何使用函数调用来解决这个问题。看看吧!)。
我们将花一些时间来介绍环境准备。之后,本文分为3个部分:
DSPy的基本概念:签名和模块 用于描述你的任务的DSPy基本构建块,以及所使用的提示技术 优化器:像机器学习一样训练我们的提示 DSPy如何通过自举法优化你的提示 完整的例子:与LLM进行提示比较 应用传统机器学习的严格性进行提示测试和选择 我们现在准备好开始了!
准备工作前往Github克隆我的代码。我文章的内容可以在dspy_tutorial笔记本中找到。请创建并激活一个虚拟环境,然后运行pip install -r requirements.txt来安装所需的包。如果你在Windows上,请同时安装Windows C++构建工具,这是使用我们将用来观察DSPy工作原理的phoneix库所必需的。我的代码使用了OpenRouter,它允许我们在被封锁的地区访问OpenAI API。请将你的OPENROUTER_API_KEY设置为环境变量,并执行“准备”部分下的代码。或者,如果你可以正常使用,你可以直接使用dspy.OpenAI类并定义OpenAI API密钥。DSPy的基本概念:签名和模块 它们是DSPy中提示编程的基础构件。让我们深入了解它们是什么!
签名:输入/输出的规范 签名是DSPy提示编程中最基本的构建块,它是一种声明式的输入/输出行为规范。签名让你告诉LM(语言模型)需要做什么,而不是指定我们应该如何要求LM去做。
比如说,我们想要获取一句话的情感倾向,传统上我们可能会写这样的提示:
但在DSPy中,我们可以通过定义一个签名来实现同样的效果。在最基本的形式上,一个签名就是一个简单的字符串,用一个箭头'->'来分隔输入和输出。
注意:本节中的代码包含了引用自DSPy的签名文档的内容。
预测结果并不理想,但为了教学目的,让我们检查所发出的提示是什么。
我们可以看到上面的提示是根据句子->情感签名组装的。但是DSPy是如何在提示中提出'Given the fields...'的呢?
检查dspy.Predict()类,我们看到当我们将签名传递给它时,该签名会被解析为该类的signature属性,并随后被组装成提示。这个指令是DSPy库中硬编码的默认指令。
如果我们想要向LLM提供关于我们目标的更详细描述,而不仅仅是基本的句子->情感签名,该怎么办呢?为此,我们需要以基于类的DSPy签名的形式提供一个更冗长的签名。
注意,我们并没有提供明确的指令告诉LLM应如何获得情感。我们只是在描述手头的任务,以及预期的输出。
现在它输出了一个更好的预测!我们再次看到,在定义基于类的DSPy签名时所做的描述被组装进了提示中。
对于简单的任务来说,这可能足够了,但高级应用可能需要复杂的提示技术,如思维链或ReAct。在DSPy中,这些被实现为模块。
模块:抽象化的提示技术我们可能习惯于通过在提示中硬编码诸如'让我们一步一步地思考'之类的短语来应用“提示技术”。在DSPy中,这些提示技术被抽象化为模块。让我们看下面一个例子,将我们的基于类的签名应用到dspy.ChainOfThought模块上。
注意,'推理:让我们一步一步地思考...'这个短语是如何被添加到我们的提示中的,现在我们的预测质量更好了。
根据DSPy的文档,截至撰写本文时,DSPy以模块形式提供了以下提示技术。请注意,我们在最初的例子中使用的dspy.Predict也是一个模块,代表没有提示技术!
dspy.Predict:基本预测器。不修改签名。处理学习的关键形式(即存储指令和演示以及更新到LM)。dspy.ChainOfThought:教导LM在承诺签名响应之前逐步思考。dspy.ProgramOfThought:教导LM输出代码,其执行结果将决定响应。dspy.ReAct:可以使用工具实现给定签名的代理。dspy.MultiChainComparison:可以比较ChainOfThought的多个输出,以产生最终预测。它还有一些函数式模块:
  1. dspy.majority:可以进行基本投票,从一组预测中返回最受欢迎的响应。
你可以在每个模块的相应指南中查看进一步的例子。
链接模块 另一方面,RAG怎么样?我们可以将模块链在一起来处理更大的问题!
首先我们定义一个检索器,对于我们的例子我们使用一个从Wikipedia Abstracts 2017获取信息的ColBERT检索器。
然后我们定义一个继承自dspy.Module的RAG类。它需要两个方法:
__init__方法将简单地声明它所需的子模块:dspy.Retrieve和dspy.ChainOfThought。后者被定义为实现我们的上下文,问题->答案签名。forward方法将描述使用我们拥有的模块回答问题的控制流程。注意:本节中的代码借鉴自DSPy的介绍笔记本。
然后我们利用这个类来执行一个RAG。
检查提示,我们看到从Wikipedia Abstracts 2017检索出的3篇文章被作为思维链生成的上下文穿插进来。
上面的例子可能看起来没什么大不了。在其最基本的应用中,DSPy似乎只是在做不能用f-string完成的事情,但它实际上为提示写作带来了范式转变,因为这为提示组合带来了模块化!
首先我们用签名描述我们的目标,然后用模块应用不同的提示技术。要测试给定问题的不同提示技术,我们可以简单地切换使用的模块并比较它们的结果,而不是硬编码“让我们一步一步地思考...”(对于思维链)或“你将交错思考、行动和观察步骤”(对于ReAct)的短语。模块化的好处将在本文后面的一个完整示例中得到展示。
DSPy的力量不仅仅局限于模块化,它还可以基于训练样本优化我们的提示,并系统地测试它。我们将在下一节中探索这个!
优化器:像机器学习一样训练我们的提示 在这一节中,我们尝试用DSPy优化一个RAG应用的提示。
以思维链为例,除了添加“让我们一步一步地思考”的短语之外,我们还可以通过一些调整来提升其性能:
添加合适的例子(也就是少样本学习)。此外,我们可以引导推理的演示,教导LMs应用适当的推理来处理手头的任务。手动执行这个任务将非常耗时,并且不能泛化到不同的问题,但是有了DSPy,这可以自动完成。让我们深入了解一下!
准备#1:加载测试数据:就像机器学习一样,要训练我们的提示,我们需要准备我们的训练和测试数据集。最初这个单元格将运行大约20分钟。
检查我们的数据集,基本上是一系列问答对。
#2 设置Phoenix以便于观察:为了便于理解优化过程,我们启动Phoenix来观察我们的DSPy应用,这是一个用于LLM可观察性的绝佳工具!我将跳过粘贴代码,但你可以在笔记本中执行它。
注意:如果你在Windows上,请在这里安装Windows C++ Build Tools,这对Phoenix是必需的
提示优化 然后我们就准备好看看这个优化是怎么回事了!要“训练”我们的提示,我们需要3样东西:
一个训练集。我们将只使用来自trainset的20个问答示例。一个用于验证的指标。这里我们使用原生的dspy.evaluate.answer_exact_match,它检查预测答案是否与正确答案完全匹配(有问题但足以示范)。对于实际应用,你可以定义自己的评估标准。一个特定的优化器(以前称为teleprompter)。DSPy库包括多种优化策略,你可以在这里查看它们。对于我们的例子,我们使用BootstrapFewShot。与其在这里用冗长的描述来描述它,不如随后用代码来演示它。现在我们来训练我们的提示。
在使用编译后的RAG回答问题之前,让我们看看在训练过程(也就是编译)中幕后发生了什么。我们通过访问http://localhost:6006/ 在浏览器中启动Phoenix控制台。
notion image
在我的运行中,我使用了RAG类进行了14次调用,在每次调用中我们向LM提交一个问题以获得预测。
参考我的笔记本中的结果汇总表,这14个样本中有4个正确答案,因此达到了我们的最大引导样本参数并停止了调用。
但是DSPy发布了哪些提示来获得引导样本呢?这是第14个问题的提示。我们可以看到,当DSPy尝试生成一个引导样本时,它会从我们的训练集中随机添加样本进行少样本学习。
是时候测试编译后的RAG了!在这里,我们提出一个问题,它在我们的结果汇总表中被错误回答,看看这次能否得到正确答案。
我们现在得到了正确的答案!
再次检查发出的提示。注意编译后的提示与引导期间使用的提示不同。除了少样本示例外,正确预测的引导上下文-问题-推理-答案示范被添加到提示中,提高了LM的能力。
所以下面基本上是在编译过程中与BootstrapFewShot背后的幕后:
notion image
以上示例仍然没有达到我们在机器学习中通常所做的:即使引导可能有用,我们还没有证明它能够提高响应的质量。
理想情况下,就像在传统机器学习中一样,我们应该定义几个候选模型,看看它们在测试集上的表现如何,并选择实现最高性能得分的那个。这就是我们接下来要做的!
成熟的示例:与LLM的提示比较这个示例的目标 在这一部分,我们想要评估什么是“最佳提示”(以模块和优化器组合的形式表达),以针对HotpotQA数据集(根据CC BY-SA 4.0许可证分发)执行RAG,鉴于我们使用的LM(GPT 3.5 Turbo)。
正在评估的模块有:
Vanilla:基于检索到的上下文回答一个问题的单跳RAG,没有像“让我们一步一步地思考”这样的关键短语 COT:带有思维链的单跳RAG ReAct:带有ReAct提示的单跳RAG BasicMultiHop:带有思维链的2跳RAG 而优化器候选人是:
None:除了签名之外没有额外的指令 Labeled few-shot:简单地从提供的标记的Q/A对构建少样本示例 Bootstrap few-shot:正如我们所演示的,为我们的模块的每一个阶段自我生成完整的示范。将简单地使用生成的示范(如果它们通过度量)而没有任何进一步的优化。对于Vanilla来说,它只是等于“Labeled few-shot” 至于评估指标,我们再次使用精确匹配作为标准(dspy.evaluate.metrics.answer_exact_match)来对抗测试集。
比较让我们开始吧!首先,我们定义我们的模块。
然后为我们的模型候选定义排列。
然后,我定义了一个辅助类来促进评估。代码有点长,所以我没有在这里粘贴,但可以在我的笔记本中找到。它所做的是针对每个优化器应用模块,编译提示,然后在测试集上进行评估。
我们现在准备好开始评估了,大约需要20分钟才能完成。
这是评估结果。我们可以看到,COT模块配合BootstrapFewShot优化器表现最佳。这些分数代表了测试集中正确答案(通过精确匹配判断)的百分比。
但在我们结束练习之前,更深入地检查结果可能会有所帮助:Multihop配合BootstrapFewShot,理论上比COT配合BootstrapFewShot拥有更多相关上下文,但性能却更差。这很奇怪!
调试和微调我们的提示 现在转到Phoenix控制台看看发生了什么。我们随机选择一个问题“William Hughes Miller出生在一个有多少居民的城市?”,并检查COT、ReAct、BasicMultiHop配合BootstrapFewShot优化器是如何得出它们的答案的。你可以在搜索栏中输入这个来过滤:"""William Hughes Miller was born in a city with how many inhabitants ?""" in input.value。
notion image
这是我运行期间3个模型提供的答案:
Multihop配合BootstrapFewShot:答案会根据William Hughes Miller出生的具体城市而变化。ReAct配合BootstrapFewShot:Kosciusko, Mississippi COT配合BootstrapFewShot:Kosciusko, Mississippi这座城市大约有7,402名居民。正确答案是2010年人口普查时的7,402。ReAct配合BootstrapFewShot和COT配合BootstrapFewShot都提供了相关的答案,但Multihop配合BootstrapFewShot却未能提供一个。
在Phoenix中检查Multihop配合BootstrapFewShot的执行追踪,看起来LM未能理解签名中指定的search_query的预期是什么。
notion image
所以我们修订了签名,并用下面的代码重新运行评估。
notion image
我们现在看到所有模型的分数都有所提高,Multihop配合LabeledFewShot和没有例子的Multihop现在表现最佳!这表明尽管DSPy尝试优化提示,但在签名中明确你的目标仍然涉及一些提示工程。
注意:甚至签名本身也可以用DSPy的COPRO进行优化!但本文不会深入探讨COPRO,因为全部理解可能太多。
现在最好的模型为我们的问题产生了一个精确匹配的答案!
由于最佳提示是Multihop配合LabeledFewShot,提示中不包含自举的Context-Question-Reasoning-Answer示范。所以自举不一定能带来更好的性能,我们需要科学地证明哪个提示是最佳的。
这并不意味着Multihop配合BootstrapFewShot总体上表现更差。只是对于我们的任务来说,如果我们使用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-string。此外,对于LM比较,我们经常提出不明确的问题,比如“不同的LM在特定问题上如何比较”,这是借鉴了斯坦福NLP论文的说法。
但正如上面的例子所展示的,有了DSPy的模块化、可组合的程序和优化器,我们现在有能力回答“在特定问题中使用模块X编译时,它们与优化器Y相比如何”,这是一个定义明确且可重复的运行,从而减少了在现代AI中巧妙构建提示的作用。
Loading...