DSPY COMPILING DECLARATIVE LANGUAGE MODEL CALLS INTO SELF-IMPROVING PIPELINES论文学习

 

DSPY COMPILING DECLARATIVE LANGUAGE MODEL CALLS INTO SELF-IMPROVING PIPELINES论文学习不懂的

DSPY COMPILING DECLARATIVE LANGUAGE MODEL CALLS INTO SELF-IMPROVING PIPELINES论文学习

不懂的

Teleprompter 在 DSPy 编译器中的作用

在 DSPy 编程模型中,Teleprompter(提示器) 是一种核心组件,负责模块化优化过程。具体来说,Teleprompter 是通用的优化策略,用于指导 DSPy 编译器如何让各个模块从数据中学习,以提升整个程序的质量或降低其运行成本。以下是对 Teleprompter 的详细解释:

  1. 定义与功能
    • 通用优化策略:Teleprompter 不局限于特定的优化方法,而是提供了一套通用的框架,可以应用于多种优化需求,如提升模型性能、减少计算资源消耗等。
    • 决策指导:它决定了每个 DSPy 模块应如何从输入数据中学习。这包括选择合适的学习方法(如少样本学习、微调等)以及如何生成和利用示例数据来改进模块的行为。
  2. 工作机制
    • 模块学习:Teleprompter 通过分析模块的示例轨迹(example traces),来理解模块在不同输入下的表现。这些轨迹包含模块在处理特定任务时的详细操作步骤和输出结果。
    • 构建提示或微调模型:基于这些轨迹,Teleprompter 可以生成有效的少样本提示(few-shot prompts),帮助模块在未来处理类似任务时表现得更好。此外,它还可以指导对小型语言模型(LMs)的微调,使其更适应管道中各个步骤的需求。
  3. 模块化优势
    • 高度模块化:由于 Teleprompter 是模块化设计的,它可以独立于具体的优化目标进行扩展和调整。这意味着可以灵活地应用不同的优化策略,而不需要对整个编译器进行大幅修改。
    • 适应多种任务:无论是提示优化、模型微调、推理增强还是数据增强,Teleprompter 都能够根据具体任务需求,选择最合适的优化方法。
  4. 自动化优化流程
    • 自动映射:Teleprompter 帮助编译器自动将声明式模块映射为高质量的优化组合。这包括提示生成、模型微调、推理过程的优化等,使得整个自然语言处理管道能够高效协同工作。
    • 自我改进:通过不断地模拟和优化,Teleprompter 使得 DSPy 程序能够自我改进,逐步提升其在特定任务上的表现。
  5. 类比与启发
    • 类比 PyTorch 的优化器:类似于 PyTorch 中用于调整模型参数的优化器(如 SGD、Adam 等),Teleprompter 也是一种用于优化 DSPy 模块行为的工具。不过,Teleprompter 更加专注于如何利用数据和示例来指导模块的学习过程。

总结 Teleprompter 在 DSPy 编译器中扮演着至关重要的角色,通过提供通用的优化策略,指导各个模块如何从数据中有效学习,从而自动优化整个 DSPy 程序的性能和效率。其模块化和自动化的设计,使得 DSPy 编程模型能够灵活适应多种自然语言处理任务,并持续提升其处理能力。

摘要

背景:

当前的解决复杂问题,需要将多个语言模型堆叠成管道单的技术来解决,然后每个 LLM 负责一个特定的任务。一般情况下设计 prompt 模版让 LLM 负责特定的任务

问题:

  1. 需要专家人工设计 prompt
  2. 设计的 prompt 依赖于经验和试错,面对复杂任务时会出错
  3. 设计的 prompt 通用性较差,无法迁移到新的任务和场景上。
  4. Prompt 是静态的,缺乏灵活的机制
  5. 用 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 任务

问题

  1. LLM 对 prompt 非常敏感,并且在多个 LM 的系统中更敏感
  2. 当前 LM 调用一般使用 prompt 模版来实现
    1. 这种 prompt 无法扩展泛化不能用到其他的 pipeline 上

工作

引入

为了实现更系统化的 AI pipeline设计方法,我们引入了 DSPy 编程模块

  • 不用自由形式的 strings,而是用类似编程语言
  • 然后编译器就可以自动从中生成 LM 调用策略和 prompt

想法来源:

受到了神经网络抽象的启发

  • 通用层的模块化组合:在构建复杂的神经网络架构时,许多通用的层(如卷积层、全连接层等)可以以模块化的方式进行组合。类似地,DSPy 也允许将各种处理步骤(例如提示生成、数据预处理等)作为模块组合起来,构建复杂的语言模型管道。
  • 优化器而非手动调优:神经网络的训练不再依赖于人工手动调整每个模型参数,而是通过使用优化算法(如梯度下降)来自动调整网络权重。同样,DSPy 的编译器通过自动化的优化策略,减少了人工设计和调优每个模型的需求
实现
DSPy programming model
  1. 将 prompt 转换为具有自然语言类型签名的声明模块
    1. DSPy 模块类似于神经网络的层,是适应任务的组件,能够抽象出各种文本转换任务,例如回答问题或总结论文。
    2. 对每个模块进行参数化,能够通过在管道中反复引导有用的示范来学习其预期的行为。

pipeline 构建:

  1. 声明所需模块
  2. 使用的逻辑流来逻辑地连接模块
DSPy compiler
  1. 编译器的作用
    • 优化 DSPy 程序:提升程序的质量或降低其成本。
    • 自动映射
      • 高质量组合:编译器自动将声明式模块映射为高质量的提示(prompting)、微调(finetuning)、推理(reasoning)和增强(augmentation)的组合,从而优化整个管道的性能。
  2. 编译器的输入
    • 程序:待优化的 DSPy 程序。
    • 训练输入:少量带有可选标签的训练数据。
    • 验证指标:用于评估优化效果的标准。
  3. 训练过程
    • 程序模拟:编译器在给定的输入上模拟程序的不同版本。
    • 自我改进:通过引导模块生成示例轨迹,编译器利用这些轨迹构建有效的少样本提示(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; 2013Paszke 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管道,能够根据少量训练示例有效地执行问答任务。

整体流程说明:

  1. 定义训练集

    • 创建一个包含问题和答案的小型训练集。例如:“What is the capital of France?” -> “Paris”。
  2. 创建远程提示器

    • BootstrapFewShot 使用指定的评估指标(如精确匹配)来指导管道的优化。
    • 远程提示器负责自动生成缺失的标签信息,如推理链和检索上下文,提升训练示例的质量和多样性。
  3. 编译优化管道

    • 使用远程提示器编译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 更适合适应不同的语言模型,并且它可能优于专家撰写的提示。