Evaluation Overview - DSPy以降のセクション[2025/1/15時点]の翻訳です。
本書は著者が手動で翻訳したものであり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
[翻訳] DSPyを学ぶのコンテンツです。
評価の概要
初期システムを手に入れたら、次はよりシステマティックに洗練できるように初期開発セットを収集する時間です。あなたのタスクにおいて、200の例を集めるのは時間を要しますが、20の入力例でも有用となりえます。あなたのメトリックに基づき、ラベルが全くない入力のみを必要とするか、入力とあなたのシステムの最終的な出力を必要とします。(DSPyにおけるプログラムの中間ステップにおいては、ほとんどラベルを必要とすることはありません。)取り組んでいるタスクに近しいデータセットを見つけ出すことができるでしょうし、HuggingFaceデータセットやStackExchangeのように自然発生しているソースを活用することもできます。ライセンス的に十分利用できるデータがある場合には、それらを使うことをお勧めします。そうでない場合には、手動でいくつかのサンプルにラベルをつけ、システムのデモのデプロイをスタートし、その途中で初期データを収集しましょう。
次に、あなたのDSPyメトリックを定義する必要があります。あなたのシステムの出力の良し悪しは何によって決定されますか?メトリクスの定義と時間をかけたインクリメンタルな改善に投資しましょう。定義できないものを継続的に改善することは困難です。メトリックとは、あなたのデータからサンプルと、あなたのシステムのアウトプットを受け取り、スコアを返す関数です。シンプルな分類や短い形態のQAタスクのようなシンプルなタスクにおいては、これは単なる「精度」となることがあります。多くのアプリケーションにおいては、長い形態の出力を生成するので、使うことになるメトリックは出力の複数のプロパティをチェックする小さなDSPyプログラムとなるでしょう。最初のトライからこれを適切に行うことはまず無いでしょう: シンプルな何かからスタートして繰り返しましょう。
いくつかのデータとメトリックを手に入れたので、これらのトレードオフを理解するために、あなたのパイプラインのデザインに対する開発評価を実行します。出力とメトリックのスコアを確認しましょう。おそらく、これによって主要な問題を特定することができ、次のステップにおけるベースラインを定義することになります。
お使いのメトリック自身がDSPyプログラムの場合は...
メトリック自身がDSPyプログラムの場合、イテレーションを行うパワフルな手段は、あなたのメトリック自身を最適化することです。メトリックの出力は通常シンプルな値なので、これは簡単なことであり、いくつかのサンプルを収集することで、簡単にメトリックのメトリックを定義し、最適化することができます。
データハンドリング
DSPyは機械学習フレームワークなので、取り扱いにはトレーニングセット、開発セット、テストセットが関係します。あなたのデータのそれぞれのサンプルに対して、通常3つのタイプの値を区別します: 入力、中間ラベル、そして最終的なラベルです。中間ラベルや最終的なラベルなしにDSPyを効果的に使うことはできますが、少なくともいくつかの入力サンプルは必要となります。
DSPyのExample
オブジェクト
DSPyにおけるデータのコアとなるデータタイプはExample
です。あなたのトレーニングセットとテストセットのアイテムを表現するためにExamplesを使うことになります。
DSPyのExamplesはPythonのdict
と似ていますが、いくつかの有用なユーティリティを提供しています。あなたのDSPyモジュールは、Example
の特殊なサブクラスであるPrediction
タイプの値を返却します。
DSPyを使う際、多くの評価ランや最適化ランを実行します。あなたの個々のデータポイントは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にはいくつかのフィールドキーと、多くは文字列型ですが値の型が含まれます。
object = Example(field1=value1, field2=value2, field3=value3, ...)
以下のようにトレーニングセットのサンプルを表現することができます:
trainset = [dspy.Example(report="LONG REPORT 1", summary="short summary 1"), ...]
入力キーの指定
従来のMLでは、分離された「入力」と「ラベル」があります。
DSPyでは、Example
オブジェクトには入力として特定のフィールドをマークできるwith_inputs()
メソッドがあります。
# Single Input.
print(qa_pair.with_inputs("question"))
# Multiple Inputs; be careful about marking your labels as inputs unless you mean it.
print(qa_pair.with_inputs("question", "answer"))
.
(ドット)オペレーターで値にアクセスできます。object.name
を通じて、定義されたオブジェクトExample(name="John Doe", job="sleep")
のキーであるname
の値にアクセスできます。
特定のキーにアクセスしたり除外するには、入力のみを含む新たなExampleオブジェクトを返却するinputs()
、入力を含まないキーのみを含むExampleオブジェクトを返却する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)
メトリクス
DSPyは機械学習フレームワークなので、(あなたの進捗を追跡するための)評価と(DSPyがあなたのプログラムをより効果的なものにする)最適化のための自動化メトリクスを考える必要があります。
メトリックとは何で、自分のタスクのメトリックをどのように定義するのか?
メトリックは、あなたのデータからのサンプルと、あなたのシステムの出力を受け取り、出力がどれだけ良かったかを定量化するスコアを返却する単なる関数です。あなたのシステムの出力が良いか悪いかはどのように決定されるのでしょうか?
シンプルなタスクにおいては、これは単なる「精度」、「正確一致」、「F1スコア」である場合があります。これは、シンプルな分類や短い形態のSAタスクで該当することがあります。
しかし、多くのアプリケーションにおいては、あなたのシステムは長い形態の出力を生成します。ここでは、あなたのメトリックは出力の複数のプロパティをチェック(LMからのAIフィードバックを用いる可能性が高いです)する小規模なDSPyプログラムとなることでしょう。
最初のトライでこれを適切に行うことはまずありませんが、シンプルなものからスタートして繰り返すべきです。
シンプルなメトリクス
DSPyのメトリックはexample
(トレーニングセットや開発セットなど)とあなたのDSPyプログラムの出力pred
を受け取り、float
(あるいはint
やbool
)のスコアを出力する単なるPythonの関数です。
また、あなたのメトリックはtrace
と呼ばれる3番目のオプションの引数を受けつ必要があります。この時点ではこれを無視しても構いませんが、最適化であなたのメトリックを使いたいと考えた際には、いくつかのパワフルなトリックを使えるようになります。
example.answer
とpred.answer
を比較するシンプルなメトリックの例を示します。この特定のメトリックはbool
を返却します。
def validate_answer(example, pred, trace=None):
return example.answer.lower() == pred.answer.lower()
何人かの方は、これらの(ビルトイン)ユーティリティが便利だと思うことでしょう:
dspy.evaluate.metrics.answer_exact_match
dspy.evaluate.metrics.answer_passage_match
複数のプロパティをチェックするなど、メトリックをさらに複雑にすることもできます。以下のメトリックは、trace is None
(評価や最適化で使用される場合)の場合にはfloat
を返却し、それ以外の場合(デモンストレーションのブートストラップで使用)にはbool
を返却します。
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
優れたメトリックを定義することは繰り返しのプロセスなので、いくつかの初期評価を行い、あなたのデータと出力に着目することが鍵となります。
評価
メトリックを手に入れたら、シンプルなPythonループで評価を実行することができます:
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)
中級: メトリックに対するAIのフィードバックの活用
多くのアプリケーションにおいては、あなたのシステムは長い形態の出力を生成するので、あなたのメトリックはLMからのAIフィードバックを用いて出力の複数の次元をチェックする必要があります。
このシンプルなシグネチャが役に立つことでしょう。
# 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()
例えば、以下のメトリックは生成されたツイートが(1)指定された質問に適切に回答しているのか、(2)興味深いものであるかどうかをチェックしています。また、(3)文字数が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
であり、事柄を判定することに関して厳密にしたいと考えるので、score >= 2
の場合にTrue
だけを返却します。それ以外の場合には、最大1.0のスコア(score / 2.0
)を返却します。
上級: メトリックとしてDSPyプログラムを使用
あなたのメトリック自体ががDSPyプログラムの場合、イテレーションを行う最もパワフルな手段は、あなたのメトリック自身をコンパイル(最適化)することです。メトリックの出力は通常はシンプルな値(最大5のスコアなど)なので、メトリックのメトリックを定義し、いくつかのサンプルを収集して最適化することは簡単です。
上級: trace
へのアクセス
評価ランの過程であなたのメトリックが使用される際、DSPyはあなたのプログラムのステップを追跡しようとはしません。
しかし、コンパイル(最適化)の際、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