DSPY COMPILING DECLARATIVE LANGUAGE MODEL CALLS INTO SELF-IMPROVING PIPELINES论文学习
不懂的
Teleprompter 在 DSPy 编译器中的作用
在 DSPy 编程模型中,Teleprompter(提示器) 是一种核心组件,负责模块化优化过程。具体来说,Teleprompter 是通用的优化策略,用于指导 DSPy 编译器如何让各个模块从数据中学习,以提升整个程序的质量或降低其运行成本。以下是对 Teleprompter 的详细解释:
- 定义与功能
- 通用优化策略:Teleprompter 不局限于特定的优化方法,而是提供了一套通用的框架,可以应用于多种优化需求,如提升模型性能、减少计算资源消耗等。
- 决策指导:它决定了每个 DSPy 模块应如何从输入数据中学习。这包括选择合适的学习方法(如少样本学习、微调等)以及如何生成和利用示例数据来改进模块的行为。
- 工作机制
- 模块学习:Teleprompter 通过分析模块的示例轨迹(example traces),来理解模块在不同输入下的表现。这些轨迹包含模块在处理特定任务时的详细操作步骤和输出结果。
- 构建提示或微调模型:基于这些轨迹,Teleprompter 可以生成有效的少样本提示(few-shot prompts),帮助模块在未来处理类似任务时表现得更好。此外,它还可以指导对小型语言模型(LMs)的微调,使其更适应管道中各个步骤的需求。
- 模块化优势
- 高度模块化:由于 Teleprompter 是模块化设计的,它可以独立于具体的优化目标进行扩展和调整。这意味着可以灵活地应用不同的优化策略,而不需要对整个编译器进行大幅修改。
- 适应多种任务:无论是提示优化、模型微调、推理增强还是数据增强,Teleprompter 都能够根据具体任务需求,选择最合适的优化方法。
- 自动化优化流程
- 自动映射:Teleprompter 帮助编译器自动将声明式模块映射为高质量的优化组合。这包括提示生成、模型微调、推理过程的优化等,使得整个自然语言处理管道能够高效协同工作。
- 自我改进:通过不断地模拟和优化,Teleprompter 使得 DSPy 程序能够自我改进,逐步提升其在特定任务上的表现。
- 类比与启发
- 类比 PyTorch 的优化器:类似于 PyTorch 中用于调整模型参数的优化器(如 SGD、Adam 等),Teleprompter 也是一种用于优化 DSPy 模块行为的工具。不过,Teleprompter 更加专注于如何利用数据和示例来指导模块的学习过程。
总结 Teleprompter 在 DSPy 编译器中扮演着至关重要的角色,通过提供通用的优化策略,指导各个模块如何从数据中有效学习,从而自动优化整个 DSPy 程序的性能和效率。其模块化和自动化的设计,使得 DSPy 编程模型能够灵活适应多种自然语言处理任务,并持续提升其处理能力。
摘要
背景:
当前的解决复杂问题,需要将多个语言模型堆叠成管道单的技术来解决,然后每个 LLM 负责一个特定的任务。一般情况下设计 prompt 模版让 LLM 负责特定的任务
问题:
- 需要专家人工设计 prompt
- 设计的 prompt 依赖于经验和试错,面对复杂任务时会出错
- 设计的 prompt 通用性较差,无法迁移到新的任务和场景上。
- Prompt 是静态的,缺乏灵活的机制
- 用 prompt 可能让多个 LLM 堆叠的性能下降
工作:
提出 DSPy(Declarative Self-Improvement Pipelines)模块: 将 LM 的工作流转换为 text transformation graph 文本变换图来实现对复杂任务的处理
这些图实际上是 命令式计算图(imperative computation graphs),其中语言模型(LM)通过声明式的模块调用。
解释:
- DSPy 模块具有参数化特性,可以通过创建和收集示例来学习- 如何应用 prompting、微调(finetuning)、数据增强(augmentation) 和 推理(reasoning) 等技术的组合。
- DSPy 允许用户通过编写简单的程序来定义 LM 管道,这些管道能够执行复杂的任务,如数学问题推理、多跳检索、复杂问题回答和控制智能体循环等。
- DSPy 中的程序通过一个编译器进行优化,能够在几分钟内自动调整管道,以最大化某个指定的指标(如任务表现)。
结果:
-
比少量样本提示(few-shot prompting) 提高了 25% 到 65% 的表现,并且相比 专家创建的示例 提高了 5% 到 46%(具体数据取决于不同的模型和任务)。
-
DSPy 程序即使编译到较小的开源语言模型(如 770M 参数的 T5 和 llama2-13b-chat)上,表现也能与依赖专家编写提示链的专有 GPT-3.5 方法相媲美。
引言
背景
多阶段管道和 agent 发展:将复杂任务分解为对多个可以调用 LM 任务
问题
- LLM 对 prompt 非常敏感,并且在多个 LM 的系统中更敏感
- 当前 LM 调用一般使用 prompt 模版来实现
- 这种 prompt 无法扩展泛化不能用到其他的 pipeline 上
工作
引入
为了实现更系统化的 AI pipeline设计方法,我们引入了 DSPy 编程模块
- 不用自由形式的 strings,而是用类似编程语言
- 然后编译器就可以自动从中生成 LM 调用策略和 prompt
想法来源:
受到了神经网络抽象的启发
- 通用层的模块化组合:在构建复杂的神经网络架构时,许多通用的层(如卷积层、全连接层等)可以以模块化的方式进行组合。类似地,DSPy 也允许将各种处理步骤(例如提示生成、数据预处理等)作为模块组合起来,构建复杂的语言模型管道。
- 优化器而非手动调优:神经网络的训练不再依赖于人工手动调整每个模型参数,而是通过使用优化算法(如梯度下降)来自动调整网络权重。同样,DSPy 的编译器通过自动化的优化策略,减少了人工设计和调优每个模型的需求
实现
DSPy programming model
- 将 prompt 转换为具有自然语言类型签名的声明模块
- DSPy 模块类似于神经网络的层,是适应任务的组件,能够抽象出各种文本转换任务,例如回答问题或总结论文。
- 对每个模块进行参数化,能够通过在管道中反复引导有用的示范来学习其预期的行为。
pipeline 构建:
- 声明所需模块
- 使用的逻辑流来逻辑地连接模块
DSPy compiler
- 编译器的作用:
- 优化 DSPy 程序:提升程序的质量或降低其成本。
- 自动映射:
- 高质量组合:编译器自动将声明式模块映射为高质量的提示(prompting)、微调(finetuning)、推理(reasoning)和增强(augmentation)的组合,从而优化整个管道的性能。
- 编译器的输入:
- 程序:待优化的 DSPy 程序。
- 训练输入:少量带有可选标签的训练数据。
- 验证指标:用于评估优化效果的标准。
- 训练过程:
- 程序模拟:编译器在给定的输入上模拟程序的不同版本。
- 自我改进:通过引导模块生成示例轨迹,编译器利用这些轨迹构建有效的少样本提示(few-shot prompts)或对管道的步骤进行小型语言模型(LMs)的微调(finetuning)。
- 模块化优化:
- Teleprompters:编译器使用称为 teleprompters 的通用优化策略来决定模块如何从数据中学习。这些策略是高度模块化的,能够灵活应用于不同的优化需求。
评估
超过专家编写 prompt 的性能
数学语言问题(GMS8K;Cobbe等,2021)和多跳问答
结果
- 表明简单的DSPy程序在性能上优于使用手工设计提示的系统,
- 同时也使我们的程序能够有效地使用更小、更高效的语言模型。
相关工作
这一段介绍了 DSPy 编程模型的背景和灵感来源,并回顾了与之相关的研究和工作。主要包括以下几个方面:
1. 灵感来源:
- DSPy 的灵感来自于 Torch, Theano, Chainer 等深度学习框架的工作,这些框架通过提供强大的抽象,推动了深度学习的发展。
- 类似的转变也正在发生在 语言模型(LM)管道 的发展上,DSPy 的目标是为 基础模型编程(foundation model programming) 提供一个坚实的概念框架和编程抽象。
- DSPy 借鉴了 可微编程(differentiable programming) 的思想,但应用于语言模型调用,而非神经网络,并且在语法上借鉴了 PyTorch 的元素。
2. In-context learning 及其发展:
- In-context learning(上下文学习) 是基础模型编程的关键机制,随着研究的发展,越来越多的工作表明,尤其是在 指令调优(instruction tuning) 上,我们可以通过 prompting(提示)来引导语言模型表现出复杂的行为。
- 这些方法依赖于语言模型进行任务指定的行为,而不再依赖于传统的手工构建的启发式规则或任务特定的标注(如 weak supervision),语言模型已经能够替代这些手动构建的任务特定方法。
3. 语言模型管道和工具的应用:
- 当前,语言模型管道通常会调用各种工具,包括 检索模型、多模态基础模型 和更传统的工具(如 API 和 计算器)。许多工具包(如 LangChain, Semantic Kernel, LlamaIndex 等)已经被开发出来,用于简化这些管道的搭建和应用。
- 这些工具包通常依赖于手工编写的 prompt模板 来表达任务特定的行为,这就是 DSPy 试图解决的问题。DSPy 提供了一种更系统化的方式,避免了手动调整模板的复杂性。
4. 离散优化和强化学习的应用:
- 当前有一些研究应用了 离散优化 和 强化学习(RL) 来寻找有效的提示,通常是针对单一的语言模型调用(例如 Guo et al., 2023 等)。然而,DSPy 希望将这一领域的技术推广,提供一个更通用的框架,允许从高层次的声明性签名中优化任意管道,并通过引导高质量的多阶段演示和约束来实现这一目标。
- 在此框架中,DSPy 编译器可能会应用 模型选择技术(如交叉验证)或使用 强化学习 和 语言模型反馈 进行优化,甚至可能结合 贝叶斯超参数优化方法。
5. DSPy 编程模型的目标与贡献:
- 本文旨在 展示 DSPy 编程模型的动机,并报告应用 DSPy 编译器后的新实证结果。其灵感来自于早期的研究工作,如 Bergstra et al., 2010; 2013 和 Paszke et al., 2019,这些研究通过基准数据和定性指标支持各自的编程模型。
- 本文重点展示了 DSPy 编程模型 和其编译器如何帮助构建 出色的语言模型系统,无需手工编写提示字符串,而是通过模块化的单元来构建,从而打开了在 高级抽象层次 上系统性探索丰富设计空间的大门。
DSPy programming model(DSPy 编程模型)
我们提出了 DSPy,它将语言模型视为文本生成的抽象设备,并优化其在任意计算图中的使用。
DSPy 程序用 Python 表达:每个程序接收任务输入(例如,要回答的问题或要总结的论文),并在一系列步骤后返回输出(例如,答案或摘要)。
DSPy 为自动优化贡献了三个抽象:签名、模块和提词器。
- 签名抽象了模块的输入/输出行为;
- 模块替代现有的手动提示技术,可以在任意管道中组合;
- 提词器优化管道中的所有模块,以最大化某个指标。
自然语言签名(Natural language signatures)
使用自然语言类型签名,抽象 prompt 和 fine-tuning
与 prompt 不同,DSPy 程序使用自然语言签名将工作分配给 LLM。
自然语言类型签名:
- 自然语言类型的函数声明,描述了输入和输出,而不是提示 LM 来实现该操作。
- 形式:一个元组(包含输入字段和输出字段)
- 一个字段由在字段名称和可选的元数据组成
- 在使用过程中 DSPy 编译器会根据字段名称推断字段的对应的内容(ICL)
模块
自然语言签名定义了一个接口,而没有实现,下面就介绍如何实例化模块。
使用签名,返回一个具有该签名的函数
预测模块
- 总体功能
- 是签名和模型之间的桥梁
- 存储提供的签名、可选的语言模型、一个用于提示的演示列表
- 工作:
- 接受输入字段的关键字参数
- 利用输入字段过构造 prompt,并包括一些示范
- 最后调用指定的 LM 生成输出
- 解析输出字段,将语言模型结果转换为最终的输出
- 编译模式
-
- 当 Predict 检测到它正处于“编译模式”时,它会内部追踪输入和输出的记录(input/output traces)。这些记录用于帮助 teleprompter(优化器)在后续的引导式学习过程中生成有用的示范(demonstrations)。
- 这个过程类似于通过示范和优化来增强模型的效果,从而提升任务执行的精度和效率。
-
其他内置模块
- 将 prompt 转换为支持任何签名的模块化函数,这与任务特定细节
- 模块里内置的是(方法),而签名定义的是任务
参数化 (将上面介绍的参数化)
参数化:DSPy 在构建和使用语言模型时,通过参数化来细化和控制模型的行为。换句话说,DSPy 允许对每个语言模型调用进行精细的定制,以满足特定任务的需求。
参数化的三个关键要素: 要使语言模型正确执行一个特定的任务或签名,DSPy 需要传递三个重要的参数:
-
(1) 特定的语言模型(LM): 这里,DSPy 需要明确调用哪个语言模型。不同的语言模型可能具有不同的能力和行为。例如,某些模型可能擅长生成文本,另一些可能在推理或翻译任务上更为有效。
举例来说,如果任务是生成一段文章的摘要,DSPy 可能选择一个大规模的文本生成模型;如果任务是代码生成,可能选择一个训练过编程语言的特定模型。
-
(2) 提示指令和字段前缀(Prompt Instructions): 对于每个任务,DSPy 需要明确地传达给模型它应该如何执行。例如,如果任务是翻译,提示指令可能是“Translate English to French”。同时,字段的前缀(字段名如 “question” 和 “answer”)会提供更多关于任务类型的上下文,以确保模型能够理解输入的格式和输出的要求。
例如:
- 输入字段:“question: What is the capital of France?”
- 输出字段:“answer: ”
DSPy 会使用这些字段来生成任务的提示,如“Question: What is the capital of France?” 然后根据输入的“question”字段生成“answer”的输出。
-
(3) 演示示例(Demonstrations): 最重要的一部分是演示示例。这些示例是 DSPy 用来指导语言模型生成正确输出的数据。演示示例提供了任务的具体表现形式,可以用来训练模型或作为少量提示(few-shot prompts)引导模型在没有完全微调的情况下学习。
- 对于冻结的模型(没有进一步训练的模型),演示示例作为提示直接传递给模型,帮助模型理解如何在特定上下文中生成正确的输出。
- 对于需要微调的模型,演示示例可以作为训练数据,帮助模型学习任务的特定行为。
作者的主要研究在于:
-
自动生成和选择有用的演示: DSPy 的关键优势在于能够自动生成和选择有用的演示示例。这使得用户能够为模型任务提供高质量的“少量示例”,而不必手动编写或收集大量的训练数据。通过从实际使用中不断“启动”(bootstrapping)新的演示,DSPy 可以让语言模型不断学习新的行为。
-
[I] 我们可以专注于模型该怎么去做,而不是专注于生成有效示例 [created:: 2024-12-10]
示例
RAG 系统示例:
假设你想要创建一个基于“检索增强生成”(RAG)的系统,该系统结合了检索和生成的步骤来回答问题。具体来说,RAG 系统从数据库中检索相关信息(context),然后基于这些信息生成答案。
以下是 RAG 系统的代码示例:
class RAG(dspy.Module):
def __init__(self, num_passages=3):
# 'Retrieve'模块会使用用户的默认检索设置,除非被覆盖。
self.retrieve = dspy.Retrieve(k=num_passages)
# 'ChainOfThought'模块:根据检索到的context和问题生成答案。
self.generate_answer = dspy.ChainOfThought("context, question -> answer")
def forward(self, question):
# 使用检索模块来获取问题的相关文档(passages)。
context = self.retrieve(question).passages
# 使用ChainOfThought模块来根据检索到的上下文和问题生成答案。
return self.generate_answer(context=context, question=question)
示例使用:
用户可以通过简单的调用来使用该系统:
RAG()("Where is Guaraní spoken?")
这行代码将会使用 RAG
模块来回答“Where is Guaraní spoken?”这个问题,具体的操作流程是:首先检索与问题相关的文档,然后使用 ChainOfThought
来生成答案。
模块化的好处:
-
模块化:该系统清晰地分为两个独立的模块:一个用于检索相关信息,另一个用于基于检索到的信息生成答案。这种模块化设计使得每个部分可以独立地进行开发、测试和优化。
-
灵活的组合:用户可以根据不同的需求替换模块。例如,
ChainOfThought
可以替代基本的Predict
模块,用来生成更复杂的答案。甚至可以根据不同的任务(例如搜索查询生成)调整模块的功能。
任务定制:
通过调整签名(signature),你可以改变系统的行为。例如,如果使用签名 "context, question -> search query"
,系统将不再生成答案,而是生成一个搜索查询。
self.generate_search_query = dspy.ChainOfThought("context, question -> search query")
这会将任务从回答问题变为生成搜索查询。这样,用户可以灵活地在不同任务之间切换。
提示器
在编译 DSPy 程序时,我们通常会调用一个提词器,它是一个优化器,接收程序、训练集和指标,并返回一个新的优化程序。不同的提词器(第 4 节)采用不同的优化策略。
-
DSPy要人工定义管道,我们可不可以自动生成管道,让 AI 定义 pipieline,这样的 pipeline 在复杂、多变的情况下更好,减少了人工干预的需要 [created:: 2024-12-10] [completion:: 2024-12-11]
- 训练数据:
- 小型训练集
- 仅使用输入数据,并不需要完整中间步骤的标签,(除非对于评估模型性能的度量标准有用)
- 减少构建一个新 pipeline 的时候需要重新标注数据
- 评估指标:
- 简单的指标: EM、F 1
- 复杂的指标,平衡多个关注点
- 提示器可以由 teacher 模型组成
- 用 teacher 模型生成适合的演示样例,这样可以提供一些无标签的数据进行训练.
示例
RAG
- 展示了如何使用一个小型的问答训练集和精确匹配指标来优化RAG模块。
代码逐行解释:
## Small training set with only questions and final answers.
qa_trainset = [dspy.Example(question="What is the capital of France?", answer="Paris")]
# The teleprompter will bootstrap missing labels: reasoning chains and retrieval contexts.
teleprompter = dspy.BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
compiled_rag = teleprompter.compile(RAG(), trainset=qa_trainset)
第1-2行:定义训练集
qa_trainset = [dspy.Example(question="What is the capital of France?", answer="Paris")]
- 解释:
- 创建了一个小型的训练集
qa_trainset
,其中包含一个问答对。 - 每个
dspy.Example
包含question
(问题)和answer
(答案)两个字段。 - 这里的训练集非常小,仅包含一个示例,但DSPy设计允许在少量数据下仍能有效工作。
- 创建了一个小型的训练集
第4-6行:引导和编译管道
teleprompter = dspy.BootstrapFewShot(metric=dspy.evaluate.answer_exact_match)
compiled_rag = teleprompter.compile(RAG(), trainset=qa_trainset)
- 解释:
- 第5行:创建一个
BootstrapFewShot
类型的 远程提示器(teleprompter),并指定使用的评估指标为精确匹配(EM)。BootstrapFewShot
的作用是自动生成缺失的标签,如推理链(reasoning chains)和检索上下文(retrieval contexts),从而丰富训练示例。- 评估指标
dspy.evaluate.answer_exact_match
用于衡量生成的答案与参考答案的精确匹配程度。
- 第6行:使用远程提示器编译
RAG
模块,并传入训练集qa_trainset
。RAG()
创建了一个RAG模块实例,该模块包含检索(Retrieve)和生成(ChainOfThought)两个子模块。teleprompter.compile(RAG(), trainset=qa_trainset)
会根据训练集和指定的指标,自动生成和优化RAG管道。- 编译后的
compiled_rag
是一个优化后的RAG管道,能够根据少量训练示例有效地执行问答任务。
- 第5行:创建一个
整体流程说明:
-
定义训练集:
- 创建一个包含问题和答案的小型训练集。例如:“What is the capital of France?” -> “Paris”。
-
创建远程提示器:
BootstrapFewShot
使用指定的评估指标(如精确匹配)来指导管道的优化。- 远程提示器负责自动生成缺失的标签信息,如推理链和检索上下文,提升训练示例的质量和多样性。
-
编译优化管道:
- 使用远程提示器编译RAG模块,并传入训练集。
- 通过编译过程,DSPy 自动生成高质量的少量示例,优化RAG管道的各个模块,使其能够更好地完成问答任务。
优势:
- 标签效率:只需为最终输出提供标签(答案),不需要为每个中间步骤提供标签(如推理链和检索上下文),减少了数据标注的工作量。
- 模块化与自动化优化:用户只需定义高层次的任务需求,DSPy 会自动生成和优化模块行为,使得管道能够高效且准确地完成任务。
- 少量数据有效性:即使训练集很小,通过引导生成高质量的示例,DSPy 仍能有效优化管道,适应复杂任务。
DSPy 编译器
分为三个阶段
阶段一:候选生成
生成数据
需要先造一下示例
用 teacher moodel 或者 zero-shot 生成一些输出,然后用指标去评估,选一些合格的好的
阶段二:参数优化
参数优化是确保程序高效运行和达成预期目标的关键步骤。该阶段的核心任务是为每个参数选择最优的候选值,以提升整体系统的性能或降低资源消耗。
- Few-shot
- 随机搜索
- 树结构的帕森估计器(Tree-structured Parzen Estimators, TPE)
- Fine-tuning
- 更新模块的权重
阶段三:高阶程序优化
修改程序的 pipeline
- 方式一:集成
- 将引导多个相同程序的副本,然后用一个新的程序替换它,新的程序并行运行所有副本,并将它们的预测通过自定义函数(例如,多数投票)汇总成一个结果
- 可不可让他自己优化pipeline [created:: 2024-12-11] [completion:: 2024-12-14]
目标和评估
结果
- 通过 DSPy,我们可以用简洁且明确定义的模块替换手工制作的提示字符串,而不会降低质量或表达能力。
- 参数化模块并将提示处理为优化问题使 DSPy 更适合适应不同的语言模型,并且它可能优于专家撰写的提示。