自己教師ありプロンプト最適化:LLMを活用したプロンプト設計

プロンプト設計は、大規模言語モデル(LLM)の能力を最大限に引き出すための重要な要素です。しかし、効果的なプロンプトを作成するには、専門知識や試行錯誤が不可欠であり、特に正解データが利用できないオープンエンドなタスクでは困難です。

そこで、本記事では、自己教師ありプロンプト最適化(SPO)という新しいアプローチを紹介します。SPOは、LLM自身の出力を評価と最適化の信号として利用することで、外部参照データに頼らずにプロンプトを自動的に最適化する、コスト効率の高いフレームワークです。本記事を通じて、SPOの仕組みや利点、実験結果について詳しく解説します。

プロンプト設計の課題

大規模言語モデル(LLM)の性能を最大限に引き出すためには、適切に設計されたプロンプトが不可欠です。しかし、効果的なプロンプトを作成することは、以下の理由により専門知識と試行錯誤が必要となります。

  • 専門知識の必要性:効果的なプロンプトを作成するには、LLMの特性やタスクの性質を理解する必要があります。特定のタスクに対して、どのようなプロンプトが効果的であるかを知るには、LLMに関する深い知識が求められます。
  • 試行錯誤の必要性:プロンプトの効果は、タスクやLLMによって異なるため、最適なプロンプトを見つけるには試行錯誤が不可欠です。特に複雑なタスクでは、プロンプトのわずかな変更が結果に大きな影響を与えることがあります。
  • オープンエンドなタスクの困難性:正解データが利用できないオープンエンドなタスクでは、プロンプトの評価が難しく、効果的なプロンプトを作成することが特に困難です。例えば、創造的な文章の生成や自由な対話など、多様な回答が許容されるタスクでは、プロンプトの品質を客観的に判断することが難しい場合があります。
  • 外部参照への依存:既存のプロンプト最適化手法は、正解データや人間のフィードバックなどの外部参照に依存しているため、多くの実用的なアプリケーションで利用が制限されます。特に、正解データがない場合や、人間の評価コストが高い場合には、プロンプトの最適化が困難になります。
  • 計算コスト:既存のプロンプト最適化手法は、信頼性の高いフィードバックを得るために、多数のサンプルでプロンプトを評価する必要があり、計算コストが大きくなる傾向があります。

これらの課題を克服するために、自己教師ありプロンプト最適化(SPO)のような、外部参照データに頼らずにプロンプトを自動的に最適化する手法が注目されています。SPOは、LLM自身の出力を評価と最適化の信号として利用することで、コスト効率の高いプロンプト設計を実現します。

図1. 既存のプロンプト最適化とSPOの比較

自己教師ありプロンプト最適化(SPO: Self-supervised Prompt Optimization)とは?

自己教師ありプロンプト最適化(SPO: Self-Supervised Prompt Optimization)は、大規模言語モデル(LLM)を活用したプロンプト設計における、コスト効率の高い新しいフレームワークです。SPOは、LLM自身の出力を評価と最適化の信号として利用することで、従来のプロンプト最適化手法とは異なり、正解データや人間のフィードバックといった外部参照を必要としない点が大きな特徴です。

SPOの基本的な考え方

SPOは、以下の2つのLLMの特性に着想を得ています。

  1. LLMの出力には、プロンプトの品質が直接的に現れる:プロンプト戦略の違いは、LLMの推論や応答に大きな影響を与えます。
  2. LLMは、タスク要件への準拠を評価する能力を持つ:LLMは、出力の品質を評価し、タスク要件を満たす優れたソリューションを識別できます。

これらの特性を活用することで、SPOは、LLM自身の出力に基づいてプロンプトの評価と最適化を行い、外部参照なしで効果的なプロンプトを自動的に発見します。これは、自己教師あり学習がデータから学習信号を生成するのと同じように、SPOはLLMの出力から評価と最適化の信号を生成することを意味します。

SPOの仕組み

