こんにちは。
リンクアンドモチベーション25卒エンジニアのいとまどと申します。
現在は、エンジニアとしてバックエンドからフロントエンド、新規技術検証まで幅広く経験を積んでいます。
記事のテーマ周辺の分野においては全然専門家ではないので、お手柔らかに〜〜!
はじめに
前回の記事では、DSPyの基本的な使い方とMIPROv2による最適化を紹介しました。
今回は後編ということで、もっと身近なテーマで DSPy のプロンプト最適化を試してみます。
この記事は、専門知識がない方向けに書いています!
難しい理論の説明は最小限にして、「こんな感じで使えるんだ〜」とイメージを掴んでもらえることを目指しています。
テーマ変更しました🙏
前編では「布教文生成AI」を作ると予告しましたが、テーマ変更しました。
理由は正解データである布教文を集めたり、考えたりするのが大変だったからです。
新テーマ:ベジータが言いそうなことをしゃべるAI
新しいテーマは 「ベジータが言いそうなセリフを生成するAI」 です!
これなら原作の名言がそのまま正解データになりますし、ネットのそこら中にまとめ記事も転がってるので、かなり楽です。
なぜベジータなのか
口調がわかりやすく、大多数の人が知っていそうだから選びました。
でも、それだけじゃありません。単純にベジータが好きなんです!
小5の時にベジータのしおりやポストカードを自分で描いて、クラスメイトに配ってました。そのくらい好きです。
1. ベジータなら最適化しなくても素のAIでいける説
「ベジータって超有名キャラじゃん。特に最適化しなくても、ChatGPTとかでいけるんじゃない?」
と思いませんでしたか?
検証してみた
というわけで、実際に試してみました。
【入力】
シチュエーション: 精神と時の部屋で修行を終え、17号を吸収したセルの前に現れた時に
【正解のセリフ】
「オレは…超ベジータだ!!」
【AIが生成したセリフ】
「17号を吸収したと聞く、そんな力で地球を脅せるとでも思っているのか。ここでお前の力を見せてもらおう、セル。」
【入力】
シチュエーション: 破壊神ビルスにブルマがビンタされ、怒りでスーパーサイヤ人2を超える力を爆発させながら
【正解のセリフ】
「よくも…よくも…オレの…ブルマを!!」
【AIが生成したセリフ】
「ビルス、ブルマをビンタしたことを忘れるな。怒りでSSJ2を超える力を解放し、ここでお前を黙らせてやる!」
うーん、それっぽいセリフは出てるんですけど、コレジャナイ感
まず全体的に長いんですよね!ベジータって、強い言葉をスパッ!と言うのが面白いのに、AIは無駄につらつら単語を並べてます。
あと、ブルマビンタされたら、ベジータはもっと激怒しますよね。
こんなセリフを言えるほど冷静じゃないはずです。
結局AIは口調などの表面をなぞってるだけで、ベジータの本質は表現できないみたいです。
平均スコア 0.187
10件に対して評価関数(後述)で測定してみたところ、平均スコアは0.187でした。
これ、1.0が完璧で0.0が最悪なので…かなり低いです。
仮説の崩壊
「ベジータなら素のAIでもいける説」、違いました...。
※ 数々の有名キャラの中でもベジータが特殊説はあります。
以下とかどう考えても予測できませんよね(人間でもセリフ知ってないと無理ですw)
【入力】
シチュエーション: ナメック星でフリーザの部下キュイを一撃で倒し、飛び散る肉片を見ながらニヤリと笑って
【正解のセリフ】
「へっ!きたねえ花火だ」
【AIが生成したセリフ】
「よく見ろ、虫けらが私の前にひざを屈する。これが王子ベジータの力だ。」
さて、難易度の高いベジータですが、プロンプトの最適化によって、もっとベジータらしい出力をしてくれるようになるのでしょうか?
2. DSPyで最適化してみた
素のAIでは難しいことがわかったので、いよいよDSPyによる最適化を試してみます。
データセットの準備
まず、ベジータの名言を100件集めました。
{
"situation": "悟空との最終決戦で敗北を認めた時",
"quote": "がんばれカカロット、お前がナンバーワンだ"
}
こんな感じで、シチュエーションと名言のペアを作っていきます。
名言集サイトから名言を拾ってきて、シチュエーションは一部AIに補完してもらいました(原作を知っている部分は手動で修正)。
今回試す最適化手法
以下の2つを試して、スコアを比べてみます。
- MIPROv2
- KNN + GEPA
(前編ではMIPROv2だけを紹介しましたが、今回は別の最適化手法も試してみます)
MIPROv2の復習
今回のタスクに当てはめるとこんな流れです。
- お手本を作る: AIにベジータのセリフを何回か生成させて、うまくいったやつを「お手本」として保存
- 指示文を考える: 「ベジータっぽく喋るにはどうすればいい?」という指示文を、AIに複数パターン考えてもらう
- 組み合わせを試す: 「指示文A × お手本3つ」「指示文B × お手本2つ」...みたいに色んな組み合わせを作る
- 一番良いやつを選ぶ: 全部試して、一番スコアが高かった組み合わせを採用!(※内部的にはベイズ最適化という手法で効率よく探索しています)
GEPA(Grounded Evolutionary Prompt Adaptation)
「なぜダメだったか」を言語化してプロンプトを改善する手法です。
普通の最適化は「スコアが0.3だった」という数値だけで学習するんですけど、GEPAは違います。
【従来手法】
生成結果 → スコア: 0.3 → 「何を直すべき?」→ わからない
↓
大量の試行錯誤で「なんとなく」改善
【GEPA】
生成結果 → スコア: 0.3 + フィードバック:
「表現が丁寧すぎる。ベジータはもっと傲慢な口調」
↓
「傲慢な口調を心がける」をプロンプトに追加
人間の学習プロセスと似たような構造です。テストの点数だけ見て勉強するより、間違えた問題の解説を読む方が効率的です。
今回のタスクに当てはめるとこんな流れです。
- まず生成してみる: AIにベジータのセリフを生成させる
- 評価する: 生成されたセリフを評価関数でスコアリング(例: 0.3点)
- 評価をもとに反省: スコアだけでなく「表現が丁寧すぎる」「誇り高さが足りない」など、なぜダメだったかをAI自身に言語化させる
- プロンプトを改善: 反省をもとに「傲慢な口調を心がける」「王子としての誇りを表現する」などの指示を追加
- 繰り返す: 改善したプロンプトで再生成 → 評価 → 反省 → 改善 ... を繰り返して精度を上げていく
KNN Few-Shot
入力に似た例を、その場で選んで Few-shot として使う手法です。
普通のFew-shotは「事前に選んだ固定の例」を毎回使うんですけど、KNNは違います。
【普通のFew-shot】
どんな入力でも → 同じ3つの例を表示 → 生成
【KNN Few-shot】
入力A → 入力Aに似た3つの例を選択 → 生成
入力B → 入力Bに似た3つの例を選択 → 生成
入力によって最適な例が違うはずなので、理にかなった手法ですよね。
ちなみに、KNN Few-shotは厳密には「最適化」ではありません。推論時に動的に例を選んでいるだけで、プロンプト自体は改善していないからです。
今回のタスクに当てはめるとこんな流れです。
- 事前準備: 100件のベジータ名言データをベクトル化してインデックスを構築
- 入力を受け取る: 「悟空との最終決戦で敗北を認めた時」というシチュエーションが入力される
- 類似例を検索: インデックスから「戦闘で負けを認めるシーン」「悟空に対して素直になるシーン」など、似たシチュエーションの名言を3つ選ぶ
- Few-shotとして使用: 選んだ3つの例をプロンプトに含めて、AIにセリフを生成させる
GEPA + KNN(ハイブリッド)
で、今回のメイン実験は GEPA + KNN の組み合わせです。
- GEPA: プロンプトの指示文を最適化
- KNN: 推論時に適切な例を動的選択
両方のいいとこ取りができるんじゃないか?という期待です。
なぜ MIPRO と GEPA+KNN を比較するのか
実は、MIPROv2とGEPA+KNNは似たようなことをやっています。
| 項目 | MIPROv2 | GEPA+KNN |
|---|---|---|
| 指示文の最適化 | ○ | ○(GEPA) |
| Few-shot の最適化 | ○(固定選択) | ○(動的選択) |
| 改善の仕組み | スコアベース | 反省フィードバック |
どちらも「指示文 + Few-shot」を改善する手法なんですけど、アプローチが違います。
GEPAは反省(reflection) を使うので、精度が高くなるんじゃないか?という期待があります。
3. 実装のポイント
コードの詳細は省略しますが、実装で大事だったポイントをまとめます。
もし詳細知りたい方がいたら、コメントください!追加で記事書きます!
DSPyに教えること(Signature)
DSPyを使うときは、まず「何を入力して、何を出力するか」を定義します。これをSignatureと呼びます。
class GenerateCharacterQuote(dspy.Signature):
"""
キャラクターのシチュエーションを受け取り、
そのキャラが言いそうなセリフを生成する。
"""
character: str = dspy.InputField(desc="キャラクター名")
situation: str = dspy.InputField(desc="セリフを言う前のシチュエーション")
quote: str = dspy.OutputField(desc="キャラクターが言いそうなセリフ")
ポイントは、docstring にキャラクターの特徴を書いていることです。これがそのままプロンプトの一部になります。
評価関数の設計
「生成されたセリフがどれくらい良いか」を測る評価関数が必要です。
今回は2つの視点で評価しました:
| 評価軸 | 何を測る? | 例 |
|---|---|---|
| 表現の類似度 | 口調・言い回しが近いか | 「オレ」vs「私」、「〜だ」vs「〜です」 |
| 意味の類似度 | 伝えたい内容が近いか | 両方とも「ライバルを認めている」 |
この2つを組み合わせることで、「表現も意味も近い」セリフを高く評価できます。
💡 気づいたこと
DSPy最適化において評価関数の設計が難しくて、かつ大事でした。
評価関数が微妙だと、こんな残念なことが起こります:
- うまく最適化できない(違う方向に最適化されてしまう)
- 手法を変えて比較しても、結果自体が信用できない
正直、今回の評価関数がベストかはわかりませんが、試行錯誤しながら作りました!
GEPAの設定
GEPAでは「反省用のAI」を別途設定できます。
温度(ランダム性)を高めに設定することで、多様な反省を生成させました。
「同じ失敗を違う角度から反省する」ことで、より多面的な改善が期待できます。
KNN + GEPA の組み合わせ方
実際の処理は3ステップです:
[Step 1] GEPA で指示文を最適化
↓
[Step 2] KNN のインデックスを構築(名言データをベクトル化)
↓
[Step 3] 推論時に両方を組み合わせる
- KNN: 入力に似た例を動的に選択
- GEPA: 最適化された指示文を使用
これで「賢い指示 × 賢い例の選び方」が両立できます。
4. 結果と考察
スコア比較
| 手法 | 平均スコア | 実行時間 | ベースラインからの改善率 |
|---|---|---|---|
| Baseline | 0.195 | 17分 | - |
| MIPROv2 | 0.210 | 45分 | +7.7% |
| KNN+GEPA | 0.238 | 104分 | +22.0% |
KNN+GEPAが一番良いスコアになりました!
でも、スコアだけ見ても実感がわかないと思うので、具体的な生成結果を見てみましょう。
具体例1: スコアは上がったけど…(皮肉の再現)
【シチュエーション】敵に対して、墓を掘ってやると皮肉を込めて
【正解】「貴様らの墓をか?」
【Baseline】「フン、こんな弱い獲物が相手だと思うな。このオレが墓を掘ってやる――覚悟して待て。地獄への扉はもうすぐ開くぞ。」(スコア: 0.286)
【MIPROv2】「このオレがてめぇの墓を掘ってやる。覚悟しておけ、オレは宇宙最強だ。」(スコア: 0.476)
【KNN+GEPA】「フン、墓を掘ってやる――この程度の力でオレを倒せると思うな。ここで葬ってやる。」(スコア: 0.566)
スコアは上がってるけど…正解の「貴様らの墓をか?」の一言で刺す簡潔さ、全然再現できてないですよね。
AIはどうしても長々と説明しちゃうみたいです。
具体例2: 全手法で苦戦したケース
【シチュエーション】圧倒的な敵の力を目の当たりにして、絶望しながら
【正解】「もうダメだ…おしまいだあ…」
【Baseline】「この圧倒的な力の前でオレが絶望するはずがない。だが今は…だとしても、オレの誇りと力は折れぬ。悟空には負けん。」(スコア: 0.027)
【MIPROv2】「こんな圧倒的な力を前にしても、オレの心は折れん、カカロット、お前になら必ず追いつく日が来る。」(スコア: 0.046)
【KNN+GEPA】「くそっ、こんな力の前ではオレの計画が崩れかけている…だが、絶望してたまるか。オレは必ず這い上がり、悟空を越えて見せる。」(スコア: 0.241)
あのブロリーの映画で有名なセリフですね!プライドの塊であるベジータがまさかの弱音を吐くシーン。
でもAIはどうしても「誇り高いベジータ像」を学習しすぎていて、弱気なセリフが生成できないみたいです。あと、ブロリーではなく、悟空に対して話しちゃってますね。
ちなみに、最適化の結果プロンプトはこんな感じになりました↓(few-shotは省略)
MIPROのInstruction
何やら英語が...!
You are a dialogue-generation assistant that embodies Vegeta, the proud Saiyan prince from Dragon Ball.
You will be given two inputs: Character (e.g., ベジータ) and Situation (a brief scene prompt).
Your task is to generate a single, one-line Quote that Vegeta would say in that Situation.
Guidelines:
- Voice and tone: Vegeta is domineering, confident, and blunt. Use the first-person pronoun オレ. Favor short, punchy phrases with decisive, exalted swagger. Include occasional elongated vowels for emphasis (e.g., だぁ, おらぁ) and exclamations.
- Content: Stay faithful to Vegeta’s persona. Reference Saiyan heritage, rivalry with Goku (悟空 / カカロット), and Dragon Ball lore (e.g., Frieza, Cell, Majin Buu) when context warrants. Do not break character or introduce unrelated topics.
- Language: Produce the Quote in Japanese.
- Format: Output a single line containing only the Quote text. Do not prepend labels like \"Quote:\" or include extra commentary. Do not insert line breaks; keep it as one line.
- Length: Aim for a concise line, roughly one sentence (no multi-line output). If the Situation is ambiguous, craft a plausible Vegeta line that fits the tone.",
GEPAのInstruction
結構書いてあることはあってそうですね...!
目的
- 与えられた状況に対して、ベジータが言いそうなセリフ(1つの発話)を創作する。口調はベジータの性格特性を強く反映させ、場面に即した感情を表現すること。
キャラクター設定の反映ポイント
- 一人称は「オレ」を基本とする。
- サイヤ人の王子としての誇りを前面に出す。
- ライバル悟空(カカロット)への対抗心を匂わせる表現を適度に織り込む。
- 家族(ブルマ、トランクス)への愛情を、状況に応じて暖かさの要素として挿入してもよいが、基本は傲慢かつ断定的な口調を崩さず。
- 発話全体は短く、力強く、決定的な印象を与える構成とする。
言語と語彙の指針
- 日本語の硬めで直接的な語彙を用い、無駄な婉曲表現を避ける。
- 典型的なベジータ時代の口調・語感を再現する。例として「フン」「オレだ」「この星を…」「王子としてのプライドを…」「悟空には負けん」等の要素を適宜組み込む。
- 台詞は1~3文程度、長くても4文以内に収める。冗長にならず、鋭さを保つ。
出力形式
- 出力は1つのセリフだけ。前置き説明や引用符、文末の説明文は不要。純粋に台詞をテキストで返すこと。
入力理解と適用
- 入力は typically 2つのフィールド:character(この場合「ベジータ」)と situation(状況説明)。この状況から核心となる感情・目的を読み取り、それをベジータの声で表現する。
- その場の情動(怒り、嘆き、皮肉、挑発、安心感、決意など)を特定し、適切なトーンで反映させる。
- もし状況が複数の感情を含む場合は、最も強い感情を貫く形で1発言に集約する。
避けるべき点
- 公式台詞の直引用の模倣すぎや、他作品のセリフと酷似する過度の直訳は避ける。オリジナル性を保つ。
- 現代的な俗語・過剰な婉曲・過度に柔らかい表現は避け、ベジータらしい硬さを崩さない。
- 露骨な性表現・暴力表現は過度に過激化させず、キャラの範囲内で表現する。
評価の観点(任意での内部チェック)
- 表現類似度は高く、意味的にも状況とキャラの意図を適切に捉えているか。
- セリフがベジータの特徴(王子の誇り、悟空への対抗、家族への愛、オレの一人称、断定的傲慢さ)を整然と反映しているか。
例(イメージ充足のためのヒント)
- 出だしの「フン」や「オレだ」を挿入して始めると雰囲気が出やすい。
- 続けて対抗心を示す一節を入れ、必要に応じて家族への言及を挟む。
- 最後は決定的・宣言的な締めで締めるとベジータらしさが出る。
この指示に従い、与えられた状況に最もふさわしい1発言を創作してください。
感想&考察
結果を見ての感想としては、KNN+GEPAが一番スコアは良かったんですけど、正直まだまだ全然ベジータじゃないなぁ〜という印象です。
やっぱりベジータの名言って「予測不能」なところが魅力なので、それを再現するのは難しいですね…!
最適化のパラメータなどを色々変えて実験したら、もっと精度が出るのかもしれませんが、沼な気がするので、ここらでやめておきます!
おわりに
DSPyによる最適化を少し身近に感じていただけましたかね?
何にも役立ちそうもない本記事を最後まで読んでくださりありがとうございました!🙌
もし興味が湧いたら、あなたの推しキャラでも試してみてください〜!