DSPy - prompt 优化

文章目录


一、关于 DSPy


DSPy 是一种用于在算法上优化 LM 提示和权重的框架 ,特别是当 LM 在管道中使用一次或多次时。

要在没有DSPy 的情况下使用 LM 构建复杂系统,您通常必须:(1) 将问题分解为步骤,(2) 很好地提示您的 LM,直到每个步骤单独运行良好,(3) 调整步骤以使其运行良好一起,(4) 生成综合示例来调整每个步骤,(5) 使用这些示例来微调较小的 LM 以降低成本。

目前,这既困难又混乱:每次更改管道、LM 或数据时,所有提示(或微调步骤)都可能需要更改。

为了使其更加系统化、更加强大,DSPy做了两件事。

首先,它将程序流程 ( modules) 与每个步骤的参数(LM 提示和权重)分开。

其次,DSPy 引入了新的LM 驱动算法,可以在您想要最大化optimizers的情况下调整 LM 调用的提示和/或权重。metric

DSPy 可以定期教导强大的模型(如GPT-3.5GPT-4)和本地模型(如T5-baseLlama2-13b)在任务中更加可靠,即具有更高的质量和/或避免特定的故障模式。DSPy优化器会将 相同的程序"编译"为不同的指令、少量提示和/或每个 LM 的权重更新(微调)。

这是一种新的范例,其中 LM 及其提示作为可以从数据中学习的大型系统的可优化部分逐渐淡出背景。**太棒了;**更少的提示、更高的分数以及更系统的方法来使用 LM 解决困难任务。


与神经网络的类比

当我们构建神经网络时,我们不会在手动调整的 浮点数列表上编写手动 for 循环。相反,您可以使用 PyTorch等框架来组成声明层(例如或),然后使用优化器(例如 SGD 或 Adam)来学习网络参数。Convolution``Dropout

同上!DSPy 为您提供了正确的通用模块(例如,ChainOfThoughtReAct),它们取代了基于字符串的提示技巧。为了取代提示黑客和一次性合成数据生成器,DSPy 还为您提供了通用优化器(BootstrapFewShotWithRandomSearchBayesianSignatureOptimizer),它们是更新程序中参数的算法。每当您修改代码、数据、断言或指标时,您都可以再次编译 程序,DSPy将创建适合您的更改的新有效提示。


常见问题解答


DSPy 优化器可以调整什么?

每个优化器都不同,但它们都试图通过更新提示或 LM 权重来最大化程序的指标。当前的 DSPyoptimizers可以检查您的数据,通过您的程序模拟跟踪以生成每个步骤的好/坏示例,根据过去的结果为每个步骤提出或改进指令,在自生成的示例上微调 LM 的权重,或者将其中几个结合起来以提高质量或降低成本。我们很乐意合并新的优化器来探索更丰富的空间:您目前为提示工程、"合成数据"生成或自我改进而经历的大多数手动步骤可能都可以推广到作用于任意 LM 程序的 DSPy 优化器中。


我应该如何使用 DSPy 完成我的任务?

使用 DSPy 是一个迭代过程。首先,您需要定义任务和要最大化的指标,并准备一些示例输入 --- 通常不带标签(或者仅为最终输出添加标签,如果您的指标需要它们)。然后,通过选择modules要使用的内置层(),为每个层提供signature(输入/输出规范),然后在 Python 代码中自由调用模块来构建管道。最后,您可以使用 DSPyoptimizer将代码编译为高质量指令、自动小样本示例或更新的 LM 权重。


如果我对提示或合成数据生成有更好的想法怎么办?

好问题。我们鼓励您考虑是否最好将其表达为模块或优化器,并且我们很乐意将其合并到 DSPy 中,以便每个人都可以使用它。 DSPy 不是一个完整的项目;这是一个持续的努力,旨在创建结构(模块和优化器)来代替黑客提示和管道工程技巧。


DSPy 代表什么?

这是一个很长的故事,但现在的反义词是 "声明式自我改进语言程序"(D eclarative S elf-improving Language P rograms,pythonically)。


二、安装

所有你需要的是:

shell 复制代码
pip install dspy-ai

要安装最新版本main

shell 复制代码
pip install git+https://github.com/stanfordnlp/dspy.git

或者在 Google Colab 中打开我们的介绍笔记本:https://colab.research.google.com/github/stanfordnlp/dspy/blob/main/intro.ipynb

openai默认情况下,DSPy从 pip安装最新版本。但是,如果您在 OpenAI 更改其 API 之前安装旧版本openai~=0.28.1,该库将很好地使用它。两者均受支持。