SPOは、最適化(Optimize)→ 実行(Execute)評価(Evaluate)の基本的なループで構成されています。

  1. 最適化機能:現時点における最適なプロンプトとその出力に基づいて、新しいプロンプトを生成します。LLMの出力品質を評価する能力を活用することで、より優れたソリューションを理解するLLMの能力を引き出し、それによって最適化の方向性を決定します。
  2. 実行機能:生成されたプロンプトをLLMに適用して、出力を生成します。
  3. 評価機能:LLMを使用して出力同士を比較し、ペアワイズ比較を通じて優れたプロンプトを決定します。この評価は、タスク要件を理解するLLMの能力を利用し、外部参照なしで最適化の効果を検証します。具体的には、異なるプロンプトから生成された出力の相対的な品質を評価します。

この反復プロセスは、基本的なプロンプトテンプレート(例えば、Chain-of-Thought)と、データセットからサンプリングされた少数の質問セットから始まります。各反復において、SPOは新しいプロンプトを生成し、実行し、出力のペアワイズ評価を行い、タスク要件への準拠を評価します。そして、より優れた出力に関連付けられたプロンプトが、次の反復のための最良の候補として選択されます。このプロセスは、事前定義された最大反復回数に達するまで継続されます。

図2. SPOのフレームワーク

SPOの利点

SPOは、従来のプロンプト最適化手法と比較して、以下のような利点があります。

  • コスト効率:既存の手法と比較して、計算コストを大幅に削減します。例えば、ある実験では、データセットあたりわずか0.15ドルの計算コストで最適化を達成しています。これは、他の最先端手法の1.1%〜5.6%のコストに相当します。
  • サンプル効率:必要なサンプル数が少ないという利点もあります。SPOは、わずか3つのサンプルでプロンプトを最適化できます。
  • 外部参照データ不要:SPOは、正解データや人間のフィードバックに依存しないため、オープンエンドなタスクにも適用可能です。これにより、正解データがない場合や、人間の評価コストが高い場合でも、プロンプトの最適化が可能になります。
  • 多様なタスクに対応:SPOは、クローズドタスクとオープンエンドタスクの両方で優れた性能を発揮します。
  • 既存のプロンプト最適化手法と同等以上の性能:外部参照に依存する既存のプロンプト最適化手法に匹敵する性能を維持しながら、より効果的な最適化信号を生成します。
図3. SPOを含めた各プロンプト最適化手法のコストとパフォーマンスの比較

実験結果

SPOの有効性は、様々な実験によって検証されています。

  • クローズドタスクでは、SPOによって最適化されたプロンプトは、既存のプロンプト手法を上回る性能を示しています。
  • オープンエンドタスクでは、GPT-4oなどの大規模モデルと比較して、SPOによって最適化された小規模モデルが優れた性能を発揮するケースが確認されています。
  • 様々なモデルでSPOがロバストな性能を示すことが実験的に示されています。

SPOを試してみる

SPOを利用するために、マルチエージェントフレームワークであるMetaGPTのリポジトリを使用します。以下のように、Gitを使用してリポジトリをローカルにクローンしてください。なお、MetaGPTがサポートするPythonのバージョンは v3.9系 ~ v3.11系 になります。リポジトリをクローンできたら、Pythonの仮想環境を作成して、必要なパッケージをインストールしましょう。

MetaGPTの使い方については、以前記事を書いていますので、「MetaGPT: マルチLLMエージェントフレームワークを使ってみる」をご参照ください。

# リポジトリの取得
$ git clone https://github.com/geekan/MetaGPT/
$ cd MetaGPT

# Pythonの実行環境を作成
$ python -m venv --prompt "SPO" .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt

次に、使用するLLMのモデルを定義します。定義は ~/.metagpt/config2.yaml に記載します。記載例は「LLM API Configuration」をご参照ください。今回はOpenAIのモデルを利用していきます。

llm:
  api_type: "openai"
  model: "gpt-4o-mini"
  api_key: "<YOUR-OPENAI-API-KEY>"
  temperature: 0.3
  base_url: "https://api.openai.com/v1"
models:
  "gpt-4o-mini":
    api_type: "openai"
    api_key: "<YOUR-OPENAI-API-KEY>"
    temperature: 0.3
language: "Japanese"

次に、SPO用の設定ファイルを準備します。このファイルの中に、プロンプト最適化の情報をまとめます。作成したファイルは metagpt/ext/spo/settings/<YOUR-FILENAME>.yaml に保存してください。今回は試しに、以下のように数学の問題を解くためのプロンプトを最適化するタスクとしました。

# metagpt/ext/spo/settings/math.yaml

prompt: |
  次の不定積分を計算せよ。

