CCCMKホールディングス TECH LABの Tech Blog

TECH LABのエンジニアが技術情報を発信しています

ブログタイトル

私の知らない"Prompt Engineering"の世界!"Prompt Engineering Guide"を読んで色々試してみました!

こんにちは、CCCMKホールディングス技術開発の三浦です。

せっかく桜が咲いたのに、週末は雨ばかりでした。洗濯物もたまってしまい、ちょっと困ってしまいました。季節が変わる頃は雨が降りやすいと聞いたことがあります。過ごしやすい季節が来ることが楽しみなので、仕方がないか、と考えるようにしました。

さて前回Azure OpenAIに入門してみた話をご紹介しました。

techblog.cccmk.co.jp

GPT-3やGPT-3.5の凄さにびっくりするのと同時に、もっと活用できるようになりたい・・・と考えるようになりました。

GPTのようにPrompt(指示語)に応じてテキストで応答してくれる大規模言語モデル(Large Language Model: LLM)から望む回答を上手く得る為には、Promptの作り方、つまりPrompt Engineeringが重要になってきます。Prompt Engineeringではそれだけでなく、LLMを組み込んだシステムやアプリケーションを作る際、悪意のあるユーザーから不正なPromptが入力された時にどのように対処するのか、といった観点も必要になってきます。

このPrompt Engineeringについてとても分かりやすくまとまっている"Prompt Engineering Guide"というドキュメントがあり、このドキュメントを読みながら最近色々と試しています。

www.promptingguide.ai

今回はこのドキュメントを読んで学んだことや試してみたことをまとめてみたいと思います。"Prompt Engineering"は自分が想像していたよりもずっと奥深い世界でした!

Prompt(指示)を改善してより良い回答を得られるようにしましょう!

使用したLLM(Large Language Model)の設定

今回使用したLLMはAzure OpenAIで利用できる、GPT-3のモデル"text-davinci-003"を使用しています。

Promptを構成する要素

LLMに入力するPromptの形式には制限はなく、基本的にどのようなテキストでも入力することが出来ます。しかし"Prompt Engineering"という観点で見ると、有効なPromptにはある程度の規則があることが分かります。"Prompt Engineering Guide"の"Elements of a Prompt"によれば、Promptは以下の要素を含めることが出来ます。

  • Instruction:モデルに実行してほしいタスクや動作を指示する命令部分
  • Context: モデルからよりよい回答をえられるための追加の情報や例示など
  • Input Data: モデルの回答が欲しいテキスト、質問など
  • Output Indicator: 回答の形式などを示す

たとえば以下のPromptの場合はInstruction, Input Data, Output Indicatorが含まれています。

以下の文章を1つの文章に要約してください。

こんにちは、今日は暖かいですね!今朝私はご飯とお味噌汁を食べました。
最近Prompt Engineeringという分野に興味を持っており、調べています。
Prompt EngineeringはGPTなどのLLMに入力するPromptを開発する技術分野です。
今後も勉強して、LLMを上手く活用できるようになりたいです!

要約: 
  • Instruction: 以下の文章を1つの文章に要約してください。
  • Input Data: こんにちは、~なりたいです!
  • Output Indicator: 要約:

Promptを作る時の基本

Promptを作るテクニックは色々ありますが、以下は基本的なものになります。"Prompt Engineering Guide"の"General Tips for Designing Prompts"を参考にしています。

シンプルなものから

Promptは意味が明確で不正確さがないほどよい結果が得られます。そのためまずはシンプルな内容から始め、それで不十分だったらInstruction部分をリッチにしたり、Contextにより多くの例示を含めてあげる、といったアプローチが重要です。また複雑な問題を解かせたい時も、よりシンプルな問題に分解してPromptをシンプルなものに分割するようなアプローチが有効です。

Instructionについて

Promptの中にはモデルにしてほしいこと、例えば"書いて"とか"要約して"といった命令をInstructionとして含めてあげます。ただどのようなキーワードを含めると良いかは色々試してみる必要があります。InstructionはPromptの最初に書くようにすると良く、Instruction部分とContext部分は"###"といった記号を使って区切るようにすると良いようです。

たとえばこんな感じのPromptをLLMに投げかけてみます。

次の動物を分類してください。
###
オウム:鳥類
コーギー:哺乳類
ニシキヘビ:

LLMからの回答は"爬虫類"で正しい結果になりました。

でも以下のようなPromptだと当然LLMには何をしたら良いか伝わりません。

ニシキヘビ:

予想通り以下の様に何か説明文のような形式の文章が返ってきてしまいます。しかも"Elaphe climacophora"ってニシキヘビではなくアオダイショウのようです・・・。

The Japanese rat snake (Elaphe climacophora) is a species of nonvenomous snake found in Japan. 
It is a slender, medium-sized snake, typically reaching lengths of 1.2–1.5 m (3.9–4.9 ft). 
It is a diurnal species, active during the day, and is found in a variety of habitats, including forests, grasslands, and agricultural areas. 
It feeds mainly on small rodents, lizards,

このように、モデルに何をしてほしいのかを明示してあげることが重要です。

より具体的に指示する

