跳转到内容

DSPy 优化器 (原名为Teleprompters)

一个DSPy优化器是一种算法,可以调整DSPy程序的参数(即提示和/或语言模型权重),以最大化您指定的指标,如准确性。

一个典型的DSPy优化器需要三个要素:

  • 您的 DSPy 程序。这可能是一个单一模块(例如,dspy.Predict)或一个复杂的多模块程序。

  • 您的评估指标。这是一个评估程序输出并为其分配分数(越高越好)的函数。

  • 少量训练输入。这可能非常少(例如,仅5或10个示例)且不完整(仅包含程序输入,没有任何标签)。

如果你恰好拥有大量数据,DSPy 可以充分利用这些数据。但你也可以从小规模开始,获得强大的结果。

注意: 之前称为teleprompters。我们正在进行正式的名称更新,这将贯穿整个库和文档。

DSPy优化器调整什么?如何调整它们?

DSPy中的不同优化器将通过为每个模块合成良好的少样本示例(如dspy.BootstrapRS,1)、为每个提示提出并智能探索更好的自然语言指令(如dspy.MIPROv2,2dspy.GEPA,3)以及为模块构建数据集并使用它们微调系统中LM权重(如dspy.BootstrapFinetune4)来优化程序的性能。

DSPy优化器的一个例子是什么?不同的优化器如何工作?

dspy.MIPROv2优化器为例。首先,MIPRO从引导阶段开始。它接收您的程序(此时可能尚未优化),并在不同输入上多次运行,以收集每个模块的输入/输出行为轨迹。它会过滤这些轨迹,仅保留那些在您的指标评分较高的轨迹中出现的部分。其次,MIPRO进入其基础提案阶段。它会预览您的DSPy程序代码、您的数据以及运行程序时的轨迹,并使用它们为程序中的每个提示草拟许多潜在的指令。第三,MIPRO启动离散搜索阶段。它从您的训练集中采样小批量数据,提出用于构建流程中每个提示的指令和轨迹组合,并在小批量数据上评估候选程序。利用得到的评分,MIPRO更新一个代理模型,帮助提案随时间逐步改进。

让DSPy优化器如此强大的一个特点是它们可以组合使用。你可以运行dspy.MIPROv2并将生成的程序作为输入再次传递给dspy.MIPROv2,或者传递给dspy.BootstrapFinetune以获得更好的结果。这在一定程度上是dspy.BetterTogether的精髓。或者,你也可以运行优化器后提取前5个候选程序,并构建一个dspy.Ensemble。这使你可以通过高度系统化的方式扩展推理时计算(例如,集成)以及DSPy独特的预推理时计算(即优化预算)。

目前有哪些DSPy优化器可用?

优化器可以通过 from dspy.teleprompt import * 访问。

自动少样本学习

这些优化器通过自动生成并包含发送给模型的提示中的优化示例来扩展签名,实现少样本学习。

  1. LabeledFewShot: 简单地从提供的带标签输入和输出数据点构建少量示例(演示)。需要 k(提示中的示例数量)和 trainset 来随机选择 k 个示例。

  2. BootstrapFewShot: 使用一个teacher模块(默认为你的程序)为程序的每个阶段生成完整的演示,以及在trainset中的标记示例。参数包括max_labeled_demos(从trainset中随机选择的演示数量)和max_bootstrapped_demos(由teacher生成的额外示例数量)。引导过程使用度量标准验证演示,仅包含通过度量标准的演示在"编译"提示中。高级:支持使用一个不同的DSPy程序作为teacher程序,该程序具有兼容的结构,用于更困难的任务。

  3. BootstrapFewShotWithRandomSearch: 多次应用BootstrapFewShot,通过随机搜索生成的演示,并在优化过程中选择最佳程序。参数与BootstrapFewShot相同,额外增加了num_candidate_programs,用于指定在优化过程中评估的随机程序数量,包括未编译程序的候选、LabeledFewShot优化程序、未打乱示例的BootstrapFewShot编译程序以及具有随机示例集的BootstrapFewShot编译程序的num_candidate_programs

  4. KNNFewShot. 使用k-最近邻算法为给定输入示例寻找最接近的训练示例演示。这些最近邻演示随后被用作BootstrapFewShot优化过程的训练集。

