当前位置:   article > 正文

DSPy 入门: 再见提示,你好编程

dspy

DSPy 入门: 再见提示,你好编程

情报工作站 情报工作站 2024-03-04 10:01 浙江

DSPy 入门: 再见提示,你好编程

翻译:Intro to DSPy: Goodbye Prompting, Hello Programming! | by Leonie Monigatti | Feb, 2024 | Towards Data Science[1]

目前,使用大型语言模型(LLM)构建应用程序不仅复杂,而且脆弱。由于 LLM 对提示方式非常敏感,因此典型的流水线通常使用提示语来实现,而提示语则是通过反复试验手工制作的。因此,当您更改管道中的某个部分(如 LLM 或数据)时,很可能会削弱其性能--除非您调整提示(或微调步骤)。

当你改变管道中的某个部分(如 LLM 或数据)时,很可能会削弱其性能...

DSPy [1] 是一个旨在解决基于语言模型(LM)的应用程序脆弱性问题的框架,它优先考虑编程而不是提示。它允许您重新编译整个流水线,以便根据您的特定任务进行优化,而不是每当您更改一个组件时,就重复一轮人工提示工程。

虽然关于该框架的论文 [1] 早在 2023 年 10 月就已发表,但我最近才了解到它。只看了一个视频("DSPy Explained!Connor Shorten)[2],我就明白了开发者社区为何对 DSPy 如此兴奋!

本文将通过以下主题简要介绍 DSPy 框架:

  • • 什么是 DSPy(包括关于 DSPy vs. LangChain vs. LlamaIndex 和 DSPy vs. PyTorch 的讨论)

  • • DSPy 编程模型: 签名、模块和提词器

  • • DSPy 编译器[3]

  • • DSPy 示例: RAG 管道

什么是 DSPy

DSPy("Declarative Self-improving Language Programs (in Python)",发音为 "dee-es-pie")[1] 是斯坦福大学 NLP 研究人员开发的 "基础模型编程 "框架。它强调编程而非提示,并将构建基于 LM 的管道从操作提示转向编程。因此,它旨在解决构建基于 LM 应用程序时的脆弱性问题。

DSPy 还将程序的信息流与每一步的参数(提示和 LM 权重)分离开来,为构建基于 LM 的应用程序提供了更系统的方法。然后,DSPy 将根据您的程序,自动优化如何针对您的特定任务提示(或微调)LM。

为此,DSPy 引入了以下概念:

  • • 手写提示和微调被抽象化,取而代之的是签名[4]

  • • 提示技术,如 "思维链"(Chain of Thought)或 "再行动"(ReAct),被抽象为模块并取而代之

  • • 人工提示工程通过优化器(提示器)和 DSPy 编译器实现自动化

使用 DSPy 构建基于 LM 的应用程序的工作流程如下所示,DSPy 入门笔记本[5]对此进行了讨论。它会让你想起训练神经网络的工作流程:

1. Collect data, 2. Write DSpy program using Signatures and modules, 3. Define validation logic using teleprompter, 4. Compile DSpy program with DSPy compiler, 5. Iterate

使用 DSPy 构建基于 LLM 的应用程序的工作流程

  1. 1. 收集数据集: 收集一些程序输入和输出的示例(如问题和答案对),用于优化管道。

  2. 2. 编写 DSPy 程序: 使用签名和模块定义程序逻辑,以及各组件之间的信息流,以解决任务。

  3. 3. 定义验证逻辑: 使用验证指标和优化器(提词器)定义优化程序的逻辑。

  4. 4. 编译 DSPy 程序: DSPy 编译器会将训练数据、程序、优化器和验证指标考虑在内,以优化程序(如提示或微调)。

  5. 5. 迭代: 重复这一过程,改进数据、程序或验证,直到你对管道的性能感到满意为止。

以下是与 DSPy 相关的所有重要链接的简短列表:

  • • DSPy 论文: DSPy: 将声明式语言模型调用编译成自我改进管道[6] [1]

  • • DSPy GitHub: https://github.com/stanfordnlp/dspy

  • • 通过关注 Omar Khattab[7] 随时了解 DSPy 的最新动态

DSPy 与 LangChain 或 LlamaIndex 有何不同?

LangChain、LlamaIndex 和 DSPy 都是帮助开发人员轻松构建基于 LM 的应用程序的框架。使用 LangChain 和 LlamaIndex 的典型流水线通常使用提示模板来实现,这使得整个流水线对组件变化非常敏感。相比之下,DSPy 将构建基于 LM 的流水线从操作提示转向编程。

DSPy 中新引入的编译器在改变基于 LM 应用程序中的部件(如 LM 或数据)时,消除了任何额外的提示工程或微调工作。相反,开发人员只需重新编译程序,即可根据新添加的变化优化流水线。因此,与 LangChain 或 LlamaIndex 相比,DSPy 可以帮助开发人员以更少的工作量获得管道的性能。

尽管 LangChain 和 LlamaIndex 已经在开发者社区中被广泛采用,但 DSPy 作为一种新的替代方案已经在同一社区中引发了相当大的兴趣。

DSPy 与 PyTorch 有什么关系?

如果您有数据科学背景,那么当您开始使用 DSPy 时,您会很快发现它与 PyTorch 在语法上的相似之处。DSPy 论文[1]的作者明确指出 PyTorch 是灵感的来源。

在 PyTorch 中,通用层可以在任何模型架构中组成,而在 DSPy 中,通用模块可以在任何基于 LM 的应用程序中组成。此外,在编译 DSPy 程序时,DSPy 模块中的参数会自动优化,这与在 PyTorch 中训练神经网络类似,在 PyTorch 中,模型权重是通过优化器来训练的。

下表总结了 PyTorch 和 DSPy 之间的类比:

Comparison: PyTorch vs. DSPy, Training neural network vs. optimizing LM-based application

DSPy 编程模型

本节讨论 DSPy 编程模型引入的以下三个核心概念:

  • • 签名: 抽象提示和微调[8]

  • • 模块: 抽象提示技术[9]

  • • 提词器: 自动提示任意管道[10]

签名 抽象提示和微调

DSPy 程序中对 LM 的每次调用都必须有一个自然语言签名,以取代传统的手写提示。签名是一个简短的函数,它规定了转换的内容,而不是如何提示 LM 完成转换(例如,"消耗问题和上下文并返回答案")。

DSPy signatures replace hand-written prompts.

DSPy 签名取代了手写提示。

签名是输入和输出字段的最小元组。

Structure of a minimal DSPy signature consists of one or more input and output fields

最小 DSPy 签名的结构

下面是一些速记语法签名的例子。

  1. "question -> answer"
  2. "long-document -> summary"
  3. "context, question -> answer"

在许多情况下,这些速记语法签名就足够了。不过,在需要更多控制的情况下,您也可以用下面的命令定义签名

  • • 对 LM 要解决的子任务的最基本描述、

  • • 输入字段描述

  • • 输出字段的描述。

下面是签名context, question -> answer的完整符号:

  1. class GenerateAnswer(dspy.Signature):
  2.     """Answer questions with short factoid answers."""
  3.     context = dspy.InputField(desc="may contain relevant facts")
  4.     question = dspy.InputField()
  5.     answer = dspy.OutputField(desc="often between 1 and 5 words")

与手写提示不同的是,签名可以通过为每个签名引导示例,编译成自改进和管道自适应的提示或 finetunes。

模块: 抽象提示技术

你可能对一些不同的提示技术并不陌生,例如在提示语开头添加 "你的任务是...... "或 "你是一个...... "这样的句子、思维链("让我们一步步思考"),或者在提示语结尾添加 "不要胡编乱造 "或 "只能使用提供的上下文 "这样的句子。

DSPy 中的模块通过模板化和参数化来抽象这些提示技术。这意味着它们可以通过应用提示、微调、增强和推理技术,使 DSPy 签名适应任务。

下图展示了如何将签名传递给 ChainOfThought 模块,然后调用输入字段上下文和问题的值。

  1. # Option 1: Pass minimal signature to ChainOfThought module
  2. generate_answer = dspy.ChainOfThought("context, question -> answer")
  3. # Option 2Or pass full notation signature to ChainOfThought module
  4. generate_answer = dspy.ChainOfThought(GenerateAnswer)
  5. Call the module on a particular input.
  6. pred = generate_answer(context = "Which meant learning Lisp, since in those days Lisp was regarded as the language of AI.",
  7.                        question = "What programming language did the author learn in college?")

下图展示了 ChainOfThought 模块如何初步实现 "上下文、问题 -> 答案 "签名。如果你想亲自尝试,可以使用 lm.inspect_history(n=1) 打印最后一次提示。

Initial implementation of the signature “context, question -> answer” with a ChainOfThought module

使用 ChainOfThought 模块初步实现 "上下文、问题 -> 答案 "签名

在撰写本文时,DSPy 实现了以下六个模块:

  • • dspy.Predict: 处理输入和输出字段,生成指令,并为指定的问题创建模板。

  • • dspy.ChainOfThought: 继承自 "预测 "模块,并增加了 "思维链 "处理功能。

  • • dspy.ChainOfThoughtWithHint:继承自 Predict 模块,并增强了 ChainOfThought 模块的推理提示选项。

  • • dspy.MultiChainComparison:多链比较: 继承自预测模块,并增加了多链比较功能。

  • • dspy.检索 从检索器模块检索段落。

  • • dspy.ReAct: 旨在组成 "思考"、"行动 "和 "观察 "的交错步骤。

你可以在继承自 dspy.Module 并使用两个方法的类中将这些模块串联起来。您可能已经注意到了与 PyTorch 的语法相似性:

  • • __init__(): 声明使用的子模块。

  • • forward(): 描述已定义的子模块之间的控制流。

  1. class RAG(dspy.Module):
  2.     def __init__(self, num_passages=3):
  3.         super().__init__()
  4.         self.retrieve = dspy.Retrieve(k=num_passages)
  5.         self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
  6.     
  7.     def forward(self, question):
  8.         context = self.retrieve(question).passages
  9.         prediction = self.generate_answer(context=context, question=question)
  10.         return dspy.Prediction(context=context, answer=prediction.answer)

上述代码在 RAG() 类中定义的模块间创建了如下信息流:

None

天真的 RAG 流水线示例代码和模块间的信息流。

提词器 自动提示任意流水线

提词器是 DSPy 程序的优化器。它们采用一种度量标准,并与 DSPy 编译器一起学习如何为 DSPy 程序的模块引导和选择有效的提示。

  1. from dspy.teleprompt import BootstrapFewShot
  2. # Simple teleprompter example
  3. teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)

