プロンプトエンジニアリング

DSPyを使用したLLMの最適化: AIシステムの構築、最適化、評価のためのステップバイステップガイド

mm
Unite.AI を Google の優先ソースに追加
DSPy is a framework for algorithmically optimizing LM prompts and weights

大規模言語モデル(LLM)の能力が拡大するにつれて、LLMの潜在能力を活用した強固なAIシステムの開発は、ますます複雑になっている。従来のアプローチでは、複雑なプロンプティング技術、ファインチューニングのためのデータ生成、およびドメイン固有の制約に従うための手動ガイダンスが必要になることが多い。しかし、このプロセスは面倒で、エラーが発生しやすく、人間の介入に大きく依存することがある。

ここで、DSPyという革命的なフレームワークが登場する。DSPyは、LLMを活用したAIシステムの開発を簡素化するために設計されたもので、LMプロンプトと重みの最適化に体系的なアプローチを提供する。

この包括的なガイドでは、DSPyの基本原理、モジュラーなアーキテクチャ、および提供される強力な機能について探る。また、DSPyを使用してLLMを活用したAIシステムの開発方法を実践的に示す例も紹介する。

DSPyとは何か、それをなぜ必要とするのか?

DSPyは、プログラムのフロー(モジュール)と各ステップのパラメータ(LMプロンプトと重み)を分離するフレームワークである。この分離により、LMプロンプトと重みの体系的な最適化が可能になり、ドメイン固有の制約に従った複雑なAIシステムをより信頼性が高く、予測可能に構築できるようになる。

従来、LLMを使用したAIシステムの開発には、問題をステップに分解し、各ステップのための複雑なプロンプトを作成し、ファインチューニングのための合成例を生成し、LLMを特定の制約に従うように手動でガイドするという面倒なプロセスが必要だった。このアプローチは時間がかかり、エラーが発生しやすく、パイプライン、LM、またはデータのわずかな変更でも、プロンプトとファインチューニングのステップの大量な再作業を必要とすることがあった。

DSPyは、これらの課題に対処するために、最適化器という新しいパラダイムを導入する。LM駆動のアルゴリズムは、指定されたメトリックを最大化するために、LM呼び出しのプロンプトと重みを調整できる。最適化プロセスを自動化することで、DSPyは、人間の介入を最小限に抑えながら、強固なAIシステムを構築できるように開発者を支援する。

DSPyのモジュラーなアーキテクチャ

DSPyの核となるのは、複雑なAIシステムの構成を容易にするモジュラーなアーキテクチャである。フレームワークは、dspy.ChainOfThoughtdspy.ReActのような、さまざまなプロンプティング技術を抽象化した一連の組み込みモジュールを提供する。これらのモジュールは、より大きなプログラムに組み合わせて構成できるため、開発者は特定の要件に合わせた複雑なパイプラインを構築できるようになる。

各モジュールには、インストラクション、ファインショットの例、LMの重みなどの学習可能なパラメータが含まれている。モジュールが呼び出されると、DSPyの最適化器は、これらのパラメータをファインチューニングして、指定されたメトリックを最大化し、LLMの出力が指定された制約と要件に従うようにすることができる。

DSPyを使用した最適化

DSPyは、AIシステムのパフォーマンスと信頼性を向上させるために、強力な最適化器を提供する。LM駆動のアルゴリズムは、LM呼び出しのプロンプトと重みを調整して、指定されたメトリックを最大化し、ドメイン固有の制約に従う。

DSPyで使用できる最適化器の例としては、次のものがある。

  1. BootstrapFewShot:この最適化器は、提供されたラベル付き入力と出力データポイントを使用して、ファインショットの例を生成する。
  2. BootstrapFewShotWithRandomSearchBootstrapFewShotを複数回、ランダムな検索で生成されたデモンストレーションを使用して適用する。
  3. MIPRO:各ステップで、インストラクションとファインショットの例を生成し、ベイジアン最適化を使用して生成のインストラクションとデモンストレーションの空間を効率的に検索する。
  4. BootstrapFinetune:プロンプトベースのDSPyプログラムを、より小さいLMの重み更新に抽出することで、基礎となるLLMをファインチューニングすることができる。

これらの最適化器を使用することで、開発者はAIシステムを体系的に最適化し、高品質の出力を保証し、ドメイン固有の制約と要件に従うことができる。

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># 入力として'question'フィールドを指定する
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語の間')</p>

ステップ4:パイプラインの構築

ここでは、RAGパイプラインをDSPyモジュールとして構築する。モジュールには、サブモジュール(dspy.Retrieveとdspy.ChainOfThought)を宣言するための初期化メソッド(__init__)と、質問に回答するためのコントロールフローを記述するためのforwardメソッド(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)</p>

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

ステップ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>

<p># 最適化器を設定する
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)</p>

<p># プログラムをコンパイルする
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)</p>

ステップ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>

<p># コンパイルされたRAGプログラムを評価する
evaluation_result = evaluate(compiled_rag)</p>

<p>print(f"評価結果: {evaluation_result}")</p>

ステップ7:モデル履歴の検査

モデルとのやり取りをより深く理解するために、モデル履歴の最新の生成を検査することができる。


<p># モデル履歴を検査する
turbo.inspect_history(n=1)</p>

ステップ8:予測の実行

パイプラインを最適化して評価したら、新しい質問に対する予測を実行することができる。


