提示工程

使用 DSPy 优化大语言模型:一步一步构建、优化和评估 AI 系统的指南

mm
DSPy is a framework for algorithmically optimizing LM prompts and weights

随着大语言模型(LLM)的能力不断扩展,开发利用其潜力的强大的 AI 系统变得越来越复杂。传统的方法通常涉及复杂的提示技术、数据生成用于微调以及手动指导以确保遵守特定领域的约束。然而,这个过程可能很繁琐、容易出错,并且严重依赖于人工干预。

这就是 DSPy 的用处,DSPy 是一个革命性的框架,旨在简化使用 LLM 的 AI 系统的开发。DSPy 引入了一种系统化的方法来优化 LM 提示和权重,允许开发人员使用最少的手动努力构建复杂的应用程序。

在这份综合指南中,我们将探讨 DSPy 的核心原理、其模块化架构以及它提供的强大功能。我们还将深入探讨实际示例,展示 DSPy 如何改变您使用 LLM 开发 AI 系统的方式。

什么是 DSPy,为什么需要它?

DSPy 是一个框架,它将程序的流程(模块)与每个步骤的参数(LM 提示和权重)分离。这种分离允许系统地优化 LM 提示和权重,允许您构建具有更高可靠性、可预测性和遵守特定领域约束的复杂 AI 系统。

传统上,使用 LLM 开发 AI 系统涉及一个耗时的过程,包括将问题分解为步骤、为每个步骤创建复杂的提示、生成合成示例用于微调以及手动指导 LLM 以遵守特定的约束。这种方法不仅耗时,还容易出错,因为即使对管道、LM 或数据的微小更改也可能需要对提示和微调步骤进行大量的重新工作。

DSPy 通过引入一种新范式来解决这些挑战:优化器。这些 LM 驱动的算法可以根据您要最大化的指标来调整 LM 调用的提示和权重。通过自动化优化过程,DSPy 启用开发人员使用最少的手动干预来构建强大的 AI 系统,从而增强 LM 输出的可靠性和可预测性。

DSPy 的模块化架构

DSPy 的核心是模块化架构,它促进了复杂 AI 系统的组成。该框架提供了一组内置模块,抽象了各种提示技术,例如 dspy.ChainOfThoughtdspy.ReAct。这些模块可以组合和组成更大的程序,允许开发人员构建适合其特定要求的复杂管道。

每个模块封装了可学习的参数,包括指令、少量示例和 LM 权重。当调用模块时,DSPy 的优化器可以微调这些参数以最大化所需的指标,确保 LM 的输出遵守指定的约束和要求。

使用 DSPy 优化

DSPy 引入了一系列强大的优化器,旨在增强 AI 系统的性能和可靠性。这些优化器利用 LM 驱动的算法来调整 LM 调用的提示和权重,最大化指定的指标,同时遵守特定领域的约束。

DSPy 中的一些关键优化器包括:

  1. BootstrapFewShot:此优化器通过自动生成和包含优化示例来扩展签名,实现少量学习。
  2. BootstrapFewShotWithRandomSearch:多次应用 BootstrapFewShot,对生成的示例进行随机搜索,选择优化过程中的最佳程序。
  3. MIPRO:生成每个步骤的指令和少量示例,指令生成是数据感知和示例感知的。它使用贝叶斯优化来有效地搜索生成指令和示例的空间。
  4. BootstrapFinetune:将基于提示的 DSPy 程序浓缩为较小的 LM 的权重更新,允许您为提高效率而微调底层 LLM。

通过利用这些优化器,开发人员可以系统地优化其 AI 系统,确保高质量的输出同时遵守特定领域的约束和要求。

开始使用 DSPy

为了说明 DSPy 的强大功能,让我们一步一步地构建一个实用的示例,使用 DSPy 构建一个用于问答的检索增强生成(RAG)系统。

步骤 1:设置语言模型和检索模型

第一步涉及在 DSPy 中配置语言模型(LM)和检索模型(RM)。

要安装 DSPy,请运行:


pip install dspy-ai

DSPy 支持多个 LM 和 RM API,以及本地模型托管,使其易于集成您首选的模型。


import dspy

<p># 配置 LM 和 RM
turbo = dspy.OpenAI(model='gpt-3.5-turbo')
colbertv2_wiki17_abstracts = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')</p>

<p>dspy.settings.configure(lm=turbo, rm=colbertv2_wiki17_abstracts)</p>

步骤 2:加载数据集

接下来,我们将加载 HotPotQA 数据集,该数据集包含一组复杂的问答对,通常以多跳方式回答。


from dspy.datasets import HotPotQA

<p># 加载数据集
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)</p>