在撰写本文时,DSPy 实现了以下五种提示器:

  • • dspy.LabeledFewShot:定义预测器使用的 k 个样本数。

  • • dspy.BootstrapFewShot: 引导

  • • dspy.BootstrapFewShotWithRandomSearch: 继承自 BootstrapFewShot 提词器,并为随机搜索过程引入了附加属性。

  • • dspy.BootstrapFinetune:将提词器定义为用于微调编译的 BootstrapFewShot 实例。

  • • dspy.Ensemble: 创建多个程序的合集版本,将不同程序的各种输出缩减为单一输出。

不同的提词器在优化成本与质量等方面会有不同的权衡。

DSPy 编译器

DSPy 编译器会对程序进行内部跟踪,然后使用优化器(提词器)对其进行优化,以最大限度地提高任务的给定指标(如提高质量或成本)。优化取决于您使用的 LM 类型:

  • • 对于 LLM:构建高质量的少量提示信息

  • • 对于小型 LM:训练自动微调

这意味着 DSPy 编译器会自动将模块映射为提示、微调、推理和增强的高质量组合。[1] 在内部,编译器会在输入上模拟各种版本的程序,并引导每个模块的示例轨迹进行自我完善,从而优化管道以适应您的任务。这一过程类似于神经网络的训练过程。