<p># 予測の実行例
question = "Gary Zukavの最初の本はどの賞を受賞したか?"</p>

<p># コンパイルされたRAGプログラムを使用して予測を実行する
prediction = compiled_rag(question)</p>

<p>print(f"質問: {question}")
print(f"回答: {prediction.answer}")
print(f"リトリーバルコンテキスト: {prediction.context}")</p>

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>

<p># GSM8Kデータセットから数学の質問を読み込む
gsm8k = GSM8K()
gsm8k_trainset, gsm8k_devset = gsm8k.train[:10], gsm8k.dev[:10]</p>

print(gsm8k_trainset)

モジュールの定義

次に、ChainOfThoughtモジュールを使用したステップバイステップの推論のためのカスタムプログラムを定義する。


<p>class CoT(dspy.Module):
def __init__(self):
super().__init__()
self.prog = dspy.ChainOfThought("question -&gt; answer")</p>

<p>def forward(self, question):
return self.prog(question=question)</p>

モデルをコンパイルして評価する

ここでは、BootstrapFewShotテレプロンプターを使用してモデルをコンパイルする。


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

<p># 最適化器を設定する
config = dict(max_bootstrapped_demos=4, max_labeled_demos=4)</p>

<p># gsm8kメトリックを使用して最適化する
teleprompter = BootstrapFewShot(metric=gsm8k_metric, **config)
optimized_cot = teleprompter.compile(CoT(), trainset=gsm8k_trainset)</p>

<p># 評価器を設定する
from dspy.evaluate import Evaluate</p>

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

<p># モデル履歴を検査する
turbo.inspect_history(n=1)</p>

この例は、環境を設定し、カスタムモジュールを定義し、モデルをコンパイルして評価する方法を示す。

DSPyでのデータ管理

DSPyでは、トレーニング、開発、テストセットを使用する。各データの例には、入力、中間ラベル、最終ラベルの3種類の値がある。中間または最終ラベルはオプションだが、少なくとも1つの例の入力が必要である。

例の作成

DSPyの例は、Pythonの辞書書きに似ているが、便利なユーティリティが付属する。


<p>qa_pair = dspy.Example(question="これは質問ですか?", answer="これは回答です。")</p>

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

出力:


<p>Example({'question': 'これは質問ですか?', 'answer': 'これは回答です。'}) (input_keys=None)
これは質問ですか?
これは回答です。</p>

入力キーの指定

DSPyのExampleオブジェクトには、特定のフィールドを入力としてマークするためのwith_inputs()メソッドがある。


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

値はドット演算子を使用してアクセスでき、inputs()やlabels()のようなメソッドは、入力または非入力キーのみを含む新しいExampleオブジェクトを返す。

DSPyの最適化器

DSPyの最適化器は、DSPyプログラムのパラメータ(プロンプトと/またはLMの重み)を、指定されたメトリックを最大化するために調整する。DSPyには、さまざまな戦略を使用する組み込みの最適化器が用意されている。

使用可能な最適化器

  • BootstrapFewShot:提供されたラベル付き入力と出力データポイントを使用して、ファインショットの例を生成する。
  • BootstrapFewShotWithRandomSearchBootstrapFewShotを複数回、ランダムな検索で生成されたデモンストレーションを使用して適用する。
  • 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)</p>

最適化されたプログラムの保存と読み込み

最適化されたプログラムを将来使用するために保存することができる。

optimized_program.save(YOUR_SAVE_PATH)

保存されたプログラムを読み込む。


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

高度な機能:DSPyのアサーション

DSPyのアサーションは、LLMの計算上の制約を自動的に適用することで、LLMの出力の信頼性、予測可能性、正確性を向上させる。

アサーションの使用

バリデーション関数を定義し、モデルの生成後にアサーションを宣言する。


<p>dspy.Suggest(
len(query) &lt;= 100,
&quot;クエリは短く、100文字以下である必要があります&quot;,
)</p>

<p>dspy.Suggest(
validate_query_distinction_local(prev_queries, query),
"クエリは次のものと異なる必要があります: " + "; ".join(f"{i+1}) {q}" for i, q in enumerate(prev_queries)),
)</p>

プログラムの変換


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

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

または、プログラムに直接アサーションを適用する。


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

アサーション駆動の最適化

DSPyのアサーションは、特にBootstrapFewShotWithRandomSearchを使用した、DSPyの最適化と連携する。

  • アサーションを使用したコンパイル
  • アサーションを使用したコンパイルと推論

結論

DSPyは、LLMとそのプロンプトの最適化に体系的なアプローチを提供する強力なツールである。ここで紹介した例に従うことで、LLMを使用したAIシステムを構築、最適化、評価することができる。DSPyのモジュラーな設計と高度な最適化器により、さまざまなLLMを効率的に統合し、高性能と信頼性を実現することができる。

質問回答システムの構築から複雑なパイプラインの構築まで、DSPyは柔軟性と堅牢性を提供するため、NLPとAIの分野で働くすべての人の貴重なツールとなっている。

私は過去5年間、機械学習とディープラーニングの魅力的世界に没頭してきました。私の情熱と専門知識は、AI/MLに特に焦点を当てた50以上の多様なソフトウェアエンジニアリングプロジェクトに貢献することになりました。私の継続的な好奇心は、自然言語処理という分野にも私を引き付け、さらに探求したいと思っています。