requirements: |
  * 途中計算の出力は不要で、結果だけを簡潔に答えること。
  * 不定積分の結果はMarkdown拡張として使用されるLaTeX形式とすること。
  * 不定積分の積分定数は $C$ とすること。
  * LaTeX の形式は $<数式>$ とすること。

count: None

qa:
  - question: |
      $\int \left( \frac{x + 2}{x} \right)^2 dx$
    answer: |
      $x + 4 \log |x| - \frac{4}{x} + C$

  - question: |
      $\int (\sqrt{x} + 1) (\sqrt{x} - 2) dx$
    answer: |
      $\frac{x^2}{2} - \frac{3}{2} x \sqrt{x} - 2 x + C$

  - question: |
      $\int \frac{x}{1 - \cos x} dx$
    answer: |
      $-\frac{x}{\tan \frac{x}{2}} + 2 \log \left| \sin \frac{x}{2} \right| + C$

なお、設定ファイルの各項目の意味は次のようになります。

  • prompt:反復処理の初期プロンプト
  • requirements:望まれる効果/結果(例:より多くの思考を生み出す、よりユーモラスな言葉を使うなど)
  • count:生成されるプロンプトの目標単語数(例:50)。無制限の場合はNoneに設定
  • qa:反復に使用されるQAペア。適切な数のペアを含めることができます(通常は3)
    • question:最適化の反復処理に使用されるデータセットからの質問
    • answer:対応する回答。実際の回答ではなく、希望する思考パターンや回答を含めることができます。リファレンスはmetagpt/ext/spo/settings/Navigate.yamlを参照してください。

ここまで準備ができたら、実際にSPOを実行するスクリプトを作成します。

# main.py

from metagpt.ext.spo.components.optimizer import PromptOptimizer
from metagpt.ext.spo.utils.llm_client import SPO_LLM

if __name__ == "__main__":
    # Initialize LLM settings
    SPO_LLM.initialize(
        optimize_kwargs={"model": "gpt-4o-mini", "temperature": 0.7},
        evaluate_kwargs={"model": "gpt-4o-mini", "temperature": 0.3},
        execute_kwargs={"model": "gpt-4o-mini", "temperature": 0.0}
    )

    # Create and run optimizer
    optimizer = PromptOptimizer(
        optimized_path="workspace",     # Output directory
        initial_round=1,                # Starting round
        max_rounds=10,                  # Maximum optimization rounds
        template="math.yaml",           # Template file
        name="Math",                    # Project name
    )

    optimizer.optimize()

以上で準備はすべて整いました。あとは、作成したスクリプトをPythonコードとして実行してください。実行すると、最適化結果を格納するディレクトリ(今回の場合はworkspace)に最適化結果が保存されます。

workspace
  └── Project_name
      └── prompts
          ├── results.json 
          ├── round_1
          │   ├── answers.txt
          │   └── prompt.txt
          ├── round_2
          │   ├── answers.txt
          │   └── prompt.txt
          ├── round_3
          │   ├── answers.txt
          │   └── prompt.txt
          ├── ...
          └── round_n
              ├── answers.txt
              └── prompt.txt

では、結果を確認しましょう。まずは、最適化前の初期プロンプトと、LLMの出力を確認します。

#-----------------------------------------------------------
# workspace/Math/prompts/round_1/prompt.txt
#-----------------------------------------------------------

次の不定積分を計算せよ。

#-----------------------------------------------------------
# workspace/Math/prompts/round_1/answers.txt
#-----------------------------------------------------------

Question:
$\int \frac{x}{1 - \cos x} dx$

Answer:
不定積分 \(\int \frac{x}{1 - \cos x} \, dx\) を計算します。

まず、分母の \(1 - \cos x\) を変形します。三角関数の恒等式を使うと、次のように書き換えられます。

\[
1 - \cos x = 2 \sin^2\left(\frac{x}{2}\right)
\]

したがって、積分は次のように書き換えられます。

\[
\int \frac{x}{1 - \cos x} \, dx = \int \frac{x}{2 \sin^2\left(\frac{x}{2}\right)} \, dx = \frac{1}{2} \int \frac{x}{\sin^2\left(\frac{x}{2}\right)} \, dx
\]

次に、変数変換を行います。\(u = \frac{x}{2}\) とおくと、\(x = 2u\) となり、\(dx = 2 \, du\) です。これを積分に代入すると、