自动指令优化

这些优化器为提示生成最优指令,并且在MIPROv2的情况下,还可以优化少量示例演示集。

  1. COPRO: 为每个步骤生成并精炼新指令,并通过坐标上升法(使用度量函数和trainset进行爬山优化)进行优化。参数包括depth,表示优化器运行提示改进的迭代次数。

  2. MIPROv2: 在每一步中生成指令少量示例。指令生成过程具备数据感知和演示感知能力。使用贝叶斯优化来有效搜索模块间生成指令/演示的空间。

  3. SIMBA

  4. GEPA: 利用语言模型反思dspy程序的执行轨迹,识别哪些部分有效、哪些无效,并提出解决差距的提示。此外,GEPA可以利用领域特定的文本反馈来快速改进dspy程序。

自动微调

该优化器用于微调底层的LLM(s)。

  1. BootstrapFinetune: 将基于提示的DSPy程序提炼为权重更新。输出是一个具有相同步骤的DSPy程序,但每个步骤都由微调后的模型执行,而不是通过提示语言模型完成。查看分类微调教程获取完整示例。

程序转换

  1. Ensemble: 将一组DSPy程序进行集成,可以选择使用完整集合或随机抽取子集形成一个单一程序。

我应该使用哪种优化器?

最终,找到适合使用的“正确”优化器以及任务的最佳配置需要进行实验。DSPy中的成功仍然是一个迭代过程——要在你的任务上获得最佳性能,你需要探索和迭代。

话虽如此,以下是入门的一般指导:

  • 如果你有非常少的示例(大约10个),从BootstrapFewShot开始。
  • 如果你有更多数据(50个示例或更多),尝试使用 BootstrapFewShotWithRandomSearch
  • 如果你更倾向于只进行指令优化(即希望保持提示为0-shot),请使用配置用于0-shot优化的MIPROv2
  • 如果你愿意使用更多推理调用来执行更长的优化运行(例如40次试验或更多),并且有足够的数据(例如200个示例或更多以防止过拟合),那么尝试MIPROv2
  • 如果你已经能够使用其中一个与大型语言模型(例如,7B参数或以上)配合,并且需要一个非常高效的程序,可以使用BootstrapFinetune为你的任务微调一个小型语言模型。

如何使用优化器?

它们都共享这个通用接口,只是在关键字参数(超参数)上存在一些差异。完整列表可以在API参考中找到。

让我们用最常见的 BootstrapFewShotWithRandomSearch 来看看这个。

from dspy.teleprompt import BootstrapFewShotWithRandomSearch

# Set up the optimizer: we want to "bootstrap" (i.e., self-generate) 8-shot examples of your program's steps.
# The optimizer will repeat this 10 times (plus some initial attempts) before selecting its best attempt on the devset.
config = dict(max_bootstrapped_demos=4, max_labeled_demos=4, num_candidate_programs=10, num_threads=4)

teleprompter = BootstrapFewShotWithRandomSearch(metric=YOUR_METRIC_HERE, **config)
optimized_program = teleprompter.compile(YOUR_PROGRAM_HERE, trainset=YOUR_TRAINSET_HERE)

入门指南 III:优化 DSPy 程序中的语言模型提示或权重

一次典型的简单优化运行大约花费2美元,耗时约十分钟,但在使用非常大的语言模型或非常大的数据集运行优化器时要小心。 优化器运行的成本可能低至几美分,也可能高达数十美元,具体取决于您的语言模型、数据集和配置。

这是一个最小但完全可运行的示例,展示了如何设置一个通过维基百科搜索回答问题的dspy.ReAct智能体,并使用dspy.MIPROv2在廉价light模式下,对从HotPotQA数据集中抽取的500个问答对进行优化。

import dspy
from dspy.datasets import HotPotQA

dspy.configure(lm=dspy.LM('openai/gpt-4o-mini'))

