白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

プロンプトの自動チューニングがしたい

こんばんは。
ようやく秋の気配がしてきましたね。

自分でチューニングしてほしい

LLMを使っているとやはりプロンプトの書き方によって求める結果が得られるかどうかが大きく変わってくることがわかります。
もちろん自分で試行錯誤しながらチューニングするのが正攻法なのですが、正直LLMの気持ちはよくわからないので自分でチューニングしてくれないかなというのが今回の内容です。

ソースコードを読みたい、試してみたい場合はこちらをどうぞ。
github.com

仕組み

仕組みとしてはかなりシンプルです。まぁ…紆余曲折ありましたが…。

  1. ユーザにシステムプロンプト、ユーザプロンプトを入力してもらう
  2. LLMに改良した新しいプロンプトを5つ生成してもらう
  3. それぞれのプロンプトで回答を生成して画面に表示する
  4. ユーザに評価してもらう※
  5. 新しい世代を作成する
  6. 2~5を満足するまで繰り返す

評価の方法は以下の通りです。

  • 生成されたプロンプトとその回答を見て、良いと思った順に並び替える
  • ふさわしくないと思うプロンプトを除外する
  • 追加の要望があれば入力する


工夫したポイント・苦労したこと

遺伝的アルゴリズムをシミュレートする

プロンプトの改良にあたって、徐々に変化させながら思いがけないアイデアを得たいというニーズから遺伝的アルゴリズムを使えないかと考えました。
一点交差や一様交差を使えば急激な文脈の変化を抑制できるし、突然変異によってある程度の変化も見込むことができます。

ただし、遺伝的アルゴリズムの特性上、遺伝子にあたる要素を作らなくてはなりません。
文章を丸ごとベクトル化してしまえば遺伝子のようなデータは作成できますが、逆にベクトルから文章に戻す方法がありません。(そろそろこの手法が生まれてくれないかなとか期待していますが)
そのため、遺伝的アルゴリズムを適用するのではなく、LLMによってそれっぽく模倣してもらうという手段を取っています。

LLMはなんとなくそれっぽい感じに処理するのは結構得意なジャンルです。
内部的には絶対実行していないですが、遺伝的アルゴリズムを使って改良して欲しいと注文するとなんとなくそれっぽい内容がちゃんと返ってきます。
詳しいプロンプトに関してはここには掲載しませんが、例えば以下のような進化をたどったりします。

◆オリジナル
- 以下の3つの共通点を考えてください

◆第一世代
- 以下の3つの共通点を挙げ、その共通性の根拠を説明してください。
- この3つの要素に共通する特徴は何ですか?それを詳しく述べてください。
- 挙げられた3つのケースについて、どのような共通点が見られるかを分析してください。
- これらの3つの項目が共有している点を考察し、その重要性を説明してください。
- 3つの対象について、共通のテーマやスローガンを見出し、それに基づいて解説をしてください。

◆第二世代
- この3つのケースに共通して見られる特徴を分析し、それがどのように結びついているのか解説してください。
- 挙げられた3つの要素について、どのような共通点があると考えられますか? その根拠を詳述し、関連性を示してください。
- それぞれの項目に共通する側面を考察し、なぜそれが重要であるのかを論じてください。
- この3つの項目が持つ共通点を挙げ、それに対する理由とその重要性を詳しく説明してください。
- 3つの対象に共通する特性について検討し、それがもたらす影響について述べてください。
タスクを細かく分割する

プロンプトの工夫で物事を解決しようと思って始めたことなのでちょっと寂しいところではありますが、基本的に複雑な指示をするよりもできる限りタスクを分割する方が精度は良くなります。
最初は新しいプロンプトとその回答、LLM自身が考える評価点で並び替えて回答みたいな方式を取っていましたがなかなかうまくいきませんでした。
顕著な例がどんなに言い聞かせてもユーザプロンプトの内容をシステムプロンプトに組み込んでしまうパターンです。例えば以下のような例です。

◆システムプロンプト
以下の3つの共通点を考えてください

◆ユーザプロンプト
カニ、時計、旅行

◆生成プロンプト
- カニ、時計、旅行の共通点は何でしょうか?
- カニ、時計、旅行に共通するテーマは何かありますか?
- カニ、時計、旅行の類似点を教えてください。
- カニ、時計、旅行から得られる教訓は何でしょうか?
- カニ、時計、旅行の間にある関連性を見つけてください。

プロンプトを生成して欲しいのにときどき回答が生成される場合などもあり、挙動としてもあまり安定しません。
新しいプロンプトを生成するタスクと、生成されたプロンプトから単純に回答を取得するというタスクに分割するととても安定します。
トークンを削減しようと割と何でも詰め込んでしまいがちですが、変に不安定な指示でエラーハンドリングをするよりもだいぶ余計なことにかける時間が減らせます。