对于可选(按字母顺序排序)ChromadbQdrantMarqo、 Pinecone、SnowflakeWeaviateMilvus检索集成,请包括以下额外内容:

shell 复制代码
pip install dspy-ai[chromadb]  # or [qdrant] or [marqo] or [mongodb] or [pinecone] or [snowflake] or [weaviate] or [milvus]

三、文档

DSPy 文档分为教程 (在 DSPy 中解决任务的分步说明)、指南 (如何使用 API 的特定部分)和示例(说明用法的独立程序)。


A) 教程

等级 教程 在 Colab 中运行 描述
初学者 入门 code 介绍 DSPy 中的基本构建模块。使用 HotPotQA 解决复杂问答任务。
初学者 最小工作示例 N/A 在 DSPy 中构建并优化一个非常简单的思维链程序,用于数学问答。很短。
初学者 为棘手的任务进行编译 N/A 教 LM 推理逻辑陈述和否定。使用 GPT-4 引导 GPT-3.5 的少量 CoT 演示。在ScoNe上建立了最先进的结果。由克里斯·波茨贡献。
初学者 本地模型和自定义数据集 code 一起说明两个不同的事情:如何使用本地模型(特别是 Llama-2-13B)以及如何使用您自己的数据示例进行培训和开发。
中间的 DSPy 论文 N/A DSPy 论文的第 3、5、6 和 7 节可以作为教程使用。它们包括解释的代码片段、结果以及抽象和 API 的讨论。
中间的 DSPy 断言 code 介绍应用 DSPy 断言同时生成对带有引用的问题的长格式答复的示例。提供零样本和编译设置的比较评估。
中间的 复杂程序的微调 code 教本地 T5 模型 (770M) 在 HotPotQA 上表现出色。仅使用 200 个带标签的答案。不使用手写提示,不调用 OpenAI,也不使用用于检索或推理的标签。
先进的 信息提取 code 解决从长文章(生物医学研究论文)中提取信息的问题。结合上下文学习和检索,在 BioDEX 上设置 SOTA。由Karel D'Oosterlinck贡献。

人们认为有用的其他资源


B) 指南

如果您是 DSPy 新手,最好按顺序进行。此后您可能会经常参考这些指南,例如复制/粘贴可以为您自己的 DSPy 程序编辑的片段。

  1. 语言模型
  2. 签名
  3. 模块
  4. 数据
  5. 指标
  6. 优化器(以前的提词器)
  7. DSPy 断言

C) 例子

DSPy 团队认为复杂性必须合理。我们认真对待这一点:我们永远不会发布复杂的教程(上图)或示例(下图),*除非我们能够凭经验证明这种复杂性通常会提高质量或成本。*其他框架或文档很少强制执行这种规则,但您可以在 DSPy 示例中信赖它。

examples/该目录和顶级目录中有很多示例。我们欢迎贡献!

您可以在 Twitter/X 上找到@lateinteraction发布的其他示例。

其他一些示例(并非详尽无遗,请随时通过 PR 添加更多内容):


TODO:添加多伦多大学临床 NLP、Plastic Labs 心智理论 (ToM) 以及 Replit DSPy 管道的最新成果的链接。

Connor Shorten 编写的Weaviate DSPy 食谱中还有最近很酷的示例。请参阅 YouTube 上的教程


四、语法:您负责工作流程------它是自由格式的 Python 代码!

DSPy 隐藏了繁琐的提示工程,但它清楚地暴露了您需要做出的重要决策:**[1]您的系统设计会是什么样子?[2]**你的程序的行为有哪些重要的限制?

您可以将您的系统表达为自由格式的 Pythonic 模块。DSPy 将以您使用基础模型的任何方式 调整程序的质量:您可以使用循环、if语句或异常进行编码,并在您认为适合您的任务的任何 Python 控制流中使用DSPy模块。

假设您想要构建一个简单的检索增强生成 (RAG) 系统来回答问题。您可以RAG像这样定义自己的程序:

python 复制代码
class RAG(dspy.Module):
    def __init__(self, num_passages=3):
        super().__init__()
        self.retrieve = dspy.Retrieve(k=num_passages)
        self.generate_answer = dspy.ChainOfThought("context, question -> answer")
    
    def forward(self, question):
        context = self.retrieve(question).passages
        answer = self.generate_answer(context=context, question=question)
        return answer

程序有两个关键方法,您可以对其进行编辑以满足您的需要。

您的__init__方法 声明了您将使用的模块。在这里,RAG将使用内置函数Retrieve进行检索和ChainOfThought生成答案。DSPy 提供通用模块,这些模块采用您自己的子任务的形式,而不是针对特定应用程序的预构建函数。

