[翻訳] DSPyにおける最適化

[Optimization Overview \- DSPy](https://dspy.ai/learn/optimization/overview/)以降のセクション[2025/1/15時点]の翻訳です。

:::note warn
本書は著者が手動で翻訳したものであり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

:::note info
[\[翻訳\] DSPyを学ぶ](https://qiita.com/taka_yayoi/items/df31bd8f1918855fd552)のコンテンツです。
:::

# 最適化の概要

システムとそれを評価する手段を手に入れたら、あなたのプログラムのプロンプトと重みをチューニングするためにDSPyオプティマイザを活用することができます。探索で活用した開発セットに加えて、トレーニングセットと別に分けておくテストセットの構築に、あなたのデータ収集の工数を拡張することが有用になります。トレーニングセット(とそのサブセットの検証セット)に関しては、多くの場合30のサンプルから大きな価値を得ることができますが、少なくとも300サンプルを狙いましょう。いくつかのオプティマイザは`trainset`のみを受け付けます。他のものは`trainset`と`valset`を必要とします。プロンプトのオプティマイザに対しては、DNNで行うのと*逆となる*トレーニングの20%スプリット、検証の80%からスタートすることをお勧めします。

はじめてのいくつかの最適化ランの後では、全てに対してハッピーになっていたり、大きな進捗を示していますが、最終的なプログラムやメトリックに関しては気に入らないかもしれません。この時点でステップ1(DSPyにおけるプログラミング)に戻り、主要な質問に再訪します。自分のタスクを適切に定義しましたか？あなたの問題に対してさらなるデータを収集する(あるいはオンラインで見つける)必要がありますか？メトリックを更新したいですか？より洗練されたオプティマイザを使いたいと考えますか？DSPyアサーションのような高度な機能を検討する必要がありますか？あるいは、おそらく最も重要なことですが、あなたのDSPyプログラム自身により多くの複雑性やステップを追加したいと考えますじゃ？順番に複数のオプティマイザを使いたいですか？

イテレーティブな開発が鍵となります。DSPyはインクリメンタルにそれを行うためのピースを提供します: データ、プログラムの構造、アサーション、メトリック、最適化ステップに対してイテレーションを行います。複雑なLMプログラムの最適化は、本書の執筆時点ではDSPyにのみ存在している(アップデート: 今では数多くのDSPy拡張フレームワークが存在しているので、この部分はすでに真ではなくなっています :-)完全に新たなパラダイムであり、すべきことに関する規範は現在も自然発生している状況です。助けが必要であれば、最近我々はコミュニティのための[Discordサーバー](https://discord.gg/XCGy2WDCQB)を作成しました。

# オプティマイザ

**DSPyオプティマイザ**は、精度のようにあなたが指定したメトリクスを最大化するように、DSPyプログラムのパラメーター(プロンプトやLMの重み)をチューニングすることができるアルゴリズムです。

典型的なDPSyオプティマイザは3つのものを受け取ります:

 - あなたの**DSPyプログラム**。これは、単一のモジュール(`dspy.Predict`など)だったり、複雑なマルチモジュールプログラムだったりします。
 - あなたの**メトリック**。これは、あなたのプログラムの出力を評価し、スコア(高い程よい)を割り当てる関数です。
 - いくつかの**トレーニング入力**。これは非常に小規模(5や10のサンプルなど)で不完全なもの(ラベルなしのプログラムへの入力のみ)である場合があります。

膨大なデータがある場合、DSPyはそれらを活用することができます。しかし、小規模からスタートして、強い結果を得ることができます。

:::note info
**注意**
これは以前はテレプロンプターと呼ばれていました。公式な名称を更新し、これらはライブラリやドキュメントに反映されます。
:::

## DSPyオプティマイザは何をチューニングするのか？どのようにチューニングするのか？

DSPyにおける様々なオプティマイザは、`dspy.BootstrapRS`[<sup>1</sup>](https://arxiv.org/abs/2310.03714)のようなすべてのモジュールに対して**優れたfew-shotサンプルを合成し**、`dspy.MIPROv2`[<sup>2</sup>](https://arxiv.org/abs/2406.11695)のようにすべてのプロンプトに対して**優れた自然言語の指示を提案、インテリジェントに探索し**、`dspy.BootstrapFinetune`[<sup>3</sup>](https://arxiv.org/abs/2407.10930)のようにモジュールに対する**データセットを構築し、あなたのシステムにおけるLMの重みをファインチューンするためにそれらのデータセットを活用**することで、あなたのプログラムの品質をチューニングします。

:::note info
**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学習を実装することで、シグネチャを拡張します。

1. **`LabeledFewShot`**: シンプルに指定されたラベル付きの入力と出力のデータポイントからfew-shotのサンプル(デモ)を構成します。ランダムに`k`個の例を選択するために、`k`(プロンプトに含める例の数)と`trainset`を必要とします。
1. **`BootstrapFewShot`**: あなたのプログラムのすべてのステージに対する完全なデモンストレーションを生成するために、(あなたのプログラムのデフォルトとなる)`teacher`モジュールと`trainset`のラベル付きのサンプルを使用します。パラメーターには、`max_labeled_demos`(`trainset`からランダムに選択されるデモンストレーションの数)、`max_bootstrapped_demos`(`teacher`によって生成される追加の例の数)が含まれます。このブートストラッピングプロセスは、「コンパイルされた」プロンプトのメトリックのみを引き渡すなど、デモンストレーションの検証にメトリックを活用します。

    上級: より困難なタスクにおいては、互換性のある構造を持つ*別の*DSPyプログラムである`teacher`プログラムの使用をサポートしています。

1. [**`BootstrapFewShotWithRandomSearch`**](https://dspy.ai/deep-dive/optimizers/bootstrap-fewshot): 生成されたデモンストレーションに対してランダムサーチを行い、複数回`BootstrapFewShot`を適用し、最適化においてベストなプログラムを選択します。パラメーターは`BootstrapFewShot`と同じであり、コンパイルされれていないプログラム候補、`LabeledFewShot`で最適化されたプログラム、シャッフルされないサンプルを用いて`BootstrapFewShot`でコンパイルされたプログラム、ランダマイズされたサンプルセットを用いて`BootstrapFewShot`を用いてコンパイルされた`num_candidate_programs`個のプログラムを含む、最適化の過程で評価されるランダムのプログラム数を指定する`num_candidate_programs`があります。
1. **`KNNFewShot`**: 指定された入力サンプルに対して最も近いトレーニングサンプルを特定するためにk-最近傍アルゴリズムを使用します。そして、これらの最近傍のデモンストレーションは、BootstrapFewShotの最適化プロセスのtrainsetとして使用されます。例に関しては、[こちらのノートブック](https://github.com/stanfordnlp/dspy/blob/main/examples/outdated_v2.4_examples/knn.ipynb)をご覧ください。

### 指示の自動最適化

これらのオプティマイザは、プロンプトに最適な指示を生成し、MIPROv2の場合は一連のfew-shotのデモンストレーションも最適化することができます。

1. [**`COPRO`**](https://dspy.ai/deep-dive/optimizers/copro): それぞれのステップにおいて新たな指示を生成、洗練し、coordinate ascent(メトリック関数と`trainset`を用いたヒルクライミング)を用いてそれらを最適化します。パラメータには、オプティマイザが実行するプロンプト改善のイテレーションの数である`depth`が含まれます。
1. [**`MIPROv2`**](https://dspy.ai/deep-dive/optimizers/miprov2): それぞれのステップの指示*と*few-shotのサンプルを生成します。この指示はデータアウェアであり、デモンストレーションアウェアです。あなたのモジュールに対する指示/デモンストレーションの生成の空間における効果的な探索のために、ベイジアン最適化を用います。

### 自動ファインチューニング

このオプティマイザは、背後のLLMをファインチューニングするために使用されます。

1. **`BootstrapFinetune`**: プロンプトベースのDSPyプログラムを重みの更新に変換します。出力は同じステップを持つDSPyプログラムですが、LMにプロンプトを提示するのではなく、モデルをファインチューニングすることでそれぞれのステップが実行されます。

### プログラム変換

1. **`Ensemble`**: 一連のDSPyプログラムをアンサンブルし、フルセットを使うか、ランダムにサブセットを単一のプログラムにサンプリングします。

## どのオプティマイザを使うべきか？

究極的には、使用すべき「適切な」オプティマイザや、タスクにベストな設定の特定には実験が必要となります。DSPyにおける成功は依然として繰り返しのプロセスです - あなたのタスクでベストなパフォーマンスを得るには、探索と繰り返しが必要となることでしょう。

とは言え、スタートする際の一般的なガイドがこちらとなります:

- **サンプルの数が非常に少ない**(約10)場合は、`BootstrapFewShot`でスタートしましょう。
- **より多くのデータ**(50サンプル以上)がある場合には、`BootstrapFewShotWithRandomSearch`をトライしましょう。
- **指示の最適化のみ**を行いたい場合(プロンプトを0-shotのままにしたい場合)には、[最適化のために0-shot最適化が設定された](https://dspy.ai/deep-dive/optimizers/miprov2#optimizing-instructions-only-with-miprov2-0-shot)`MIPROv2`を使いましょう。
- **より長時間の最適化ラン**(40トライアル以上など)を実行するために、より多くの推論コールを使いたい場合で、十分なデータがある場合(過学習を防ぐために200サンプル以上がある)には`MIPROv2`を試しましょう。
- 大規模なLM(7Bパラメーター以上)を用いて、これらのいずれかを活用できる場合において、非常に**効率的なプログラム**を必要としている場合には、`BootstrapFinetune`であなたのタスクに合わせて小規模なLMをファインチューニングしましょう。

## どのようにオプティマイザを使うのか？

これら全てはこの一般的なインタフェースを共有し、キーワードの引数(ハイパーパラメータ)にある程度の違いがあります。キーとなるオプティマイザの詳細なドキュメントは[こちら](https://dspy.ai/deep-dive/optimizers/vfrs)からアクセスでき、完全なリストは[こちら](https://dspy.ai/cheatsheet)にあります。

最も一般的なものである`BootstrapFewShotWithRandomSearch`の使い方を見てみましょう。

```py
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)
```

:::note info
**スタートしてみる: DSPyプログラムにおけるLMプロンプトや重みの最適化**
典型的なシンプルな最適化ランは$2 USD程度のコストが発生し、約10分の時間を要しますが、非常に大規模なLMやデータセットでオプティマイザを実行する際には注意してください。オプティマイザのコストは、お使いのLM、データセットや設定に応じて、数千とから数十ドルになり得ます。

[原文](https://dspy.ai/learn/optimization/optimizers/)にはいくつかのサンプルコードが記載されています。

**ReActエージェントのプロンプトの最適化**

これは、Wikipediaの検索を通じて質問に回答する最小限ですが完全に実行可能な`dspy.ReAct`エージェントの例であり、`HotPotQA`データセットからサンプリングした500個の質問-回答のペアにおいて安価な`light`モードで`dspy.MIPROv2`を用いて最適化しています。

```py
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`メソッドを使うことができます。

```py
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メソッドを呼び出すことができます。

```py
loaded_program = YOUR_PROGRAM_CLASS()
loaded_program.load(path=YOUR_SAVE_PATH)
```