例如,虽然前面创建的 ChainOfThought 模块(初始提示)可能是任何 LM 理解任务的良好起点,但它可能不是最佳提示。如下图所示,DSPy 的编译

DSPy 编译器如何优化初始提示(灵感来自 Erika's

如下代码和图片所示,编译器需要以下输入:

  • • 程序、

  • • 提词器,包括已定义的验证码。

  • • 一些训练样本。

  1. from dspy.teleprompt import BootstrapFewShot
  2. # Small training set with question and answer pairs
  3. trainset = [dspy.Example(question="What were the two main things the author worked on before college?"
  4.                          answer="Writing and programming").with_inputs('question'),
  5.             dspy.Example(question="What kind of writing did the author do before college?"
  6.                          answer="Short stories").with_inputs('question'),
  7.             ...
  8.             ]
  9. # The teleprompter will bootstrap missing labels: reasoning chains and retrieval contexts
  10. teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
  11. compiled_rag = teleprompter.compile(RAG(), trainset=trainset)

None

DSPy 示例: Naive RAG 管道

既然您已经熟悉了 DSPy 中的所有基本概念,那么就让我们将其整合到您的第一个 DSPy 管道中吧。

检索增强生成(RAG)目前在生成式人工智能领域风靡一时。如果你一直关注我的工作,就会看到我使用 LangChain和 LlamaIndex构建了初级和高级 RAG 管道。因此,从快速、简单的 RAG 管道开始学习 DSPy 是非常有意义的。

关于 Jupyter Notebook 形式的端到端管道,我建议您查看 DSPy GitHub 存储库中的 Intro Notebook[11] 或 Connor Shorten 撰写的 Getting Started with RAG in DSPy Notebook[12]。

先决条件:安装 DSPy

要安装 dspy-ai Python 软件包,只需 pip install 即可。

pip install dspy-ai
步骤 1:设置

首先,您需要设置 LM 和检索模型 (RM):

  • • LM:我们将使用 OpenAI 的 gpt-3.5-turbo,您需要一个 OpenAI API 密钥。要获得该密钥,您需要一个 OpenAI 帐户,然后在 API 密钥下 "创建新密钥"。

  • • RM:我们将使用开源矢量数据库 Weaviate,并在其中填充其他数据。

首先,让我们用 LlamaIndex GitHub 代码库(MIT 许可)中的一些示例数据填充外部数据库。您可以用自己的数据替换这部分内容。

  1. !mkdir -p 'data'
  2. !wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham_essay.txt'

接下来,我们将把文档分割成单句,并将其输入数据库。为简单起见,我们将使用本文中嵌入的 Weaviate,您无需注册 API 密钥即可免费使用。请注意,在使用 Weaviate 时,必须使用名为 "content "的属性摄取数据。

  1. import weaviate
  2. from weaviate.embedded import EmbeddedOptions
  3. import re
  4. # Connect to Weaviate client in embedded mode
  5. client = weaviate.Client(embedded_options=EmbeddedOptions(),
  6.                              additional_headers={
  7.                                 "X-OpenAI-Api-Key""sk-<YOUR-OPENAI-API-KEY>",
  8.                              }
  9.                          )
  10. # Create Weaviate schema
  11. # DSPy assumes the collection has a text key 'content'
  12. schema = {
  13.    "classes": [
  14.        {
  15.            "class""MyExampleIndex",
  16.            "vectorizer""text2vec-openai",
  17.             "moduleConfig": {"text2vec-openai": {}},
  18.            "properties": [{"name""content""dataType": ["text"]}]
  19.        }      
  20.    ]
  21. }
  22.     
  23. client.schema.create(schema)
  24. # Split document into single sentences
  25. chunks = []
  26. with open("./data/paul_graham_essay.txt"'r', encoding='utf-8'as file:
  27.     text = file.read()
  28.     sentences = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s', text)
  29.     sentences = [sentence.strip() for sentence in sentences if sentence.strip()]
  30.     chunks.extend(sentences)
  31. # Populate vector database in batches
  32. client.batch.configure(batch_size=100)  # Configure batch
  33. with client.batch as batch:  # Initialize a batch process
  34.     for i, d in enumerate(chunks):  # Batch import data
  35.         properties = {
  36.             "content": d,
  37.         }
  38.         batch.add_data_object(
  39.             data_object=properties,
  40.             class_name="MyExampleIndex"
  41.         )

现在,您可以在全局设置中配置 LM 和 RM。

  1. import dspy
  2. import openai
  3. from dspy.retrieve.weaviate_rm import WeaviateRM
  4. Set OpenAI API key
  5. openai.api_key = "sk-<YOUR-OPENAI-API-KEY>"
  6. # Configure LLM
  7. lm = dspy.OpenAI(model="gpt-3.5-turbo")
  8. # Configure Retriever
  9. rm = WeaviateRM("MyExampleIndex"
  10.                 weaviate_client = client)
  11. # Configure DSPy to use the following language model and retrieval model by default
  12. dspy.settings.configure(lm = lm, 
  13.                         rm = rm)
第二步:收集数据

接下来,我们将收集一些训练示例(本例中为手工标注的示例)。与训练神经网络不同,您只需要几个示例。

  1. # Small training set with question and answer pairs
  2. trainset = [dspy.Example(question="What were the two main things the author worked on before college?"
  3.                          answer="Writing and programming").with_inputs('question'),
  4.             dspy.Example(question="What kind of writing did the author do before college?"
  5.                          answer="Short stories").with_inputs('question'),
  6.             dspy.Example(question="What was the first computer language the author learned?"
  7.                          answer="Fortran").with_inputs('question'),
  8.             dspy.Example(question="What kind of computer did the author's father buy?"
  9.                          answer="TRS-80").with_inputs('question'),
  10.             dspy.Example(question="What was the author's original plan for college?"
  11.                          answer="Study philosophy").with_inputs('question'),]
第 3 步:编写 DSPy 程序

现在,您可以编写第一个 DSPy 程序了。它将是一个 RAG 系统。首先,您需要定义一个签名上下文,问题 -> 答案,如 Signatures 中所示,名为 GenerateAnswer:

  1. class GenerateAnswer(dspy.Signature):
  2.     """Answer questions with short factoid answers."""
  3.     context = dspy.InputField(desc="may contain relevant facts")
  4.     question = dspy.InputField()
  5.     answer = dspy.OutputField(desc="often between 1 and 5 words")

定义完签名后,你需要创建一个继承自 dspy.Module 的自定义 RAG 类。在 __init__(): 方法中声明相关模块,在 forward() 方法中描述模块间的信息流。

  1. class RAG(dspy.Module):
  2.     def __init__(self, num_passages=3):
  3.         super().__init__()
  4.         self.retrieve = dspy.Retrieve(k=num_passages)
  5.         self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
  6.     
  7.     def forward(self, question):
  8.         context = self.retrieve(question).passages
  9.         prediction = self.generate_answer(context=context, question=question)
  10.         return dspy.Prediction(context=context, answer=prediction.answer)
第 4 步:编译 DSPy 程序

最后,您可以定义提词器并编译 DSPy 程序。这将更新 ChainOfThought 模块中使用的提示符。在本例中,我们将使用一个简单的 BootstrapFewShot 提示器。

  1. from dspy.teleprompt import BootstrapFewShot
  2. Set up a basic teleprompter, which will compile our RAG program.
  3. teleprompter = BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
  4. # Compile!
  5. compiled_rag = teleprompter.compile(RAG(), trainset=trainset)

现在,您可以调用 RAG 管道,如下图所示:

pred = compiled_rag(question = "What programming language did the author learn in college?")

在此基础上,您可以评估结果并反复修改流程,直到您对管道的性能感到满意为止。有关评估的详细说明,我建议查看 DSPy GitHub 存储库中的介绍笔记本或 Connor Shorten 撰写的《DSPy 笔记本中的 RAG 入门[13]》。

总结

本文简要介绍了 DSPy 框架[1],目前生成式人工智能社区对该框架非常感兴趣。DSPy 框架引入了一系列概念,将构建基于 LM 的应用程序从手动提示工程转向编程。

在 DSPy 中,传统的提示工程概念被以下概念所取代:

  • • 签名取代手写提示、

  • • 模块取代特定的提示工程技术,以及

  • • 提词器和 DSPy 编译器取代了手工迭代的提示工程。

在介绍了 DSPy 概念之后,本文将通过一个使用 OpenAI 语言模型和 Weaviate 向量数据库作为检索器模型的天真 RAG 管道示例为您进行讲解。

参考文献

文献

[1] Khattab, O., Singhvi, A., Maheshwari, P., Zhang, Z., Santhanam, K., Vardhamanan, S., ... & Potts, C. (2023). Dspy: ArXiv preprint arXiv:2310.03714.

其他学习资源
  • • DSPy 介绍笔记本[14]

  • • Connor Shorten 制作的系列视频: DSPy Explained[15]!

  • • Connor Shorten 的《DSPy 中的 RAG 入门[16]!》及相关 Jupyter 笔记本

引用链接

[1] Intro to DSPy: Goodbye Prompting, Hello Programming! | by Leonie Monigatti | Feb, 2024 | Towards Data Science: https://towardsdatascience.com/intro-to-dspy-goodbye-prompting-hello-programming-4ca1c6ce3eb9
[2] 视频("DSPy Explained!Connor Shorten): https://www.youtube.com/watch?v=41EfOY0Ldkc
[3] DSPy Compiler: https://freedium.cfd/https://towardsdatascience.com/intro-to-dspy-goodbye-prompting-hello-programming-4ca1c6ce3eb9#a471
[4] 签名: https://freedium.cfd/https://towardsdatascience.com/intro-to-dspy-goodbye-prompting-hello-programming-4ca1c6ce3eb9#7029
[5] DSPy 入门笔记本: https://github.com/stanfordnlp/dspy/blob/main/intro.ipynb
[6] DSPy: 将声明式语言模型调用编译成自我改进管道: https://arxiv.org/abs/2310.03714
[7] Omar Khattab: https://twitter.com/lateinteraction
[8] Signatures: Abstracting prompting and fine-tuning: https://freedium.cfd/https://towardsdatascience.com/intro-to-dspy-goodbye-prompting-hello-programming-4ca1c6ce3eb9#7029
[9] Modules: Abstracting prompting techniques: https://freedium.cfd/https://towardsdatascience.com/intro-to-dspy-goodbye-prompting-hello-programming-4ca1c6ce3eb9#d613
[10] Teleprompters: Automating prompting for arbitrary pipelines: https://freedium.cfd/https://towardsdatascience.com/intro-to-dspy-goodbye-prompting-hello-programming-4ca1c6ce3eb9#f290
[11] Intro Notebook: https://github.com/stanfordnlp/dspy/blob/main/intro.ipynb
[12] Getting Started with RAG in DSPy Notebook: https://github.com/weaviate/recipes/blob/main/integrations/dspy/1.Getting-Started-with-RAG-in-DSPy.ipynb
[13] DSPy 笔记本中的 RAG 入门: https://github.com/weaviate/recipes/blob/main/integrations/dspy/1.Getting-Started-with-RAG-in-DSPy.ipynb
[14] DSPy 介绍笔记本: https://github.com/stanfordnlp/dspy/blob/main/intro.ipynb
[15] DSPy Explained: https://www.youtube.com/watch?v=41EfOY0Ldkc
[16] DSPy 中的 RAG 入门: https://www.youtube.com/watch?v=CEuUG4Umfxs

大语言模型84

提示工程10

langchain8

RAG17

大语言模型 · 目录

上一篇LLM评估指标高级指南下一篇2 月 19 日至 2 月 25 日一周最重要的LMM论文

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/866005
推荐阅读
  

闽ICP备14008679号