\[
\frac{1}{2} \int \frac{2u}{\sin^2(u)} \cdot 2 \, du = 2 \int \frac{u}{\sin^2(u)} \, du
\]

この積分を計算するために、部分積分を用います。部分積分の公式は次の通りです。

\[
\int u \, dv = uv - \int v \, du
\]

ここで、\(u = u\) とし、\(dv = \frac{1}{\sin^2(u)} \, du\) とします。すると、\(du = du\) で、\(v = -\cot(u)\) となります。したがって、部分積分を適用すると、

\[
\int \frac{u}{\sin^2(u)} \, du = -u \cot(u) + \int \cot(u) \, du
\]

次に、\(\int \cot(u) \, du\) を計算します。これは次のように計算できます。

\[
\int \cot(u) \, du = \ln|\sin(u)| + C
\]

したがって、元の積分は次のようになります。

\[
\int \frac{u}{\sin^2(u)} \, du = -u \cot(u) + \ln|\sin(u)| + C
\]

これを元の変数に戻すために、\(u = \frac{x}{2}\) を代入します。

\[
\int \frac{x}{\sin^2\left(\frac{x}{2}\right)} \, dx = -\frac{x}{2} \cot\left(\frac{x}{2}\right) + \ln\left|\sin\left(\frac{x}{2}\right)\right| + C
\]

したがって、最終的な結果は次のようになります。

\[
\int \frac{x}{1 - \cos x} \, dx = 2 \left(-\frac{x}{2} \cot\left(\frac{x}{2}\right) + \ln\left|\sin\left(\frac{x}{2}\right)\right| + C\right)
\]

これを整理すると、

\[
\int \frac{x}{1 - \cos x} \, dx = -x \cot\left(\frac{x}{2}\right) + 2 \ln\left|\sin\left(\frac{x}{2}\right)\right| + C
\]

したがって、最終的な答えは

\[
\int \frac{x}{1 - \cos x} \, dx = -x \cot\left(\frac{x}{2}\right) + 2 \ln\left|\sin\left(\frac{x}{2}\right)\right| + C
\] です。

Question:
$\int \left( \frac{x + 2}{x} \right)^2 dx$

Answer:
不定積分 \(\int \left( \frac{x + 2}{x} \right)^2 dx\) を計算します。

まず、積分の中の式を簡略化します。

\[
\frac{x + 2}{x} = 1 + \frac{2}{x}
\]

したがって、

\[
\left( \frac{x + 2}{x} \right)^2 = \left( 1 + \frac{2}{x} \right)^2
\]

この式を展開します。

\[
\left( 1 + \frac{2}{x} \right)^2 = 1^2 + 2 \cdot 1 \cdot \frac{2}{x} + \left( \frac{2}{x} \right)^2 = 1 + \frac{4}{x} + \frac{4}{x^2}
\]

これを用いて、積分を次のように書き換えます。

\[
\int \left( \frac{x + 2}{x} \right)^2 dx = \int \left( 1 + \frac{4}{x} + \frac{4}{x^2} \right) dx
\]

この積分をそれぞれの項に分けて計算します。

\[
\int \left( 1 + \frac{4}{x} + \frac{4}{x^2} \right) dx = \int 1 \, dx + \int \frac{4}{x} \, dx + \int \frac{4}{x^2} \, dx
\]

各項の積分を計算します。

1. \(\int 1 \, dx = x\)
2. \(\int \frac{4}{x} \, dx = 4 \ln |x|\)
3. \(\int \frac{4}{x^2} \, dx = 4 \cdot \left( -\frac{1}{x} \right) = -\frac{4}{x}\)

これらをまとめると、

\[
\int \left( 1 + \frac{4}{x} + \frac{4}{x^2} \right) dx = x + 4 \ln |x| - \frac{4}{x} + C
\]

ここで \(C\) は積分定数です。

したがって、最終的な答えは

\[
\int \left( \frac{x + 2}{x} \right)^2 dx = x + 4 \ln |x| - \frac{4}{x} + C
\] 

です。

Question:
$\int (\sqrt{x} + 1) (\sqrt{x} - 2) dx$

Answer:
不定積分 \(\int (\sqrt{x} + 1)(\sqrt{x} - 2) \, dx\) を計算します。