使用 LM 的模块(例如ChainOfThought)需要签名 。这是一个声明性规范,告诉模块它应该做什么。在此示例中,我们使用简写签名表示法context, question -> answer来告诉ChainOfThought它将给出 somecontext和 aquestion并且必须生成一个answer。我们将在下面讨论更高级的**签名**。

您的forward方法 表达了您想要对模块进行的任何计算。在本例中,我们使用该模块self.retrieve来搜索一些context,然后使用该模块self.generate_answer,该模块使用contextquestion来生成answer

您现在可以在零次模式 RAG下使用该程序。或者编译它以获得更高的质量。零样本的使用很简单。只需定义程序的一个实例,然后调用它:

python 复制代码
rag = RAG()  # zero-shot, uncompiled version of RAG
rag("what is the capital of France?").answer  # -> "Paris"

下一节将讨论如何编译我们的简单RAG程序。当我们编译它时,DSPy编译器 将注释其步骤的演示 :(1)检索,(2)使用上下文,以及(3)使用思路 来回答问题。

从这些演示中,DSPy 编译器将确保它生成有效的小样本提示,与您的 LM、检索模型和数据配合良好。如果您正在使用小型模型,它会微调您的模型(而不是提示)来完成此任务。

如果您后来决定在管道中需要另一个步骤,只需添加另一个模块并再次编译即可。也许添加一个在搜索过程中考虑聊天历史记录的模块?


五、两个强大的概念:签名和提词器

**注意:**我们很快将把提词器重命名为优化器。这不会影响它们的功能,但会简化所使用的术语。

为了能够编译您编写的任何程序,DSPy引入了两个简单的概念:签名和提词器。


a) 使用以下语句声明 LM 的输入/输出行为 dspy.Signature

当我们将任务分配给DSPy 中的 LM 时,我们将所需的行为指定为Signature 。签名是DSPy 模块的输入/输出行为的声明性规范。

签名使您能够告知DSPy 子任务是什么 ,而不是投入精力如何 让您的 LM 执行子任务。稍后,DSPy 编译器将弄清楚如何为您的大型 LM(或微调您的小型 LM)专门针对您的签名、数据和管道内构建复杂的提示。


签名由三个简单元素组成:

  • 对 LM 应该解决的子任务的最小描述。
  • 我们将向 LM 提供的一个或多个输入字段(例如,输入问题)的描述。
  • 我们期望从 LM 获得一个或多个输出字段(例如,问题的答案)的描述。

我们支持两种表示签名的符号。简写签名符号 是为了快速开发。您只需为您的模块(例如,dspy.ChainOfThought)提供一个字符串,input_field_name_1, ... -> output_field_name_1, ...其中字段之间用逗号分隔。

RAG之前的课程中,我们看到:

python 复制代码
self.generate_answer = dspy.ChainOfThought("context, question -> answer")

在许多情况下,这个准系统签名就足够了。然而,有时您需要更多的控制。在这些情况下,我们可以使用完整的符号来表达下面更成熟的签名。

python 复制代码
class GenerateSearchQuery(dspy.Signature):
    """Write a simple search query that will help answer a complex question."""

    context = dspy.InputField(desc="may contain relevant facts")
    question = dspy.InputField()
    query = dspy.OutputField()

# inside your program's __init__ function
self.generate_answer = dspy.ChainOfThought(GenerateSearchQuery)

您可以选择为每个输入或输出字段提供一个prefix和/或desc键,以使用您的签名细化或限制模块的行为。子任务本身的描述被指定为文档字符串(即"""Write a simple...""")。


b) 要求 DSPy 自动优化您的程序dspy.teleprompt.*

定义好程序后RAG,我们就可以编译它了。编译程序将更新存储在每个模块中的参数。对于大型 LM,这主要是通过创建和验证良好的演示以包含在提示中的形式。

编译取决于三件事:(可能很小)训练集、验证指标以及您从DSPy 中选择的提词器。提词器 是功能强大的优化器(包含在DSPy中),可以学习引导并为任何程序的模块选择有效的提示。 (名字中的"tele-"是"远距离"的意思,即远距离自动提示。)

DSPy 通常需要非常少的标签。例如,我们的RAG管道可能只需要少数包含问题 及其(人工注释的)答案 的示例即可正常工作。您的管道可能涉及多个复杂的步骤:我们的基本RAG示例包括检索到的上下文、思路链和答案。但是,您只需要初始问题和最终答案的标签。DSPy将引导支持您的管道所需的任何中间标签。如果您以任何方式更改管道,引导的数据将相应更改!

