プロンプトエンジニアリング vs. ブラインドプロンプティング
「プロンプトエンジニアリング」という言葉は、言語モデルの発展とともに生まれました。プロンプトを活用して言語モデルから情報を効果的に引き出す一連のプロセスを指す言葉で、通常は実際のアプリケーションで使うことを想定しています。
現在プロンプトエンジニアリングをしていると主張する人の多くは、実際にはブラインドプロンプティングをしているに過ぎません。1「ブラインドプロンプティング」は私が作った言葉で、ほとんどテストをせず、プロンプトに関する知識も表面的なまま、雑な試行錯誤だけでプロンプトを作る手法を指します。ブラインドプロンプティングはプロンプトエンジニアリングではありません。
また、プロンプトエンジニアリングは本当に「エンジニアリング」と呼べるのか、単なるハイプに乗った人たちが唱える「魔術」に過ぎないのではないか、という懐疑的な見方も多くあります。私の考えでは、そうした懐疑論の多くは、私が目にしてきたプロンプトエンジニアリングを謳うツイートやブログ記事の多くが、せいぜいブラインドプロンプティングに薄い膜をかぶせた程度のものに過ぎないという事実に根ざしています。
この記事では、プロンプトエンジニアリングは実際の実験的手法に基づいて磨くことのできる、れっきとしたスキルであることを論じます。アプリケーションに実用的な価値をもたらす課題を例に、プロンプトをエンジニアリングして解決策を作り上げるプロセスを順を追って解説します。
なお、この記事全体はテキスト出力を前提に書いています。私自身が言語モデルを使ってきた主な用途がテキスト出力だからです。テスト手法などの一部のテクニックは、画像のような別の出力形式にはそのまま当てはまりません。ただし、この記事で紹介する内容はマルチモーダルな入力には十分に有効です。
プロンプトとは何か
「プロンプト」や「プロンプティング」という言葉をすでに十分ご存知の方は、このセクションは読み飛ばしていただいて構いません。
言語モデル(この言葉に馴染みがない方はChatGPTを想像してください)において、「プロンプト」とはモデルに与えるユーザー側の入力のことです。ChatGPTで言えば、テキストを入力するあのボックスだと考えていただくと分かりやすいでしょう。言語モデルはプロンプトに対して「補完(completion)」を推論して返します。たとえばChatGPTに「4 + 3 = 」と入力すれば、おそらく「7」と返ってきます。この場合、「4 + 3 = 」がプロンプトで、「7」が補完にあたります。
「プロンプティング」とは、プロンプトを使ってモデルから欲しい情報を引き出す行為を指します。大規模なオフラインの学習データも、モデルへのオフラインアクセスも必要なく、エンジニアでなくても直感的に扱えるため、情報を引き出す手法として魅力的に映ります。プロンプティングはモデルを調整する手法の一つに過ぎません。
そして「プロンプトエンジニアリング」は、(この記事で示すように)プロンプティングを活用して実際のアプリケーションで信頼できる機能を構築することを目指す、より厳密な取り組みを指します。ChatGPTのようにその場限りのやり取りをするプロンプティングとは異なり、プロンプトエンジニアリングで作られるプロンプトは、特定の課題を安定的に解決するために、大量かつ多様な状況で繰り返し使われることを前提としています。
課題
まずは、解決したい課題がなければ始まりません。課題が明確であれば、プロンプティングが最適な解決策なのか、あるいは他により適したアプローチがあるのかを判断できます。エンジニアリングとは、手法のために手法を使うことではなく、それが適切な手法であるという確信に基づいて進めるものです。
今回の例では、私たちがカレンダークライアントを作っている会社だと想定します。ユーザーが自然な言葉で予定を入力できるようにしたいと考えています。たとえば次のような入力です。
Dinner with Alice next Tuesday at Taco Bell
CorpConf on 11/4
1:1 with Bob tomorrow at 10 AM
こうした自然な入力から構造化されたデータを抽出し、アプリケーション内で扱える形で予定を記述するために、言語モデルは有効な解決策になり得ます。
もちろん、他にも解決策は考えられます。たとえば正規表現や文字列検索を組み合わせて、よくあるフレーズ(on <Day of Week>、tomorrow、today、next weekなど)を探す方法です。
言語モデルには言語モデルならではの利点もあります。他の言語への対応がより優れているかもしれませんし、タイプミスや文法的な誤りにも強く、少なくとも正規表現がうまくいかなかったときの保険として機能するかもしれません。いずれにせよ、プロンプティングを有望な解決策として追求するだけの価値はあります。
デモンストレーションセット
次に、デモンストレーションセットを用意します。デモンストレーションセットは、想定される入力と期待される出力のペアで構成されます。このセットには主に次のような目的があります。
プロンプトの精度を測るために使います。個々のデモの入力を与えることで、期待通りの出力が得られるかを検証できます。
プロンプトの入出力がどのような形であるべきかを明確にします。これにより、エンジニアとしてその形が課題に適しているかを判断できます。
このセットの一部を、few-shotプロンプトで使う例(exemplar)として活用できます。「few-shot」という言葉に馴染みがない方のために補足すると、few-shotとはプロンプトに加えていくつかの例を示すスタイルのことです。Few-ShotとZero-Shotプロンプティングの良い概要はこちらをご覧ください。
上記の(2)は特に重要です。入力として何を期待し、出力として何を期待するのかを大まかにでも把握しておく必要があります。なぜなら、その前後には[通常]データを一定の形式に保ち、一定の形式での出力を期待するソフトウェアが控えているからです。これは、課題を入出力の期待値が定まった一連の関数に分解していく、通常のソフトウェアエンジニアリングと何ら変わりません。
先ほどの例を使って、完全なデモンストレーションに拡張してみましょう。
Q: Dinner with Alice next Tuesday at Taco Bell
A: next Tuesday
Q: CorpConf on 11/4
A: 11/4
Q: 1:1 with Bob tomorrow at 10 AM
A: tomorrow
デモンストレーションセットのサイズについての注意: この記事では例として3件だけを用意していますが、実際には少なくとも十数件は欲しいところです。デモが多ければ多いほどテストは充実しますが、その分トークン使用量が増えてコストもかさみます。ある程度の規模を超えると、言語モデルをファインチューニングする方が経済的な場合もあります。
上記のデモンストレーションでは、二つの重要な判断をしています。どのようなプロンプト課題であっても、同様の判断が必要になります。
第一に、抽出する情報は一つに絞っています。イベント名、参加者、日時、場所など、予定の全体を抽出して、すぐに使えるきれいなJSONなどの形式で出力させたくなるかもしれません。モデルならそれも可能かもしれません。しかし新しい課題に取り組むときは、まず一つの問題に分解することをお勧めします。その方が課題は扱いやすくなりますし、最終的に複数の出力を扱うアプローチが本当に価値があるのかを判断するためのベースラインとなる精度も得られます。
第二に、出力は変換していません。すべてを日付型に変換したり、大文字・小文字を整えたりといったことは目指していません。ここでは文字通りのテキスト抽出を行っています。「next Tuesday」のような文字列をタイムスタンプに変換する、精度の高い決定的なライブラリはすでに数多く存在します。そのような処理をわざわざ言語モデルにやらせる必要はありません。ですから、まずは「next Tuesday」「11/4」「tomorrow」といった大まかな日付表現を取り出すだけにとどめ、そこからタイムスタンプへの変換は従来のプログラミング手法で解くことにします。そちらの方が自明な問題だからです。出力がシンプルであればあるほど、高い精度は得やすくなります。
最後に、出力のデコードについて一言: LLMはさまざまな形でプロンプトを補完します。完全な文で返してきたり、ピリオドを付けたり、大文字で始まったりとさまざまです。LLMの出力にどこまでの完璧さを求めるのか、デモンストレーションセットで検証する前にどこまで正規化するつもりなのかを決めておく必要があります。
たとえばテキスト抽出を行う場合、私は通常、前後の空白やピリオドを取り除き、出力全体を小文字に揃える程度であれば妥当だと考えています。JSON生成のようなより高度なことをする場合は、JSONをパースして決まった順序やスタイルで再エンコードし、比較が決定的に行えるようにすることもあるでしょう。といった具合です。
私のお勧めは、LLMからの出力はできるだけシンプルで柔軟なものにとどめ、正規化の処理はアプリケーション側で行うことです。最初からLLMに完全に正確なフォーマットでの出力を無理に求めないでください。初期の段階でLLM側に「出力整形」をさせすぎると、LLMが本来のタスク(この場合は情報抽出)をこなす能力と、出力を整形する能力とを切り分けるのが難しくなってしまいます。
プロンプト候補
ここでいくつかのプロンプト候補を考えます。プロンプト候補とは、言語モデルから望む振る舞いを引き出せそうだと私たちが考えるプロンプトのことです。最初から最良のプロンプトを選び当てられることはまずないので、複数の候補を用意します。
入門的な内容にとどめるため、ここではプロンプトを手作業で考えます。効果的なプロンプトを作るために、プロンプトエンジニアが知っておくべき基本的な知識がいくつかあります。たとえば、防御的な書き方よりも断定的な書き方の方が良いことが多いです。冗長で長い表現よりも、明確で簡潔な方が良い場合がほとんどです。few-shotプロンプトを作る際には、ラベルの分布を均等にすることや、ラベルの全種類を示すことが重要です。例(exemplar)を選ぶ際には、LLMが間違えそうな例が最も効果的なことが多く、例は短いものから長いものへと並べると最も性能が良くなることが多い、といった知見もあります。
要出典! 申し訳ありませんが、これらの推奨を裏付ける実験的研究の出典は示していません。正直なところ、それぞれについて読んだ論文(一点につき複数あることも多い)をいちいち探すのが面倒だったからです。もし私の言うことを信じられないのであれば、それでも構いません。ここでより重要なのは、プロンプト技術とその有効性に関する実験的研究が実際に存在するということです。でっち上げではないとお約束しますが、最新のモデルでは一部が古くなっている可能性はあります。
これらのテクニック一つひとつを解説するだけで一本の記事になってしまうので、ここでは扱いません。この記事の目的は、全体の流れを高い視点から端から端まで示し、LLMから価値を引き出すためのエンジニアリング手法が存在することをお見せすることです。
現時点でのゴールは、良いzero-shotプロンプトをいくつか考えることです。zero-shotプロンプトはfew-shotに変換でき、さらにそこからchain-of-thoughtへと発展させることができます。そしてそれぞれは、さらにバッチ化したプロンプトなどへと展開できます。zero-shotは試すべき基本形ですので、まずはそこに焦点を当てます。
私が考えたプロンプト候補は次の3つです。
Identify the date or day mentioned in the given text and provide it as the output.
Identify the date or day mentioned in the given event description.
Determine the date or day from each input and provide the output accordingly as a single word or date.
どれも一見もっともなプロンプトです。教養のある人間であれば、どのプロンプトでもほぼ正確に答えられるでしょう。しかし言語モデルは人間ではありませんから、同等の性能を自動的に期待することはできません。以前にもお見せしたように、一見もっともなプロンプトでも悲惨な性能になることはよくあります。そこで次のステップとして、テストと計測によって判断材料を集めます。
プロンプトのテスト
候補となるプロンプトのセットとデモンストレーションセットが揃えば、精度を測定できます。今日の時点で私が最も良いと感じている方法は、LangChainのようなライブラリを使ってシンプルなPythonスクリプトを組むことです。テストでは通常、各デモに対して次のようなプロンプトテンプレートで実行します。
{{prompt}}. Q: {{input}} A:
私は必ずまずzero-shotでテストします。ベースラインとなる精度の指標を得たいからです。そこからfew-shotをテストし、候補同士だけでなく、プロンプトのタイプ間でも比較できます。といった具合に進めていきます。
これはモデルごとに行う必要があります。同じプロンプトでも、より高性能なモデルで同じかそれ以上の精度が出るとは限りません。精度が下がることさえあります2。
プロンプトテストの最も基本的な成果物は、下のような表になるはずです。モデル(たとえばGPT-3.5とGPT-4)のような別の軸が加わることもあります。
| Zero-Shot | Few-Shot | ... |
-----------------------------------------
Prompt 1 | 64 | 68 | ... |
-----------------------------------------
Prompt ... | 44 | 52 | ... |
-----------------------------------------
Prompt N | 23 | 22 | ... |
この表では、縦軸にプロンプト候補、横軸にそのプロンプトを使ったプロンプトタイプを取っています。値は正答率をパーセンテージで示したものです。カレンダーアプリの例で続けると、「プロンプト1」をあるデモに対してzero-shotで使った場合は次のようになります。
Identify the date or day mentioned in the given text and provide it as the output. Q: CorpConf on 11/4 A:
そして「11/4」という答えを期待します。few-shot版は次のようになります。
Identify the date or day mentioned in the given text and provide it as the output.
Q: Dinner with Alice next Tuesday at Taco Bell. A: next Tuesday
Q: 1:1 with Bob tomorrow at 10 AM. A: tomorrow
Q: CorpConf on 11/4. A:
経験豊富な方向けの補足: 上のfew-shotの例では「以下の例を真似してください」といった指示は入れていません。実験的研究によれば、そうした一文を入れても精度が安定的に上がるわけではないため、トークン数を抑えるためにも、まずは入れずにテストするのが私のやり方です。第二に、このfew-shotの例では「MM/DD」形式の抽出を一度も例示していませんが、これは本来は良くないやり方です。実際のfew-shotでは、あらゆる抽出パターンを例示することが重要になる場合があります(Zhaoら 2021)。
分類問題のような特定のタイプの課題では、混同行列(Strobeltら 2022)を使って他のラベルの確率を可視化し、ラベルセットをより適切に調整できるかを判断することもできます。
精度に加えて、使用トークン数やリクエスト数なども測定しておきたいところです。最終的なプロンプトを選ぶ際には、これらすべてを考慮する必要があります。
プロンプトの選択
最後に、候補の中から一つを選んでアプリケーションに組み込みます。必ずしも最も精度の高いプロンプトを選ぶわけではありません。使用するモデル、必要なトークン数、得られた精度に基づいた、コストと精度のトレードオフの分析になります。
たとえば、few-shot版が最も高性能だと分かったとしても、テストセットでの精度が4%高いだけで、必要なトークンは200%増える(現在のAPIベースのモデルでは実質コストが2倍になる)といった場合もあるでしょう。ビジネス上の判断として、コストが半分になるのであれば4%精度が低くても構わない、と結論づけることもあり得ます。
あるいは、精度を上げるために別のアプローチを試すことに戻るかもしれません。たとえば、より安価なモデルでセルフコンシステンシー・デコーディング戦略(Wangら 2022)を試して、十分に精度が上がるかを見てみる、といった具合です。安価なモデルで多くのトークンを使った方が、高価なモデルで少ないトークンを使うよりも大幅にコストを抑えられる場合もあります。たとえば現在、GPT-4はGPT-3.5の約15倍高価です。つまり、GPT-3.5のプロンプト精度を上げるために使えるトークン予算は実質15倍あるということです(レート制限に関する注意点はありますが)。
この記事の例で言えば、表の中のプロンプト1のzero-shot版を選ぶかもしれません。精度は64%ですが、トークン数は大幅に少ない可能性が高いからです。64%の精度でも、カレンダーアプリでイベントのテンプレートを埋める程度には十分だと判断するかもしれません。この特定の課題については、64%よりもずっと良い精度が出せると思いますが、これはあくまで記事の中で使った数字です。
最も重要なのは、情報に基づいた意思決定を行うためのデータが手元にある、ということです。
信頼しつつ検証し、継続的に改善する
生成AIは確率的な性質を持つため、プロンプトには何らかの問題が残っている可能性が高いです。テストセットで精度が100%だったとしても、誤った出力を生む未知の入力が存在するはずです。したがって「信頼しつつ検証する」姿勢が重要であり、検証に失敗したケースをデモンストレーションセットに追加して、新たなプロンプトを開発し精度を高めていくべきです。
検証の方法は課題に大きく依存します。カレンダーアプリの例であれば、ユーザーに「この予定は正しいですか?」と明示的に尋ね、「いいえ」と答えられた場合に自然言語の入力をログに残して人間が確認する、といった方法が考えられます。あるいは、自動的な情報抽出の後にユーザーが手動で修正した予定を自動的に追跡する方が、さらに良いかもしれません3。
別の例として、プロンプトでコード(正規表現やプログラミング言語のテキストなど)を生成しているのであれば、最低限パースを試みることができます。パースがセキュリティ上の懸念になることはないはずですし4、少なくとも構文が正しいかどうかという最も基本的な検証になります。そしてここでも、検証に失敗した場合は入力と出力をログに残し、デモンストレーションセットを拡充して、より良いプロンプトを開発していきます。
検証は敵対的プロンプティングへの対策にも役立ちます。敵対的プロンプティングはそれ自体で一つの大きなトピックなので、この記事では扱いません。
そして、この先へ……
この記事では、プロンプトの開発がいかに——私の考えでは——エンジニアリングの実践になり得るかを示しました。課題を特定し、解決策を形作り、それを検証し、継続的な改善によって洗練させていく体系的なアプローチを説明してきました。
この記事で紹介したアプローチを、経験談と場当たり的な試行錯誤に頼って何らかの解決策にたどり着き、長期的にプロンプトを安定的に改善していくための体系的な基盤を築かない「ブラインドプロンプティング」と比べてみてください。
なお、この記事は非常に初歩的な内容であることを強調しておきます。すでに知られているより高度なテクニックを使えば改善できる点は、この記事の中にも複数あります。また、敵対的プロンプティングのような重要なトピックも扱っていません。具体例を挙げれば、few-shotプロンプトで最良の例を選ぶためのより科学的なアプローチも存在しますが、この記事ではできるだけ基本に絞りたかったのです。より高度なテクニックを学びたい方は、Lilian Weng氏によるPrompt Engineeringが素晴らしい概要を提供しています。
加えて、今や誰もがプロンプトチェイニングやエージェントといった、より高次のLLM統合へと急速に移行しています。こうした今後のイノベーションによって人手によるプロンプティングは不要になる、と主張する人もいます。それが真実かどうかはさておき、私は「第一原理」5から学ぶことに価値があると信じていますし、こうしたプロンプティングのテクニックを学ぶことで、高次の言語モデル技術を活用する能力も向上したと感じています。また、このような基本的なプロンプティングが、高次の概念の性能を今なお支えているとも考えています。
脚注
もっとも「声の大きい」人たち、という意味かもしれません。私はこの分野で本当に優れたエンジニアリング手法を実践している素晴らしいプロンプトエンジニアたちに多く出会い、学ばせてもらいました。残念ながら、Twitterなどのプラットフォームで目にするノイズの多くは、そうしたものではありません。↩
またもや要出典です。今回も正直面倒だっただけですが、複数の論文で読んだ実験的研究に基づいています。信じられないのであればそれでも構いませんので、ぜひご自身でテストしてみてください。↩
もちろんここにはプライバシー上の含意があります。あくまで一例として挙げているだけであり、実際の状況によってはこの手法が適切でない場合もあります。↩
YAML。😐 ↩
実際の「第一原理」はもっと低レイヤーであることは承知しています。ここでは「第一原理」という言葉を、より多くの知識を積み上げる土台となる、ある程度低いレベルの地点を指す一般的な比喩として使っています。↩
記事をランダムに読む