目次
はじめに
大規模言語モデル(LLM)の精度や効率性を高めるためには、プロンプトの最適化が欠かせません。本記事では、Optunaを活用してプロンプトエンジニアリングを効率化する手法を紹介します。架空のコンペ "LLM Prompt Reverse" を題材に、具体的な実装方法とともに、簡便で実務やコンペに適したプロンプト最適化の方法を提案します。
お題:架空のコンペ『LLM Prompt Reverse』
タスク概要
- 目標: 入力として与えられた文字列を反転させる。
- 指標: 正解文字列(反転した文字列)とLLMが予測した文字列の編集距離を最小化する。
詳細は以下の記事をご覧ください。
実装方法
プロンプト最適化を以下の手順で進めます。
- 指標を明確にする
- 候補プロンプトをリストアップ
- ベースプロンプトを決定
- プロンプトを要素に構造化して最適化
1. 指標を明確にする
まず、最適化の指標を明確に定めます。本タスクでは、編集距離を指標とし、これを最小化するプロンプトを目指します。定性的な指標が必要な場合、Preferential Optimizationを利用した方法も有効です。
2. 候補プロンプトをリストアップ
初期段階では、どのプロンプトが良い結果をもたらすかは分かりません。そのため、たとえば以下の方法で候補をリストアップします。
- 生成AIに案を出させる
- 人間が直感的に考える
- Kaggleのディスカッションや公開ノートブックから収集する
今回は、ChatGPTを用いてプロンプトの候補をリストアップしました。
prompt_candidates = [
"Reverse the string:\n{string}",
"Please reverse the following string:\n{string}",
"Turn this string around and write it backwards:\n{string}",
"""Reverse the string:
Example 1: abcdefghij -> jihgfedcba
Example 2: zyxwvutsrq -> qrstuvwxyz
Now, reverse the following string:
{string}""",
"""I will provide a string, and your task is to reverse it:
Example 1: hello -> olleh
Example 2: python -> nohtyp
Example 3: abcdefghij -> jihgfedcba
Now, reverse this string:
{string}"""
]
3. ベースプロンプトを決定
収集したプロンプトを全探索し、編集距離が最小となるプロンプトをベースプロンプトとして選定します。
下記のプロンプトが最も良いスコアとなりました。これをベースプロンプトとします。
"Reverse the string:\n{string}"
以下のノートブックで詳細な結果を確認できます。
4. プロンプトを要素に構造化して最適化
ベースプロンプトを分割し、構造化します。この構造化により、プロンプトの要素ごとに最適化が可能になります。
プロンプトクラスの実装例
以下は、プロンプトを要素に分割し、最適化可能なクラスにした例です。
class Prompt:
"""
プロンプトを要素に分割し、構成可能にするクラス。
Optunaなどで各要素を最適化する際に利用。
"""
def __init__(
self,
action: str = "Reverse",
target: str = "the string",
separator: str = "\n",
):
"""
Args:
action (str): プロンプトの動詞部分。例: "Reverse"
target (str): プロンプトの目的語部分。例: "the string"
separator (str): 改行文字や区切り文字。例: "\n"
"""
self.action = action
self.target = target
self.separator = separator
def __str__(self) -> str:
"""
プロンプトの文字列表現を生成します。
Returns:
str: プロンプトの文字列表現。
"""
return f"{self.action} {self.target}:{self.separator}{{string}}"
このクラスを用いることで、プロンプトの要素を柔軟に最適化できます。
Optunaによる最適化
以下は、Optunaを用いてプロンプトの各要素を最適化する部分のコードスニペットです。
def objective(trial):
action = trial.suggest_categorical("action", ["Reverse", "Please reverse", "Flip"])
target = trial.suggest_categorical("target", ["the string", "this string", "the text"])
separator = trial.suggest_categorical("separator", ["\n", " ", ": "])
prompt_template = str(Prompt(action, target, separator))
print(f"Evaluating prompt:\n{prompt_template}\n")
train["pred"] = generate_predictions(
pipeline,
train["original"].tolist(),
prompt_template,
max_tokens,
string_length
)
true_strings = train["reversed"].tolist()
pred_strings = train["pred"].tolist()
score = levenshtein_evaluation.calculate_levenshtein_distance(true_strings, pred_strings)
print(f"Levenshtein Distance Score for this prompt: {score}\n")
return score
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=10)
詳細な結果については、以下のノートブックをご覧ください。
参考にしたもの
[2] [PyCon APAC 2023] ハイパーパラメータ最適化フレームワークOptunaの最新機能紹介 by Masashi Shibata
[5] 『大規模言語モデルを使いこなすためのプロンプトエンジニアリングの教科書』
[6] 『LLMのファインチューニングとRAG: チャットボット開発による実践』
ソースコード
この記事で使用したソースコードは、以下のGitHubレポジトリからご確認ください。