python 复制代码
my_rag_trainset = [
  dspy.Example(
    question="Which award did Gary Zukav's first book receive?",
    answer="National Book Award"
  ),
  ...
]

其次,定义验证逻辑,它将表达对程序或单个模块的行为的一些约束。对于RAG,我们可以像这样表达一个简单的检查:

python 复制代码
def validate_context_and_answer(example, pred, trace=None):
    # check the gold label and the predicted answer are the same
    answer_match = example.answer.lower() == pred.answer.lower()

    # check the predicted answer comes from one of the retrieved contexts
    context_match = any((pred.answer.lower() in c) for c in pred.context)

    return answer_match and context_match

不同的提词器在成本与质量的优化程度等方面提供了各种权衡。对于RAG,我们可以使用名为 的简单提词器BootstrapFewShot。为此,我们使用验证函数实例化提词器本身my_rag_validation_logic,然后针对某些训练集进行编译my_rag_trainset

python 复制代码
from dspy.teleprompt import BootstrapFewShot

teleprompter = BootstrapFewShot(metric=my_rag_validation_logic)
compiled_rag = teleprompter.compile(RAG(), trainset=my_rag_trainset)

如果我们现在使用compiled_rag,它将通过丰富的提示来调用我们的 LM,并对我们的数据进行思想链检索增强问答的少量演示。


六、Pydantic 类型

有时您需要的不仅仅是字符串输入/输出。例如,假设您需要找到

python 复制代码
from pydantic import BaseModel, Field
from dspy.functional import TypedPredictor

class TravelInformation(BaseModel):
    origin: str = Field(pattern=r"^[A-Z]{3}$")
    destination: str = Field(pattern=r"^[A-Z]{3}$")
    date: datetime.date
    confidence: float = Field(gt=0, lt=1)

class TravelSignature(Signature):
    """ Extract all travel information in the given email """
    email: str = InputField()
    flight_information: list[TravelInformation] = OutputField()

predictor = TypedPredictor(TravelSignature)
predictor(email='...')

这将输出一个TravelInformation对象列表。

还有其他方法可以创建键入的签名。例如

python 复制代码
predictor = TypedChainOfThought("question:str -> answer:int")

它应用了思想链,并且保证返回一个 int 。

甚至还有一种受tanuki.py启发的方法,在定义模块时可以很方便:

python 复制代码
from dspy.functional import FunctionalModule, predictor, cot

class MyModule(FunctionalModule):
    @predictor
    def hard_question(possible_topics: list[str]) -> str:
        """Write a hard question based on one of the topics. It should be answerable by a number."""

    @cot
    def answer(question: str) -> float:
        pass

    def forward(possible_topics: list[str]):
        q = hard_question(possible_topics=possible_topics)
        a = answer(question=q)
        return (q, a)