def search(query: str) -> list[str]:
    """Retrieves abstracts from Wikipedia."""
    results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3)
    return [x['text'] for x in results]

trainset = [x.with_inputs('question') for x in HotPotQA(train_seed=2024, train_size=500).train]
react = dspy.ReAct("question -> answer", tools=[search])

tp = dspy.MIPROv2(metric=dspy.evaluate.answer_exact_match, auto="light", num_threads=24)
optimized_react = tp.compile(react, trainset=trainset)

在DSPy 2.5.29上进行类似非正式运行,将ReAct的得分从24%提升至51%。

给定一个用于search的检索索引,你最喜欢的dspy.LM,以及一个包含问题和真实答案的小型trainset,以下代码片段可以使用内置的dspy.SemanticF1指标(该指标作为一个DSPy模块实现)来优化你的具有长输出的RAG系统。

class RAG(dspy.Module):
    def __init__(self, num_docs=5):
        self.num_docs = num_docs
        self.respond = dspy.ChainOfThought('context, question -> response')

    def forward(self, question):
        context = search(question, k=self.num_docs)   # not defined in this snippet, see link above
        return self.respond(context=context, question=question)

tp = dspy.MIPROv2(metric=dspy.SemanticF1(), auto="medium", num_threads=24)
optimized_rag = tp.compile(RAG(), trainset=trainset, max_bootstrapped_demos=2, max_labeled_demos=2)

要运行一个完整的RAG示例,请启动此教程。它将StackExchange社区子集上的RAG系统质量从53%提升至61%。

点击显示数据集设置代码。

import random
from typing import Literal

from datasets import load_dataset

import dspy
from dspy.datasets import DataLoader

# Load the Banking77 dataset.
CLASSES = load_dataset("PolyAI/banking77", split="train", trust_remote_code=True).features["label"].names
kwargs = {"fields": ("text", "label"), "input_keys": ("text",), "split": "train", "trust_remote_code": True}

# Load the first 2000 examples from the dataset, and assign a hint to each *training* example.
trainset = [
    dspy.Example(x, hint=CLASSES[x.label], label=CLASSES[x.label]).with_inputs("text", "hint")
    for x in DataLoader().from_huggingface(dataset_name="PolyAI/banking77", **kwargs)[:2000]
]
random.Random(0).shuffle(trainset)

import dspy
lm=dspy.LM('openai/gpt-4o-mini-2024-07-18')

# Define the DSPy module for classification. It will use the hint at training time, if available.
signature = dspy.Signature("text, hint -> label").with_updated_fields('label', type_=Literal[tuple(CLASSES)])
classify = dspy.ChainOfThought(signature)
classify.set_lm(lm)

# Optimize via BootstrapFinetune.
optimizer = dspy.BootstrapFinetune(metric=(lambda x, y, trace=None: x.label == y.label), num_threads=24)
optimized = optimizer.compile(classify, trainset=trainset)

optimized(text="What does a pending cash withdrawal mean?")

# For a complete fine-tuning tutorial, see: https://dspy.ai/tutorials/classification_finetuning/

可能的输出(来自最后一行):

Prediction(
    reasoning='待处理的现金取款表示取款请求已发起但尚未完成或处理。此状态意味着交易仍在进行中,资金尚未从账户中扣除或提供给用户。',
    label='pending_cash_withdrawal'
)

在DSPy 2.5.29上进行类似非正式运行,将GPT-4o-mini的得分从66%提升至87%。

保存和加载优化器输出

在通过优化器运行程序后,保存它也是很有用的。在稍后的时间点,可以从文件加载程序并用于推理。为此,可以使用loadsave方法。

optimized_program.save(YOUR_SAVE_PATH)

生成的文件是纯文本JSON格式。它包含源程序中的所有参数和步骤。你可以随时读取它,查看优化器生成了什么。

要从文件加载程序,你可以从该类实例化一个对象,然后调用其上的加载方法。

loaded_program = YOUR_PROGRAM_CLASS()
loaded_program.load(path=YOUR_SAVE_PATH)
优云智算