<p># 指定“问题”字段作为输入
trainset = [x.with_inputs('question') for x in dataset.train]
devset = [x.with_inputs('question') for x in dataset.dev]</p>

步骤 3:构建签名

DSPy 使用签名来定义模块的行为。在这个示例中,我们将为答案生成任务定义一个签名,指定输入字段(上下文和问题)和输出字段(答案)。


<p>class GenerateAnswer(dspy.Signature):
"""使用简短的事实答案回答问题。"""

context = dspy.InputField(desc="可能包含相关事实")
question = dspy.InputField()
answer = dspy.OutputField(desc="通常在 1 到 5 个词之间")

步骤 4:构建管道

我们将构建我们的 RAG 管道作为一个 DSPy 模块,它由一个初始化方法(__init__)组成,用于声明子模块(dspy.Retrieve 和 dspy.ChainOfThought),以及一个前向方法(forward)来描述使用这些模块回答问题的控制流程。


<p>class RAG(dspy.Module):
def __init__(self, num_passages=3):
super().__init__()

self.retrieve = dspy.Retrieve(k=num_passages)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)

def forward(self, question):
context = self.retrieve(question).passages
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(context=context, answer=prediction.answer)

步骤 5:优化管道

有了管道的定义,我们现在可以使用 DSPy 的优化器来优化它。在这个示例中,我们将使用 BootstrapFewShot 优化器,它根据训练集和验证指标生成和选择有效的提示。


<p>from dspy.teleprompt import BootstrapFewShot</p>

<p># 验证指标
def validate_context_and_answer(example, pred, trace=None):
answer_EM = dspy.evaluate.answer_exact_match(example, pred)
answer_PM = dspy.evaluate.answer_passage_match(example, pred)
return answer_EM and answer_PM

<p># 设置优化器
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)

<p># 编译程序
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)

步骤 6:评估管道

在编译程序后,评估其在开发集上的性能以确保它达到所需的准确性和可靠性至关重要。


from dspy.evaluate import Evaluate

<p># 设置评估器
evaluate = Evaluate(devset=devset, metric=validate_context_and_answer, num_threads=4, display_progress=True, display_table=0)

<p># 评估编译的 RAG 程序
evaluation_result = evaluate(compiled_rag)

<p>print(f"评估结果:{evaluation_result}")

步骤 7:检查模型历史

为了更深入地了解模型的交互,您可以通过检查模型的历史来查看最近的生成。


<p># 检查模型的历史
turbo.inspect_history(n=1)

步骤 8:进行预测

有了管道的优化和评估,您现在可以使用它来进行新问题的预测。


<p># 示例问题
question = "Gary Zukav 的第一本书获得了什么奖项?"

<p># 使用编译的 RAG 程序进行预测
prediction = compiled_rag(question)

<p>print(f"问题:{question}")
print(f"答案:{prediction.answer}")
print(f"检索的上下文:{prediction.context}")

使用 DSPy 的最小工作示例

现在,让我们一步一步地构建另一个使用 GSM8K 数据集 和 OpenAI GPT-3.5-turbo 模型来模拟 DSPy 中的提示任务的最小工作示例。

设置

首先,确保您的环境配置正确:


<p>import dspy
from dspy.datasets.gsm8k import GSM8K, gsm8k_metric</p>

<p># 设置 LM
turbo = dspy.OpenAI(model='gpt-3.5-turbo-instruct', max_tokens=250)
dspy.settings.configure(lm=turbo)

<p># 加载 GSM8K 数据集中的数学问题
gsm8k = GSM8K()
gsm8k_trainset, gsm8k_devset = gsm8k.train[:10], gsm8k.dev[:10]

print(gsm8k_trainset)

定义模块

接下来,定义一个使用 ChainOfThought 模块进行步骤式推理的自定义程序:


<p>class CoT(dspy.Module):
def __init__(self):
super().__init__()
self.prog = dspy.ChainOfThought("问题 -&gt; 答案")

def forward(self, question):
return self.prog(question=question)

编译和评估模型

现在,使用 BootstrapFewShot 优化器编译它:


<p>from dspy.teleprompt import BootstrapFewShot</p>

<p># 设置优化器
config = dict(max_bootstrapped_demos=4, max_labeled_demos=4)

<p># 优化使用 gsm8k_metric
teleprompter = BootstrapFewShot(metric=gsm8k_metric, **config)
optimized_cot = teleprompter.compile(CoT(), trainset=gsm8k_trainset)

<p># 设置评估器
from dspy.evaluate import Evaluate

<p>evaluate = Evaluate(devset=gsm8k_devset, metric=gsm8k_metric, num_threads=4, display_progress=True, display_table=0)
evaluate(optimized_cot)