有关更多示例,请参阅[上面的列表](https://github.com/stanfordnlp/dspy#:\~:text=Typed DSPy)以及该模块的单元测试


七、常见问题解答:DSPy 适合我吗?

DSPy 的理念和抽象与其他库和框架有很大不同,因此通常可以很容易地确定DSPy何时是(或不是)适合您的用例的框架。

如果您是 NLP/AI 研究人员(或探索新管道或新任务的从业者),答案通常是肯定。如果您是做其他事情的练习者,请继续阅读。


[a] DSPy 与提示的瘦包装器(OpenAI API、MiniChain、基本模板)

换句话说:为什么我不能直接将提示编写为字符串模板?好吧,对于极其简单的设置,这可能工作得很好。 (如果您熟悉神经网络,这就像将一个微小的两层神经网络表示为 Python for 循环一样。它还不错。)

然而,当您需要更高的质量(或可管理的成本)时,您需要迭代地探索多阶段分解、改进的提示、数据引导、仔细的微调、检索增强和/或使用更小的(或更便宜的或本地的)模型。使用基础模型进行构建的真正表现力在于这些部分之间的相互作用。但每次更改其中一个部件时,您都可能会损坏(或削弱)其他多个部件。

DSPy 干净地抽象出( 有力地优化)了实际系统设计外部的这些交互部分。它可以让您专注于设计模块级交互:用 10 或 20 行DSPy表达的 相同程序 可以轻松编译为 的多级指令、 的详细提示或 的微调。GPT-4``Llama2-13b``T5-base

哦,您不再需要在项目的核心维护又长又脆弱的特定于模型的字符串。


[b] DSPy 与 LangChain、LlamaIndex 等应用程序开发库的比较

注意:如果你使用 LangChain 作为你自己的提示字符串的薄包装,请参考答案 [a]。

LangChain 和 LlamaIndex 是针对使用 LM 进行高级应用程序开发的流行库。他们提供许多包含电池的 预构建应用模块,可插入您的数据或配置。事实上,实际上,许多用例确实不需要任何特殊组件。如果您很乐意使用某人的通用、现成的提示通过 PDF 或标准文本到 SQL 进行问答,只要它很容易在您的数据上进行设置,那么您可能会发现一个非常丰富的生态系统在这些图书馆中。

与这些库不同,DSPy 内部不包含针对您可以构建的特定应用程序的手工提示。相反,DSPy 引入了一组非常小的功能更强大且通用的模块,这些模块可以学习在数据管道中提示(或微调)您的 LM

DSPy 提供了完全不同的模块化程度:当您更改数据、调整程序的控制流或更改目标 LM 时,DSPy 编译器 可以将您的程序映射到一组专门针对此管道进行优化的新提示(或微调)。因此,如果您愿意实现(或扩展)自己的短程序,您可能会发现DSPy 可以以最少的努力为您的任务获得最高的质量。简而言之,DSPy适用于您需要轻量级但自动优化的编程模型的情况,而不是预定义提示和集成的库。

如果您熟悉神经网络:

这就像 PyTorch (即代表DSPy )和 HuggingFace Transformers (即代表更高级别的库)之间的区别。如果您只是想使用现成的BERT-base-uncasedGPT2-large对其进行最小程度的微调,HF Transformers 会让这一切变得非常简单。但是,如果您希望构建自己的架构(或显着扩展现有架构),则必须快速使用像 PyTorch 这样更加模块化的架构。幸运的是,HF Transformers 在 PyTorch 等后端实现的。我们同样对常见应用程序的DSPy 高级包装感到兴奋。如果这是使用DSPy 实现的,您的高级应用程序还可以以静态提示链无法做到的方式显着适应您的数据。如果您想帮助解决此问题,请打开一个问题。


[c] DSPy 与 Guidance、LMQL、RELM、Outlines 等生成控制库的比较

Guidance、LMQL、RELM 和 Outlines 都是令人兴奋的新库,用于控制 LM 的各个完成,例如,如果您想要强制执行 JSON 输出模式或将采样限制为特定的正则表达式。

这在很多情况下都非常有用,但它通常侧重于单个 LM 调用的低级、结构化控制。它无法确保你获得的 JSON(或结构化输出)是正确的或对你的任务有用的。

相比之下,DSPy 会自动优化程序中的提示,使其与各种任务需求保持一致,其中还可能包括生成有效的结构化输出。也就是说,我们正在考虑允许DSPy 中的签名来表达由这些库实现的类似正则表达式的约束。


八、测试

要运行测试,您需要首先克隆存储库。

然后通过诗歌安装包: 注意 - 您可能需要

shell 复制代码
poetry install --with test

然后使用以下命令运行所有测试或特定测试套件:

shell 复制代码
poetry run pytest
poetry run pytest tests/PATH_TO_TEST_SUITE

您还可以阅读有关框架从 Demonstrate-Search-Predict 到 DSPy 的演变的更多信息:

注意:如果您正在寻找 Demonstrate-Search-Predict (DSP)(DSPy 的早期版本),您可以在此存储库的v1分支上找到它。


2024-05-21(二)

相关推荐
semantist@语校1 天前
第十九篇|东京世界日本语学校的结构数据建模:制度函数、能力矩阵与升学图谱
数据库·人工智能·线性代数·矩阵·prompt·github·数据集
山海青风2 天前
12 Prompt 模板化与参数化
人工智能·prompt
山海青风2 天前
11 Prompt 工程进阶:Few-shot 与 Chain-of-Thought
人工智能·prompt
人工智能培训2 天前
AI提示词(Prompt)基础核心知识点
大模型·prompt·提示词·input
虫无涯3 天前
LangChain中的Prompt模板如何使用?
服务器·langchain·prompt
今天也要学习吖4 天前
谷歌nano banana官方Prompt模板发布,解锁六大图像生成风格
人工智能·学习·ai·prompt·nano banana·谷歌ai
zzywxc7874 天前
AI在金融、医疗、教育、制造业等领域的落地案例
人工智能·机器学习·金融·prompt·流程图
落樱弥城5 天前
Prompt Engineering to Context Engineering
prompt
非晓为骁5 天前
【AI】AI 评测入门(二):Prompt 迭代实战从“能跑通”到“能落地”
人工智能·prompt·提示词工程·ai评测
leo03086 天前
7种流行Prompt设计模式详解:适用场景与最佳实践
设计模式·prompt