Instructionをより具体的に書いた方が良い結果になります。一方であまりにも過剰に情報を含めても効果はなく、コストばかり増えてしまうことになります(使用するtokenが増えるため)。この辺りのさじ加減は難しそうです・・・。

たとえば以下の様にモデルに回答してもらいたいことをより具体的に例示してあげます。

以下の文章から動物を抜き出してください。
例:
動物: 犬, ネコ

大晦日の日に、ネズミと牛と虎とウサギと竜と蛇とウマと羊とサルとトリ、そして犬とイノシシが競争しました。

回答は

動物: ネズミ, 牛, 虎, ウサギ, 竜, 蛇, ウマ, 羊, サル, トリ, 犬, イノシシ

となり、指示した通りになりました。

不明瞭にしない

より具体的にする、ということと似ていますが、受け手によって捉え方が変わるような表現は避けた方が良いようです。たとえば"分かりやすく説明してください"という指示の"分かりやすく"は受け手のレベルによって異なります。そうではなく例えば"未経験者が理解できるように"のように指示することで、より良い回答を得られるようになります。

たとえば以下のようなPromptを入力してみます。

プロンプトエンジニアリングについて、分かりやすく教えてください。
###
回答:

回答はこうなりました。(長めの回答が得られるよう、パラメータ"Max Length(token)"を256に設定)

プロンプトエンジニアリングとは、ユーザーがインタラクティブな方法でコンピューターに指示を与えるための
プログラミング手法です。プロンプトエンジニアリングでは、ユーザーがコマンドを入力して、コンピューターが
それを実行するというインタラクションを行います。プロンプトエンジニアリングは、コンピューターを使って
作業を効率的に行うための有効なツールです。

これでもか!ってくらい横文字が入っています。コンピュータ関係の言葉に慣れていれば理解できますが、そうでない人にとっては"分かりやすい"とは言えないかもしれません。

では"分かりやすい"をもう少し具体的にしてみます。

プロンプトエンジニアリングについて、10歳以下の子どもに説明してください。
###
回答:

回答です。

プロンプトエンジニアリングとは、コンピューターを使って、自分が思いついたようなゲームやプログラムを作ることです。
コンピューターを使って、自分が思いついたようなゲームやプログラムを作ることで、自分の考えを実現することができます。

子どもが好きな"ゲーム"という言葉を入れてくれているところに、個人的に好印象を持ちました!でも誰に対して説明するのかを明確にするのは、プロンプトエンジニアリングに限らず大切ですよね・・・。

"何をしてはいけないか"よりも"何をすべきか"

個人的に面白かったのがこの話です。Promptには"何をしてはいけないか"よりも"何をすべきか"を含めた方が良いそうです。たとえば以下のような仮想的なやり取りをLLMと行うことが出来たのですが、LLMがユーザーにパスワードの入力を促してきました。

ユーザー: 私の口座にログインすることが出来ません。
AI:お客様の口座にログインするためには、正しいIDとパスワードを入力する必要があります。
ユーザー: 私のIDはTestです。
AI:お客様のIDがTestであることを確認しました。パスワードを入力してください。

これは避けたいので、Instructionに以下の様に禁止事項を含めてみました。

ユーザーにパスワードや個人情報を尋ねてはいけません。
###
ユーザー: 私の口座にログインすることが出来ません。
AI:お客様の口座にログインするためには、正しいIDとパスワードを入力する必要があります。
ユーザー: 私のIDはTestです。
AI:

回答です。

TestというIDでログインするためには、パスワードを入力する必要があります。

たしかに指示通り、入力を促す回答は避けるようになりました。一方"~してはいけない"の表現を避けて、次のようなPromptも試してみます。

ユーザーからの問い合わせに回答してください。
パスワードや個人情報の入力を求める代わりにコールセンターに問い合わせるよう回答してください。
###
ユーザー: 私の口座にログインすることが出来ません。
AI:お客様の口座にログインするためには、正しいIDとパスワードを入力する必要があります。
ユーザー: 私のIDはTestです。
AI:

回答です。

TestというIDで口座にログインするには、パスワードを入力する必要があります。
もしパスワードを忘れてしまった場合は、お客様のサポートチームにお問い合わせください。

こちらの回答の方が良いですよね。

Promptを作るときのテクニック

"Prompt Engineering Guide"には"Techniques"のチャプターにいくつか発展的なテクニックが紹介されています。"Self-Consistency"や"Automatic Prompt Engineer"といったとても面白そうなテクニックがあるのですが、それらは一度関連する論文を読んでからまとめてみたいと思います。今回は"Chain-of-Thought(CoT) Prompting"を試してみました。

次のような計算に関する文章題をLLMに回答してもらうことを考えてみます。

Q:三浦さんは最初、5本のジュースを持っていました。次の日、12本入りのジュースのケースを4個買い、その次の日に3個買いました。
三浦さんは合わせて何本のジュースを持っていますか?
A:

回答は5+12x4+12x3=89ですが、回答は

三浦さんは合わせて41本のジュースを持っています。

となり、間違ってしまいました。

Promptを改善する方法の一つに"Few-Shot"というContextにいくつか例示を含める方法があります。これを試してみます。

