3 DSPy 中的评估(Evaluation)
3.1 概述
有了初步的系统,下一步就是收集开发数据集,以便能更系统地对系统进行优化。建议数据集不低于20个(200个效果会好些)。根据开发的系统的评估指标不同,数据集可能只需要输入数据,不需要标签。也可能输入数据和最终输出都需要。
提示:可以在HuggingFace数据集或像StackExchange这样的找到与你任务相似的数据集。条件允许,建议使用这些数据。当然,你可以手动标注一些示例。
接下来,定义DSPy指标。什么样的输出是好的还是坏的。这需要投入精力来定义这些指标,并逐步优化;不然就很难持续进行改进。指标也是一个函数,它获取从数据输入,并根据输出的结果,返回一个评分。对于简单的任务,这评分可能是“准确率”,例如简单的分类或短问答任务等。对于大多数应用,系统会生成很长的输出,因此你的指标将是一个较小的DSPy程序,用于检查输出的多种属性。
有开发数据和DSPy指标函数,就可以运行评估来权衡你设计的流程控制。反复的查看输出和指标评分,来设定一个基线。
3.2 DSPy的数据对象
DSPy 是一个机器学习框架,其在工作通常用到训练集、开发集和测试集,每个示例(Example)是数据集最小组成单位。示例内部通常可以分为三类:输入、中间标签或者最终标签。示例可以没有中间标签或最终标签,但至少需要输入。接下来是我们要介绍的DSPy数据集示例:Example。
3.2.1 DSPy的 Example 对象
DSPy中的核心数据类型是Example。DSPy使用Example 来表示训练集和测试集中的项目。
Example类似于Python dict类型 ,但它可以包含一些工具。DSPy 模块返回 类型的值Prediction,是 Example 的一个特殊子类。
:
qa_pair = dspy.Example(question="This is a question?", answer="This is an answer.")
print(qa_pair)
print(qa_pair.question)
print(qa_pair.answer)
日志打印
Example({'question': 'This is a question?', 'answer': 'This is an answer.'}) (input_keys=None)
This is a question?
This is an answer.
Example的参数可以是任何字段键(Keys )和值类型(value),默认是字符串,单个Example如下:
object = Example(field1=value1, field2=value2, field3=value3, ...)
训练集是多个Example列表(list),如下:
trainset = [dspy.Example(report="LONG REPORT 1", summary="short summary 1"), ...]
3.2.2 Example 键的指定及访问
在传统的机器学习(Machine Learning, ML)中,数据通常被分为两部分:
- 输入(Input):也称为特征(Features),是模型用来学习和预测的数据。例如,在图像分类任务中,输入可能是图像的像素值;在房价预测任务中,输入可能是房屋的面积、位置等信息。
- 标签(Label):也称为目标(Target)或输出(Output),是模型需要预测的值。例如,在图像分类任务中,标签可能是图像的类别(如“猫”或“狗”);在房价预测任务中,标签可能是房屋的价格。
在 DSPy 中, Example 对象可以使用with_inputs()方法某个字段作为收入键input。(不标记则默认为元数据或标签键label)
# 单个输入,标记question为输入标签
print(qa_pair.with_inputs("question"))
# 多个输入;注意这里的answer也是输入
print(qa_pair.with_inputs("question", "answer"))
Example 对象可以通过. (点) 运算符来访问键的值。如:Example(name="John Doe", job="sleep"),可以通过object.name来访问name键的值。
也可以使用 inputs() 和 labels()方法,访问所有输入和标签的值如下:
# article已标记为inputs,summary未标记则默认为labels
article_summary = dspy.Example(article= "This is an article.", summary= "This is a summary.").with_inputs("article")
input_key_only = article_summary.inputs()
non_input_key_only = article_summary.labels()
print("Example object with Input fields only:", input_key_only)
print("Example object with Non-Input fields only:", non_input_key_only)
日志打印
Example object with Input fields only: Example({'article': 'This is an article.'}) (input_keys=None)
Example object with Non-Input fields only: Example({'summary': 'This is a summary.'}) (input_keys=None)
3.3 Metrics 指标
DSPy 是一个机器学习框架,因此需要考虑用于评估的自动指标(以跟踪进展)和用于优化的自动指标(以便 DSPy 可以使程序更有效)
3.3.1 指标概述
指标只是一个函数,它通过数据集的输入、获取系统输出,及返回评分。来衡量系统的输出效果是好是坏。
于简单的任务,如简单的分类或简短形式的问答任等,可以使用“准确率”或“精确匹配”或“F1 分数”来作为指标。
然而,对于大多数应用来说,需要输出很长的内容。那么,指标应该是一个较小的DSPy程序,用于检查输出的多个属性(很可能使用来自LM的AI反馈)。一次做到完美不太可能,应该从简单的东西开始,然后逐步改进。
3.3.2 简单的指标
DSPy 指标是一个 Python 函数, 它传入的参数主要是数据集example(训练集或开发) 和DSPy 程序的输出pred,返回评估的结果 float分数(int 或 bool )。
指标还可以传入第三个参数 trace[可选],可以暂时忽略这个参数,在以后得高阶技能,这个参数非常重要。
下面是一个简单的示例,指标是比较 example.answer 和 pred.answer 是否相对,返回一个 bool 值。
def validate_answer(example, pred, trace=None):
return example.answer.lower() == pred.answer.lower()
上面的例子也可以改用DSPy内置的工具进行比较:
- dspy.evaluate.metrics.answer_exact_match
上例的指标可以改成的复杂点,例如检查多个属性。如下:
- 当用于评估或优化时,返回一个浮点数;
- 当用于启动示范时,返回一个布尔值。
def validate_context_and_answer(example, pred, trace=None):
# check the gold label and the predicted answer are the same
answer_match = example.answer.lower() == pred.answer.lower()
# check the predicted answer comes from one of the retrieved contexts
context_match = any((pred.answer.lower() in c) for c in pred.context)
if trace is None: # if we're doing evaluation or optimization
return (answer_match + context_match) / 2.0
else: # if we're doing bootstrapping, i.e. self-generating good demonstrations of each step
return answer_match and context_match
提示:定义一个好的指标函数是一个迭代过程,进行初步评估并查看数据和输出是关键。
3.3.3 Evaluation 评估指标
有了指标函数,现在可以进行循环评估了。
scores = []
for x in devset:
pred = program(**x.inputs())
score = metric(x, pred)
scores.append(score)
Evaluate 内置了一些工具,可以帮助进行并行评估(多线程)或显示输入/输出样本以及指标评分。
from dspy.evaluate import Evaluate
# Set up the evaluator, which can be re-used in your code.
evaluator = Evaluate(devset=YOUR_DEVSET, num_threads=1, display_progress=True, display_table=5)
# Launch evaluation.
evaluator(YOUR_PROGRAM, metric=YOUR_METRIC)
3.3.4 中级:使用AI反馈优化指标
对于大多数应用,系统会输出长格式的内容,因此指标应该使用来自语言模型(LM)的AI反馈来检查输出的多个维度。下面是一个Signature的简单例子:
# Define the signature for automatic assessments.
class Assess(dspy.Signature):
"""Assess the quality of a tweet along the specified dimension."""
assessed_text = dspy.InputField()
assessment_question = dspy.InputField()
assessment_answer: bool = dspy.OutputField()
例如,下面是一个简单的指标,用于检查生成的推文.评估维度为:
- 是否正确回答了给定的问题;
- 是否具有吸引力;
- len(tweet) <= 280 个字符。
def metric(gold, pred, trace=None):
question, answer, tweet = gold.question, gold.answer, pred.output
engaging = "Does the assessed text make for a self-contained, engaging tweet?"
correct = f"The text should answer `{question}` with `{answer}`. Does the assessed text contain this answer?"
correct = dspy.Predict(Assess)(assessed_text=tweet, assessment_question=correct)
engaging = dspy.Predict(Assess)(assessed_text=tweet, assessment_question=engaging)
correct, engaging = [m.assessment_answer for m in [correct, engaging]]
score = (correct + engaging) if correct and (len(tweet) <= 280 else 0 if trace is not none: return score>= 2
return score / 2.0
当 trace is not None 为 True 时,返回 score >= 2 。否则,将返回一个介于 0 到 1 之间的分数(即, score / 2.0 )。
3.3.5 高级:使用 DSPy 程序作为指标
如果你的评估指标本身是一个 DSPy 程序(优化器optimize),它的迭代方法是编译这个评估指标本身,只需要通过收集几个例子即可。
高级:访问 trace
当你的指标在评估运行中被使用时,DSPy 不会尝试跟踪你程序的步骤。
但在编译(优化器optimize)过程中,DSPy 会跟踪你的 LM 调用。跟踪信息将包含每个 DSPy 预测器的输入/输出,你可以利用这些信息验证优化过程中的中间步骤。
def validate_hops(example, pred, trace=None):
hops = [example.question] + [outputs.query for *_, outputs in trace if 'query' in outputs]
if max([len(h) for h in hops]) > 100: return False
if any(dspy.evaluate.answer_exact_match_str(hops[idx], hops[:idx], frac=0.8) for idx in range(2, len(hops))): return False
return True