Optimization Overview - DSPy以降のセクション[2025/1/15時点]の翻訳です。
本書は著者が手動で翻訳したものであり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
[翻訳] DSPyを学ぶのコンテンツです。
最適化の概要
システムとそれを評価する手段を手に入れたら、あなたのプログラムのプロンプトと重みをチューニングするためにDSPyオプティマイザを活用することができます。探索で活用した開発セットに加えて、トレーニングセットと別に分けておくテストセットの構築に、あなたのデータ収集の工数を拡張することが有用になります。トレーニングセット(とそのサブセットの検証セット)に関しては、多くの場合30のサンプルから大きな価値を得ることができますが、少なくとも300サンプルを狙いましょう。いくつかのオプティマイザはtrainset
のみを受け付けます。他のものはtrainset
とvalset
を必要とします。プロンプトのオプティマイザに対しては、DNNで行うのと逆となるトレーニングの20%スプリット、検証の80%からスタートすることをお勧めします。
はじめてのいくつかの最適化ランの後では、全てに対してハッピーになっていたり、大きな進捗を示していますが、最終的なプログラムやメトリックに関しては気に入らないかもしれません。この時点でステップ1(DSPyにおけるプログラミング)に戻り、主要な質問に再訪します。自分のタスクを適切に定義しましたか?あなたの問題に対してさらなるデータを収集する(あるいはオンラインで見つける)必要がありますか?メトリックを更新したいですか?より洗練されたオプティマイザを使いたいと考えますか?DSPyアサーションのような高度な機能を検討する必要がありますか?あるいは、おそらく最も重要なことですが、あなたのDSPyプログラム自身により多くの複雑性やステップを追加したいと考えますじゃ?順番に複数のオプティマイザを使いたいですか?
イテレーティブな開発が鍵となります。DSPyはインクリメンタルにそれを行うためのピースを提供します: データ、プログラムの構造、アサーション、メトリック、最適化ステップに対してイテレーションを行います。複雑なLMプログラムの最適化は、本書の執筆時点ではDSPyにのみ存在している(アップデート: 今では数多くのDSPy拡張フレームワークが存在しているので、この部分はすでに真ではなくなっています :-)完全に新たなパラダイムであり、すべきことに関する規範は現在も自然発生している状況です。助けが必要であれば、最近我々はコミュニティのためのDiscordサーバーを作成しました。
オプティマイザ
DSPyオプティマイザは、精度のようにあなたが指定したメトリクスを最大化するように、DSPyプログラムのパラメーター(プロンプトやLMの重み)をチューニングすることができるアルゴリズムです。
典型的なDPSyオプティマイザは3つのものを受け取ります:
- あなたのDSPyプログラム。これは、単一のモジュール(
dspy.Predict
など)だったり、複雑なマルチモジュールプログラムだったりします。 - あなたのメトリック。これは、あなたのプログラムの出力を評価し、スコア(高い程よい)を割り当てる関数です。
- いくつかのトレーニング入力。これは非常に小規模(5や10のサンプルなど)で不完全なもの(ラベルなしのプログラムへの入力のみ)である場合があります。
膨大なデータがある場合、DSPyはそれらを活用することができます。しかし、小規模からスタートして、強い結果を得ることができます。
注意
これは以前はテレプロンプターと呼ばれていました。公式な名称を更新し、これらはライブラリやドキュメントに反映されます。
DSPyオプティマイザは何をチューニングするのか?どのようにチューニングするのか?
DSPyにおける様々なオプティマイザは、dspy.BootstrapRS
1のようなすべてのモジュールに対して優れたfew-shotサンプルを合成し、dspy.MIPROv2
2のようにすべてのプロンプトに対して優れた自然言語の指示を提案、インテリジェントに探索し、dspy.BootstrapFinetune
3のようにモジュールに対するデータセットを構築し、あなたのシステムにおけるLMの重みをファインチューンするためにそれらのデータセットを活用することで、あなたのプログラムの品質をチューニングします。
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 *
経由でオプティマイザにアクセスすることができます。
Few-Shotの自動学習
これらのオプティマイザは自動で、最適化されたサンプルを生成し、モデルに送信されるプロンプトに含め、few-shot学習を実装することで、シグネチャを拡張します。
-
LabeledFewShot
: シンプルに指定されたラベル付きの入力と出力のデータポイントからfew-shotのサンプル(デモ)を構成します。ランダムにk
個の例を選択するために、k
(プロンプトに含める例の数)とtrainset
を必要とします。 -
BootstrapFewShot
: あなたのプログラムのすべてのステージに対する完全なデモンストレーションを生成するために、(あなたのプログラムのデフォルトとなる)teacher
モジュールとtrainset
のラベル付きのサンプルを使用します。パラメーターには、max_labeled_demos
(trainset
からランダムに選択されるデモンストレーションの数)、max_bootstrapped_demos
(teacher
によって生成される追加の例の数)が含まれます。このブートストラッピングプロセスは、「コンパイルされた」プロンプトのメトリックのみを引き渡すなど、デモンストレーションの検証にメトリックを活用します。上級: より困難なタスクにおいては、互換性のある構造を持つ別のDSPyプログラムである
teacher
プログラムの使用をサポートしています。 -
BootstrapFewShotWithRandomSearch
: 生成されたデモンストレーションに対してランダムサーチを行い、複数回BootstrapFewShot
を適用し、最適化においてベストなプログラムを選択します。パラメーターはBootstrapFewShot
と同じであり、コンパイルされれていないプログラム候補、LabeledFewShot
で最適化されたプログラム、シャッフルされないサンプルを用いてBootstrapFewShot
でコンパイルされたプログラム、ランダマイズされたサンプルセットを用いてBootstrapFewShot
を用いてコンパイルされたnum_candidate_programs
個のプログラムを含む、最適化の過程で評価されるランダムのプログラム数を指定するnum_candidate_programs
があります。 -
KNNFewShot
: 指定された入力サンプルに対して最も近いトレーニングサンプルを特定するためにk-最近傍アルゴリズムを使用します。そして、これらの最近傍のデモンストレーションは、BootstrapFewShotの最適化プロセスのtrainsetとして使用されます。例に関しては、こちらのノートブックをご覧ください。
指示の自動最適化
これらのオプティマイザは、プロンプトに最適な指示を生成し、MIPROv2の場合は一連のfew-shotのデモンストレーションも最適化することができます。
-
COPRO
: それぞれのステップにおいて新たな指示を生成、洗練し、coordinate ascent(メトリック関数とtrainset
を用いたヒルクライミング)を用いてそれらを最適化します。パラメータには、オプティマイザが実行するプロンプト改善のイテレーションの数であるdepth
が含まれます。 -
MIPROv2
: それぞれのステップの指示とfew-shotのサンプルを生成します。この指示はデータアウェアであり、デモンストレーションアウェアです。あなたのモジュールに対する指示/デモンストレーションの生成の空間における効果的な探索のために、ベイジアン最適化を用います。
自動ファインチューニング
このオプティマイザは、背後のLLMをファインチューニングするために使用されます。
-
BootstrapFinetune
: プロンプトベースのDSPyプログラムを重みの更新に変換します。出力は同じステップを持つDSPyプログラムですが、LMにプロンプトを提示するのではなく、モデルをファインチューニングすることでそれぞれのステップが実行されます。
プログラム変換
-
Ensemble
: 一連のDSPyプログラムをアンサンブルし、フルセットを使うか、ランダムにサブセットを単一のプログラムにサンプリングします。
どのオプティマイザを使うべきか?
究極的には、使用すべき「適切な」オプティマイザや、タスクにベストな設定の特定には実験が必要となります。DSPyにおける成功は依然として繰り返しのプロセスです - あなたのタスクでベストなパフォーマンスを得るには、探索と繰り返しが必要となることでしょう。
とは言え、スタートする際の一般的なガイドがこちらとなります:
-
サンプルの数が非常に少ない(約10)場合は、
BootstrapFewShot
でスタートしましょう。 -
より多くのデータ(50サンプル以上)がある場合には、
BootstrapFewShotWithRandomSearch
をトライしましょう。 -
指示の最適化のみを行いたい場合(プロンプトを0-shotのままにしたい場合)には、最適化のために0-shot最適化が設定された
MIPROv2
を使いましょう。 -
より長時間の最適化ラン(40トライアル以上など)を実行するために、より多くの推論コールを使いたい場合で、十分なデータがある場合(過学習を防ぐために200サンプル以上がある)には
MIPROv2
を試しましょう。 - 大規模なLM(7Bパラメーター以上)を用いて、これらのいずれかを活用できる場合において、非常に効率的なプログラムを必要としている場合には、
BootstrapFinetune
であなたのタスクに合わせて小規模なLMをファインチューニングしましょう。
どのようにオプティマイザを使うのか?
これら全てはこの一般的なインタフェースを共有し、キーワードの引数(ハイパーパラメータ)にある程度の違いがあります。キーとなるオプティマイザの詳細なドキュメントはこちらからアクセスでき、完全なリストはこちらにあります。
最も一般的なものである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)
スタートしてみる: DSPyプログラムにおけるLMプロンプトや重みの最適化
典型的なシンプルな最適化ランは$2 USD程度のコストが発生し、約10分の時間を要しますが、非常に大規模なLMやデータセットでオプティマイザを実行する際には注意してください。オプティマイザのコストは、お使いのLM、データセットや設定に応じて、数千とから数十ドルになり得ます。
原文にはいくつかのサンプルコードが記載されています。
ReActエージェントのプロンプトの最適化
これは、Wikipediaの検索を通じて質問に回答する最小限ですが完全に実行可能なdspy.ReAct
エージェントの例であり、HotPotQA
データセットからサンプリングした500個の質問-回答のペアにおいて安価なlight
モードでdspy.MIPROv2
を用いて最適化しています。
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%になりました。
オプティマイザの出力の保存とロード
オプティマイザを通じてプログラムを実行した後は、保存も行うことが有用です。後の時点で、ファイルからプログラムをロードし、推論に用いることができます。このためには、load
やsave
メソッドを使うことができます。
optimized_program.save(YOUR_SAVE_PATH)
結果として得られるファイルは、平文のJSONフォーマットです。これには、ソースプログラムのすべてのパラメーターとステップが含まれています。オプティマイザが生成したものを常に読んで確認することができます。optimized_program.save(YOUR_SAVE_PATH, save_field_meta=True)
によって、キーname
、field_type
、description
、prefix
を持つフィールドのリストを追加で保存するためにsave_field_meta
を追加することができます。
ファイルからプログラムをロードするには、そのクラスからオブジェクトをインスタンス化し、それに対してloadメソッドを呼び出すことができます。
loaded_program = YOUR_PROGRAM_CLASS()
loaded_program.load(path=YOUR_SAVE_PATH)