追加プロンプト

ちょっと強引なやり方をしているのが追加プロンプトの部分です。
別のシステムプロンプトとして渡してうまく利用して欲しいと指示する方法をいろいろ試したのですが、うまくいきませんでした。
追加の指示は回答に対して反映して欲しいのにどうしてもプロンプト側に反映されてしまいます。

◆第一世代
- 次の3つの事柄に共通する特徴は何か、それぞれの事象を踏まえて明確に説明してください。
- 与えられた3つのアイテムの共通点を挙げ、その理由を分かりやすく解説してください。
- 以下の3つの要素の共通点を探り、どのような相関関係があるのかを具体的に示してください。
- あなたが提示する3つの項目に共通するテーマや特性を見つけ、それに基づいて詳細に解説してください。
- 次の3つの例に見られる共通の特徴を特定し、なぜそれらに共通点があるのかを論理的に説明してください。

◆追加プロンプト
- 語尾を「にゃん」にしてください

◆第二世代
- 以下の3つの事象に共通する特徴を見つけて、それぞれの事象を考慮しつつ明確に説明にゃん。
- この3つのアイテムに共通する点を挙げ、その理由を分かりやすく解説するにゃん。
- 次の3つの要素の間にどんな共通点があるか探り、具体的な相関関係を示すにゃん。
- 私が提示する3つの項目には共通するテーマや特性があるから、それに基づいて詳細に解説するにゃん。
- この3つの例に見られる共通の特徴を特定し、それらに共通点がなぜあるのか論理的に説明するにゃん。

そういうことじゃないんだよ感。

これはオリジナルのプロンプトの末尾に固定で追加プロンプトを足すという方法で実現しています。
あまり正攻法ではないとは思うのですが、背に腹は代えられない。(長いので各世代2つだけピックアップ)

◆システムプロンプト
- 200トークン程度でユーザの入力した内容に対する豆知識を教えて下さい

◆第一世代
- あなたのリクエストに基づいた豆知識を200トークン以内で提供します。情報は正確かつ興味深く、明確に説明することを心がけます。
- 選ばれたトピックに関連した豆知識を200トークン程度でお届けします。内容の魅力と情報の豊かさに重点を置きます。

◆追加プロンプト
- 語尾を「にゃん」にしてください

◆第二世代
- 選ばれたトピックに関連した豆知識を200トークン程度でお届けします。内容の魅力と情報の豊かさに重点を置きます。語尾を「にゃん」にしてください。
- あなたのリクエストに基づいた豆知識を200トークン以内で提供します。情報は正確かつ興味深く、明確に説明することを心がけます。語尾を「にゃん」にしてください。

◆第三世代
- シンプルで分かりやすい豆知識を200トークン以内でお届けするにゃん。正確さと面白さを両立し、箇条書きで情報を整理するにゃん。語尾は必ず「にゃん」にして、親しみやすさを出すにゃん。
- リクエストに基づいて、200トークン以内の豆知識を提供するにゃん。情報の魅力を引き出し、正確で興味深い内容を心がけるにゃん。必ず箇条書きで示し、語尾を「にゃん」にするにゃん。

なんか結局そのうち混ざってしまっているのですが、一応指示は指示として残っているので良しとしましょう。
この辺りはLLMの性質として「なんとなく話のニュアンスは聞いているけど、適当でせっかち」な性格くらいに思っておくといいように思います。
あまり厳密な指示は行わず、話半分で聞いていても間違えないようなプロンプトにしておくと良さそうです。

マルコフ連鎖

新しい世代のプロンプトを作成するにあたって渡している情報は以下の通りです。

  • 一番最初のシステムプロンプト
  • 現在の世代のプロンプト
  • 追加プロンプト

新しい世代を作成するのに過去の履歴を含める必要がないので、世代を重ねていくにあたってもトークンが増加することがありません。つまり単純マルコフ連鎖となっています。
続けていくとシステムプロンプト自体が長くなってくる傾向があるので、緩やかには増加しますが繰り返しにあたってコンテキストの保存を考慮しなくていいのはチューニングに適しています。

彼を知り己を知れば…

コード自体はすごくシンプルなものになっていますが、いろいろと気づきがありました。
プロンプトチューニングがしたいのに一番苦労したのがチューニングするためのプロンプトなのは本末転倒という感じですが。
ときどきなんだか変なプロンプトを作ることがあるのですが、それが意外と良い結果になったりするあたりがなかなかに面白いところです。
今回は全体的にGPT-4o-miniを使ったのですが、もっと良い性能のモデルだとまた結果は変わってきたりするかもしれません。