牛本に学ぶ「納得して」即実践できるプロンプトテクニック
はじめに
この記事は、筆者がLLMのプロンプトエンジニアリング(―GitHub Copilotを生んだ開発者が教える生成AIアプリケーション開発)を読んで「これはすぐに実践できそうだな」と感じた(または印象に強く残った)プロンプトのテクニックを横流しに紹介するものとなっています。
従って、この書籍を読まれた方にとっては既知のテクニックばかりで得られるものは少ないかと思われます。
当書籍は、LLMの特性を読者に伝えた上で、どのようにプロンプトを組み立てると効率的かを述べてくれています。そのため、読み手も納得感を持ってそのテクニックを使うことができるようになる、いい書籍でした。おすすめです。
LLMのメカニズムを知ってテクニックを使うのと、「よくわからないがいいらしい」でテクニックを使うのとではまた、引き出せる効果に差が出てくると思います(「納得感」が大事だと思うんですよね)。
さて、皆さんもご存じの通り、LLMはどんどん賢くなっています。筆者はAIに詳しいわけではないのですが、それでも日々いろんなニュースが飛び込んでくるのを見ていると、「LLMの進化」がすごく身近に感じられます。
LLMが進化して賢くなるにつれて、「プロンプト」の質はLLM側で吸収されるようになっているとも感じられて、多少適当なものでもそれなりの精度で返してくれている印象もあります。
とはいえ、LLMの出すアウトプットをより適切なものに導くための「プロンプトテクニック」を知ることに損はないでしょう。
テクニック紹介
それでは、牛本(って呼び方が一般的なのかはさておき)を読んで「これはすぐに使えそう」と感じたテクニックを列挙していきます。
単に手法を述べるだけではなく、期待される効果に納得をもちやすいように、LLMの特徴も合わせて記述していきます。
(どれもこれも、当書籍から学んだことです)。
学習データのドキュメントを意識する
例えば、プロンプトを作成するとき、コードスニペットを含める場合があります。
その場合、コードスニペットはなるべくコードブロック(```〜```)に含めた方がアウトプットは安定しやすいでしょう。
というのも、LLMが行っている計算は、次に続くもっともらしいトークンを予測することです。最終的なアウトプットも、その「プロンプトに続くもっともらしいトークンの補完」の積み重ねに過ぎません。
その「もっともらしい」の判断材料になっているのがモデルのトレーニングに使用されたドキュメント等のデータです。
世の中にはマークダウン形式の文書も多いので、LLMにとって「```から始まる次の部分」がコードスニペットであると予測しやすいはずです。これは、より正確にLLMにコンテキストを伝えられることを意味します。
このようにトレーニングデータを意識してプロンプトを組み立てることは、アウトプットを期待値に近づけることができる重要な要素であると言えるでしょう。
プロンプトが英語の方が出力の精度が高い傾向がある、とよく耳にしますが、これもトレーニングデータの占める割合として英語が圧倒的に高いからでしょう。
リフォーカス
何か複雑な仕様が予想される要件についての実装(または実装計画)をLLMにリクエストする場合、以下のようなプロンプトを作成するかもしれません。
プロンプト例(長いのでたたみました)
# やりたいこと
Web アプリケーションにユーザー権限管理機能を追加したい。
# 詳細
私たちのシステムはチームごとに複数のメンバーを抱えており、各メンバーに異なるアクセス権限を割り当てたいと考えています。
具体的には、管理者(Admin)、編集者(Editor)、閲覧者(Viewer)の3つのロールを用意し、それぞれのロールに応じて利用できる機能を制限します。
また、将来的にロールを増やしたり、特定のユーザーに一時的な権限を付与するなど、柔軟に拡張できる設計を望んでいます。
さらに、監査ログも重要です。ユーザーがいつどのデータにアクセスしたか、権限変更が誰によっていつ行われたかを記録し、あとから追跡できるようにしたいです。
ただし、このログはできるだけパフォーマンスに影響を与えない仕組みが必要です。
**RBAC と ABAC のどちらを採用すべきでしょうか?**
柔軟性を優先すべきか、シンプルさを優先すべきかで悩んでいます。今後の拡張や監査要件を考えるとどちらが適切でしょうか?
# 実装上の懸念
- 既存のデータベーススキーマにどう組み込むか
- Rails + React 環境にどう統合するか
- 将来的にマイクロサービス化する可能性もある
- セキュリティ監査やコンプライアンスの観点も考慮する必要がある
# 最終的に欲しいもの
- 推奨する設計方針(RBAC or ABAC)
- 実装ステップの大まかな流れ
- データベーススキーマの例
- 監査ログの実装戦略
やりたいことが最初の方に明示されている上に、構造化されていますが、LLMの特性を踏まえるとまだ工夫が必要そうです。
というのも、LLMには以下の特徴があるようです。
-
中間部の喪失
- プロンプトの冒頭と末尾の情報は比較的思い出しやすいが、中間の情報は活用が難しくなる
-
コンテキスト内学習
- プロンプトの末尾に違いほどモデルに影響を与えやすい
この「中間部の喪失」は、プロンプトが長くなればなるほど顕著になっていきます。
この中間部は、「無関心の谷(Valley of Meh)」とも呼ばれています。
例に挙げたプロンプトを改めて見てみると、「やりたいこと」は冒頭で明確なように見えますが、ユーザーにとっては以下の部分がかなり重要なコンテキストなのではないでしょうか。
**RBAC と ABAC のどちらを採用すべきでしょうか?**
このプロンプト量だと気にするほどでもないかもしれませんが、この質問部分は無関心の谷に落ちてしまう可能性があります。
結果として、ユーザーが求めている「RBAC と ABACのどちらを採用すべきかという意見」から外れて、全く異なるアプローチが提案され、話が進められるかもしれません(それはそれでいいのですが)。
この問題に対処するために本書で紹介されていたのが、リフォーカスです。
これは、影響を与えやすいとされるプロンプトの末尾に、改めて重要なコンテキスト(本題の質問など)を差し込み、モデルの注意をそちらに移す効果をもたらします。
以下のようなプロンプトを末尾に差し込めば、(ユーザーの期待するような)アウトプットを安定して引き出せることでしょう。
# 再喝
このシステムでは **RBAC(ロールベースアクセス制御)** と **ABAC(属性ベースアクセス制御)** のどちらを採用すべきでしょうか?
柔軟性を優先するか、シンプルさを優先するかで悩んでいます。
今後の拡張や監査要件を考慮すると、どちらが適切かを中心に回答してください。
タイポには気を付ける
ごく当たり前のことを言うようですが、これも大事な気がします。
この話をする前に、logprobという数値について取り上げたいと思います。
LLMは次のトークンを予測するとき、候補ごとに確率を割り当てます。その確率を対数で表したものが **logprob **で、モデルの「そのトークンに対する自信度」を示します。
プロンプトをトークン単位で次に何が来るか予測している中、タイポされている単語に直面してしまうと、logprobは極端に低くなるようです。
例えば、トークンを逐次処理していく中で「次に続くのはcompletion
(という単語)だろう」と予測していたところ、compuletiun
といった存在しない単語だった時、LLMはその部分に自信をなくしてしまいます。
これまでの入力: The task is to provide a
次の入力:compuletiun
予測候補:
" completion" logprob ≈ -0.16
" computation" logprob ≈ -2.30
" compuletiun" logprob ≈ -9.21 # 自信がない
モデルに自信がなくて、最高のパフォーマンスを出してくれるのかというと、それは無理だろうなと思います。
実際、タイプミスが出力を不安定にすることは各論文にて言及があるようです。
つまりタイポはLLMにとってノイズであり、極力排除したいのは自明ですね。
なるべくLLMを変なところで驚かせないようにしましょう。
CoTプロンプティング
こちらは手法として結構有名な手法です。調べたら多くの記事がヒットします。
この手法を使う前に、LLMには「内的な独り言」がないということを理解しておくべきです。
これを解決するために、最終的な回答を出力する前に「思考」を挟んでもらうようにする必要があります。「思考の過程」を声に出しながら回答させるようにします。
そうすれば、「思考過程を独白しているトークン」が後続に引き継がれるようになります。
モデルは、先行しているトークンとの一貫性を保とうとするため、思考プロセスと整合性のあるアウトプットを最終的に出してくれるようになります。
では、どうすれば思考してくれるんだということになりますが、方法は様々です。いかにその一部を示します。
Zero-ShotでCoTを促す
これは簡単で、例えば
ステップバイステップで考えましょう。
と付け足すだけです。
Few-ShotでCoTを促す
これは、Zero-Shotと違って、いくつか「例」を見せてそのパターンを模倣させることでCoTを促すやり方です(Few-Shot Prompting自体はCoTに限定せず、様々な使い方があります)。
以下はその例です。
Q: 以下のログを見て、原因を推定してください。
ログ: hogehogehoge....
A: ログには「Connection refused」とあります。これはデータベース(PostgreSQL)に接続できていないことを意味します。
「localhost:5432」で待ち受けしていない、もしくはPostgreSQLサービスが停止している可能性が高いです。
原因: **PostgreSQLが起動していない、または接続先が間違っている**。
---
Q: 以下のログを見て、原因を推定してください。
ログ: fugafugafuga....
A:
1つ目の例では、最終的な原因を答えとする前に「思考」を挟んでいることが見て取れます。
このパターンに従わせれば、思考を挟むようになるということです。
もっと効果的な出力を安定させたいなら、もういくつかの例を提示するのが良いのだと思います。
例を用意する分手間がかかるのが個人的に難点です(面倒)。
正直、今のLLMの賢さならZero-Shotでもまあまあ精度が高い印象ですが、Few-Shotの方が出力が安定しそうなのは研究から見ても確かなようです。
Plan-and-Solveプロンプティング
LLMに対して、まずは全体的に問題を理解させ、計画を立ててから問題解決を行うように指示する手法です。
牛本から直接引用しますが、以下のようなプロンプトを使用すると良いようです。
まず、問題を理解し、問題を解決するための計画を立てましょう。それから、計画を実行し、段階的に問題を解決しましょう
引用:LLMのプロンプトエンジニアリング(―GitHub Copilotを生んだ開発者が教える生成AIアプリケーション開発
このようなプロンプトを足すだけで、Zero-Shotで使用した「ステップバイステップで考えましょう」で指示した時と比べ、計算エラーや推論ステップの欠落が大幅に減少したようです。
これも、本質的にはCoTで達成したいことと同じで、問いに対する答えを直感的に出す前に「推論させる」ためのものと筆者は解釈しています。
おわりに
本記事を書いたモチベとしては以下の2点でした。
- 自己学習のアウトプット
- 「牛本」を勧めたい
牛本はすでに著名な書籍ですし、読んだことがある方も多いかと思いますが、この本が良いのは、
- プロンプトエンジニアリングを小手先のもので終わらせない
ことだと感じています。
LLMのメカニズムにも触れた上で、効果的なプロンプトとはどのようなものかを述べてくれているので、読み手にとっても「納得感」を持てた上で理解でき、すぐに実践に移すことができます。
この本を読むまではLLMを魔法のようなものと捉えていましたが、読み終えた後でその感覚は薄れました。
この記事を読んで少しでも興味が湧いたら一読をお勧めします。
Discussion