<p># 检查模型的历史
turbo.inspect_history(n=1)

这个示例演示了如何设置环境,定义自定义模块,编译模型,并使用提供的数据集和优化器配置严格评估其性能。

DSPy 中的数据管理

DSPy 操作训练、开发和测试集。对于数据中的每个示例,您通常有三种类型的值:输入、中间标签和最终标签。虽然中间或最终标签是可选的,但拥有几个示例输入是必不可少的。

创建示例对象

DSPy 中的示例对象类似于 Python 字典,但带有有用的实用程序:


<p>qa_pair = dspy.Example(question="这是一个问题?", answer="这是一个答案。")

<p>print(qa_pair)
print(qa_pair.question)
print(qa_pair.answer)

输出:


<p>Example({'question': '这是一个问题?', 'answer': '这是一个答案。'}) (input_keys=None)
这是一个问题?
这是一个答案。

指定输入键

在 DSPy 中,示例对象具有 with_inputs() 方法来标记特定字段为输入:


<p>print(qa_pair.with_inputs("question"))
print(qa_pair.with_inputs("question", "answer"))

值可以使用点运算符访问,方法如 inputs() 和 labels() 返回仅包含输入或非输入键的新示例对象。

DSPy 中的优化器

DSPy 优化器调整 DSPy 程序的参数(即提示和/或 LM 权重)以最大化指定的指标。DSPy 提供各种内置优化器,每个优化器都使用不同的策略。

可用的优化器

  • BootstrapFewShot:使用提供的标记输入和输出数据点生成少量示例。
  • BootstrapFewShotWithRandomSearch:多次应用 BootstrapFewShot,使用生成的示例进行随机搜索。
  • COPRO:生成和完善每个步骤的新指令,使用坐标上升法进行优化。
  • MIPRO:使用贝叶斯优化来优化指令和少量示例。

选择优化器

如果您不确定从哪里开始,请使用 BootstrapFewShotWithRandomSearch:

对于非常少的数据(10 个示例),使用 BootstrapFewShot。
对于稍多的数据(50 个示例),使用 BootstrapFewShotWithRandomSearch。
对于更大的数据集(300+ 个示例),使用 MIPRO。

以下是如何使用 BootstrapFewShotWithRandomSearch:


<p>from dspy.teleprompt import BootstrapFewShotWithRandomSearch</p>

<p>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)

保存和加载优化程序

在运行程序通过优化器之后,保存它以备将来使用:

optimized_program.save(YOUR_SAVE_PATH)

加载已保存的程序:


<p>loaded_program = YOUR_PROGRAM_CLASS()
loaded_program.load(path=YOUR_SAVE_PATH)

高级功能:DSPy 断言

DSPy 断言自动执行 LM 的计算约束,增强 LM 输出的可靠性、可预测性和正确性。

使用断言

定义验证函数并在模型生成后声明断言。例如:


<p>dspy.Suggest(
len(query) &lt;= 100,
&quot;查询应该短小,少于 100 个字符&quot;,
)

<p>dspy.Suggest(
validate_query_distinction_local(prev_queries, query),
"查询应该与以下内容不同:" + "; ".join(f"{i+1}) {q}" for i, q in enumerate(prev_queries)),
)

转换程序以使用断言


<p>from dspy.primitives.assertions import assert_transform_module, backtrack_handler</p>

<p>baleen_with_assertions = assert_transform_module(SimplifiedBaleenAssertions(), backtrack_handler)

<p>或者,在程序上直接激活断言:</p>

[code language="Python"]

<p>baleen_with_assertions = SimplifiedBaleenAssertions().activate_assertions()

断言驱动优化

DSPy 断言与 DSPy 优化器(特别是 BootstrapFewShotWithRandomSearch)配合使用,包括以下设置:

  • 带有断言的编译
  • 带有断言的编译和推理

结论

DSPy 提供了一种强大且系统化的方法来优化语言模型和其提示。通过遵循这些示例中概述的步骤,您可以轻松地构建、优化和评估复杂的 AI 系统。DSPy 的模块化设计和高级优化器允许高效地集成各种语言模型,使其成为任何从事 NLP 和 AI 领域的人的宝贵工具。

无论您是构建一个简单的问答系统还是一个更复杂的管道,DSPy 都提供了必要的灵活性和可靠性来实现高性能和可靠性。

我已经沉浸在了令人着迷的机器学习和深度学习世界中五年了。我的热情和专业知识让我为超过50个不同的软件工程项目做出了贡献,特别关注AI/ML。我的持续的好奇心也让我对自然语言处理产生了兴趣,这是一个我渴望进一步探索的领域。