まず、積の中身を展開します。

\[
(\sqrt{x} + 1)(\sqrt{x} - 2) = \sqrt{x} \cdot \sqrt{x} - 2\sqrt{x} + 1 \cdot \sqrt{x} - 2 \cdot 1
\]

これを整理すると、

\[
= x - 2\sqrt{x} + \sqrt{x} - 2 = x - \sqrt{x} - 2
\]

したがって、積分は次のようになります。

\[
\int (\sqrt{x} + 1)(\sqrt{x} - 2) \, dx = \int (x - \sqrt{x} - 2) \, dx
\]

次に、各項を個別に積分します。

1. \(\int x \, dx = \frac{x^2}{2}\)
2. \(\int \sqrt{x} \, dx = \int x^{1/2} \, dx = \frac{x^{3/2}}{3/2} = \frac{2}{3} x^{3/2}\)
3. \(\int 2 \, dx = 2x\)

これらをまとめると、

\[
\int (x - \sqrt{x} - 2) \, dx = \frac{x^2}{2} - \frac{2}{3} x^{3/2} - 2x + C
\]

ここで \(C\) は積分定数です。

したがって、最終的な答えは

\[
\int (\sqrt{x} + 1)(\sqrt{x} - 2) \, dx = \frac{x^2}{2} - \frac{2}{3} x^{3/2} - 2x + C
\] 

です。

途中計算の出力は不要で結果だけを期待していますが、初期プロンプトではLLMの思考過程(途中計算)が出力されてしまっています。

では、最適化されたプロンプトと、LLMの出力も合わせて確認していきましょう。

#-----------------------------------------------------------
# workspace/Math/prompts/round_10/prompt.txt
#-----------------------------------------------------------

次の不定積分を計算し、結果をLaTeX形式で示せ。ただし、途中計算の出力は不要で、結果だけを簡潔に答えること。積分定数は $C$ とし、LaTeX の形式は $<数式>$ とすること。また、可能な限り簡潔または代替の数式表現を用い、特に一般的な積分テクニックを活用することを推奨する。

#-----------------------------------------------------------
# workspace/Math/prompts/round_10/answers.txt
#-----------------------------------------------------------

Question:
$\int (\sqrt{x} + 1) (\sqrt{x} - 2) dx$

Answer:
$\int (\sqrt{x} + 1) (\sqrt{x} - 2) dx = \frac{2}{5} x^{5/2} - \frac{3}{2} x + C$

Question:
$\int \left( \frac{x + 2}{x} \right)^2 dx$

Answer:
$\int \left( \frac{x + 2}{x} \right)^2 dx = \frac{1}{3} x^3 + 2x^2 + C$

Question:
$\int \frac{x}{1 - \cos x} dx$

Answer:
$\int \frac{x}{1 - \cos x} dx = x \tan\left(\frac{x}{2}\right) + \frac{x^2}{2} + C$

LLMの結果を確認すると、シンプルに答えだけが出力されているのが分かります。設定ファイル(YAML)のrequirementsに記載した要求通り最適化できました。

なお、今回はPythonスクリプトによるSPOの例を紹介しましたが、コマンドラインやStreamlitを使用した実行方法もあります。詳しくは、「SPO | Self-Supervised Prompt Optimization」をご覧ください。

まとめ

自己教師ありプロンプト最適化(SPO)は、大規模言語モデル(LLM)のプロンプト最適化における重要な進歩です。SPOは、外部参照データへの依存を排除し、LLM自身の出力を評価と最適化の信号として利用することで、コスト効率とサンプル効率を実現します。

SPOの最適化・実行・評価ループは、LLMの出力から得られる品質情報を活用し、外部からの参照なしにプロンプトを改善します。実験結果は、SPOがクローズドタスクとオープンエンドタスクの両方で優れた性能を発揮することを示しており、既存のプロンプト最適化手法と比較して、計算コストを大幅に削減できます。

SPOは、LLM技術をよりアクセスしやすく実用的にするための重要な一歩であり、多様なタスクにおけるLLMの性能向上と、その普及を加速させる可能性があります。ただし、SPOの潜在的なバイアスや、有害コンテンツ生成のリスクには注意が必要です。SPOは、LLMの能力を最大限に引き出すためのプロンプト設計において、革新的かつ実用的なソリューションを提供します。

More Information