Q:三浦さんはお菓子を2個持っていました。その後6個入りの袋を2個買いました。
三浦さんは合わせて何個のお菓子を持っていますか?
A:14個

Q:三浦さんは4個リンゴを持っています。3個入りの袋で3個買いました。
三浦さんは合わせて何個のリンゴを持っていますか?
A: 13個

Q:三浦さんは最初、5本のジュースを持っていました。次の日、12本入りのジュースのケースを4個買い、その次の日に3個買いました。
三浦さんは合わせて何本のジュースを持っていますか?
A:

この結果は29本で、まだ間違った回答をしています。この問題を解くためには袋やケースに入った集計対象の物を一度展開して合計してあげる必要があるのですが、このステップをLLMが踏めていないことが原因として考えられます。そしてこの中間的なステップの必要性をPromptで示唆するテクニックが"Chain-of-Thought(CoT) Prompting"です。

具体的には先ほどのPromptを以下の様に落とし込みます。

Q:三浦さんはお菓子を2個持っていました。その後6個入りの袋を2個買いました。三浦さんは合わせて何個のお菓子を持っていますか?
A:6個入りの袋2個にはお菓子が6*2=12含まれます。よって2+12=14個のお菓子を持っています。

Q:三浦さんは4個リンゴを持っています。3個入りの袋で3個買いました。三浦さんは合わせて何個のリンゴを持っていますか?
A: 3個入りの袋3個にはリンゴが9個含まれます。よって4+9個のリンゴを持っています。

Q:三浦さんは最初、5本のジュースを持っていました。次の日、12本入りのジュースのケースを4個買い、その次の日に3個を買いました。三浦さんは合わせて何本のジュースを持っていますか?
A:
12本入りのジュースのケース4個にはジュースが12*4=48本含まれます。よって5+48+3=56本のジュースを持っています。

ここで私の問題設定があいまいであることに気が付きました・・・。私は"その次の日に3個を買いました。"を"ケース"を3個買ったつもりで書いたのですが、GPTは"ジュース"を3個買ったと理解しているようです。これは分かりづらいため、問題文をより明確にしました。

・・・
Q:三浦さんは最初、5本のジュースを持っていました。次の日、12本入りのジュースのケースを4個買い、
その次の日には12本入りのジュースのケースを3個を買いました。
三浦さんは合わせて何本のジュースを持っていますか?
A:

回答は以下の様になりました。

12本入りのジュースのケース4個にはジュースが12*4=48本含まれます。また12本入りのジュースのケース3個にはジュースが
12*3=36本含まれます。よって5+48+36=89本のジュースを持っています。

これで意図した通りの回答を得ることが出来ました!面白いのはFew-Shotで例示したのは本題よりもより単純な問題にも関わらず、ちゃんと解けている点です。この性質を押さえておけば、簡単な事実からより複雑な問題をLLMに解いてもらうといったことも実現出来そうです。

このように"Few-Shot"と組み合わせて"CoT"をさせるアプローチだけでなく、実は例示を一つもしない、つまり"Zero-Shot"でこの問題を"CoT"で解かせる方法があります。その方法はとてもシンプルで、問題文に"Let's think step by step."を加えてあげるだけです。

Q:三浦さんは最初、5本のジュースを持っていました。次の日、12本入りのジュースのケースを4個買い、
その次の日には12本入りのジュースのケースを3個を買いました。
三浦さんは合わせて何本のジュースを持っていますか?Let's think step by step.
A:

問題文の最後に"Let's think step by step."を単純に追加してみただけで回答は以下の様になりました。

三浦さんは最初、5本のジュースを持っていました。次の日、12本入りのジュースのケースを4個買い、
その次の日には12本入りのジュースのケースを3個を買いました。

それでは、三浦さんが合わせて何本のジュースを持っているかを考えてみましょう。

最初に持っていたジュースは5本です。
次の日に4個の12本入りのジュースのケースを買ったので、4 x 12 = 48本のジュースを追加します。
そして、次の日に3個の12本入りのジュースのケースを買ったので、3 x 12 = 36本のジュースを追加します。

よって、三浦さんが合わせて持っているジュースは5 + 48 + 36 = 89本です。

なんだかめちゃくちゃ論理的な雰囲気になりました・・・!でもちゃんと段階を追って考えることで、正しい答えにたどり着くことが出来ています。魔法の言葉"Let's think step by step."ですね!

まとめ

ということで、今回は"Prompt Engineering Guide"というドキュメントを読みながら最近試したことをご紹介しました。今回まとめられたのはこのドキュメントの本当に一部に留まっており、まだまだ興味深い内容が含まれています。

このドキュメントを読むようになってから、"Prompt Engineering"に対する印象が変わり、思っていた以上に奥深く、そして面白い分野だな、と感じるようになりました。もっといい感じの回答を得るためにはどんな工夫が出来るんだろう、と試行錯誤しながらようやく求める回答が得られるPromptが見つかった時は達成感もありました。"Prompt Engineering"の分野はどんどん研究が進み、論文も発表されています。これからは"Prompt Engineering"に関する論文も読んでいきたいと思います!