この記事は Anthropic Advent Calendar の8日目の投稿です。
LLMを実行して意図した結果を得るためには Prompt をどのように記述するか、いわゆる Prompt Engineering が重要になります。Claudeにも OpenAI系のGPT-4/3.5 とは少し異なるテクニックがあります。基本的なものについては @kiiwamitさんが書かれている Anthropic Claude - Prompt Design大全を参照いただき、本稿では応用編と言うことで実行時の精度を上げるテクニックについてご紹介したいと思います。ここで取り上げるLLMの精度とは以下を指します。
- 安定して指定されたOutputフォーマットで出力できる
- 指示を無視しない
- 自然言語で記述された条件を正しく理解できる
- より深い推論を行える
現状、LLMの実行においては出力が "安定している"ことが強い意味を持ちます。従来のプログラムコードであれば、入力が同じであれば同じ出力が出てくることが期待されますが、LLMは違います。生成するテキストのランダム性を下げるには temperature を下げることが推奨されていますが、例え temperature=0 にしても Claude2の出力は毎回変化します。どの程度変化するかはPromptで与えるゴールと処理するトークン量に依存します。例えば、LLMに日本語の文法をチェックさせる次のようなPromptですと、変数として与えるテキストのサイズが大きくなると(数万トークン以上)、Completionで返される結果にはかなり揺らぎがありました。
Human: あなたは企業のxxx担当者でxxの専門家です。ルール(<rules>)に基づいて文書(<document>)に対してチェックを行って下さい。
<document>
{doc}
</document>
## 文書を校正する際に考慮すべき基本ルール
<rule>
### 日本語の文法として正しい
誤字・脱字がなく、日本語の文法として正しい文章であること。句読点を適切に使用すること。助詞を正確に使用すること。
・・・(省略)
</rule>
## その他のルール
- 次のXMLフォーマットで回答して下さい(正しいXML文書で出力すること。タグ内のテキストに改行は含めない)
<comments>
<comment>
<body>指摘した箇所が分かるように、30文字以内を目安に元文章からコピーする</body>
<reason>修正すべき点とその理由を記載する。できれば代替表現も記載する。</reason>
</comment>
</comments>
・・・(省略)
##指示
文書(<document>)に対して、ルール(<rule>)に基づいて文書の文法的なチェックと校正を行い、修正すべき点を報告して下さい。
軽微な修正は無視して、重要な修正点から順に報告して下さい(指摘の数は最大15で、それ以上は回答不要)。
Assistant:
Claude2では最大10万トークン(※2023年11月にGAされたClaude2.1では最大20万トークン)のInputを処理することができますが、トークン数が大きくなると精度が下がる傾向がありました。Anthropic社によるとClaude2.1ではPromptを工夫することで精度を維持できるとありますが、同様な傾向はGPT-4にもあるようですので、トークン数と精度の関連性については今後の動向に注意を払いたいです。
参考:
Pressure Testing Claude-2.1
Context Utilisation: Claude-2 vs GPT-4-turbo
Long context prompting for Claude2.1
実際に Claude2 を使ったPrompt開発の経験から言いますと、大きな日本語の文書(2−3万トークン以上)を扱うとLLM側の指示の遵守率が下がり、精度問題が無視できなくなりました。具体的には以下の2つが課題となりました。
正しくないXMLフォーマットで出力される
例えば以下のように出力フォーマットを指定したとします。
次のXMLフォーマットで回答して下さい(正しいXML文書で出力すること。タグ内のテキストに改行は含めない)
<comments>
<commnet>
<body>指摘箇所が分かるように、50文字以内を目安に元文章からコピーする</body>
<reason>修正すべき点とその理由を記載する。</reason>
</comment>
</comments>
ClaudeはXMLタグの処理に強く、安定してXMLベースの文字列を出力できるのですが、Promptのトークン数が大きくなるとたまに以下のようなフォーマットで出力することがあります。 この例の出力では1つの <comments>タグの中に複数の<comment>を含めて欲しいのですが、1つの<commnet>しか含めていません。正しいXMLなのですが、このフォーマットだとCompletionを後続の処理系でプログラム的に処理(XMLをパースして値を取得する等)する場合には、上手く処理できず困ったことになります。
<comments>
<comment>
<body>AIシステムへのアクセスを厳格に制御するための重要な手段です。</body>
<reason>主語が明確でないため、「これらは」と補うべき</reason>
</comment>
</comments>
<comments>
<comment>
<body>ユーザーからの明示的な同意を取得し、データの収集と利用に関する透明性を提供します。</body>
<reason>「明示的な」は不要な重複</reason>
</comment>
</comments>
また、CompletionをXML文書とするためにPromptに以下のような指示を含めておいたとしても、トークン数が増えるとこの指示を無視して前置き文書を入れてくることがあります。
前置きなしで回答を出力して下さい。
正しいXML文書で出力して下さい
"前置きありの出力"とは以下のようなもので、このケースもCompletionがXML文字列であることを期待して後続で処理しようとすると不正なXML文字列となりエラーとなります。
はい、分かりました。日本語の文法をルールに従ってチェックして報告します。
<comments>
<comment>
<body>AIシステムへのアクセスを厳格に制御するための重要な手段です。</body>
<reason>主語が明確でないため、「これらは」と補うべき</reason>
</comment>
・・・
指示を無視する
Promptに含まれるトークン数が大きくなるとLLMは読み込んだテキストを忘れてしまうのか、大胆に指示を無視する状況に陥ることがありました。以下は文書内の誇張表現をチェックするPromptで、<document></document>で囲まれたテキストのみをチェック対象にするように指示していますが、これを無視して<rule></rule>タグ内に記述されたテキストも対象にして結果を返すことがありました。<rule>タグ内に記載されたテキストは単に例示しているものなので、これらはチェック対象とすべきではありません。
トークン数が3万を超えない時はこうした結果が返ってくることはなかったので、明確にトークン数が影響していると考えられます。
Human: あなたは日本語の先生です。文書(<document>)内の誇張表現を見つけて、代替表現を回答して下さい。
<document>
{doc}
</document>
以下は、誇張表現をチェックするためのルールです。
<rule>
日本語の最上級表現は、何かが他のものよりも「最も」優れている、または「一番」であるということを示す表現です。以下は、日本語の最上級表現の例とその説明です。
### 形容詞の最上級
基本形: 〜い形容詞(例:高い、安い)
最上級形: 一番 + 基本形
・・・
### 注意点
「最も」は形容詞や形容動詞にも使えますが、一般的には「一番」の方が口語でよく使われます。
・・・
</rule>
・・・ (省略)
Claude2 でPrompt実行の精度を高めるテクニック
ここからは、Claude2実行時の精度を高めるために試行錯誤した結果として、実際に効果があったテクニックをいくつかご紹介していきたいと思います。
1. サンプルを含める
出力フォーマットの意図を明確にして安定化させるための基本的な方法としては、回答例としてサンプルをPromptに含める方法があります。いわゆる one/few shot promptingと言われるテクニックです。
# 回答例
<example>
<comments>
<comment>
<body>xxxxxxxxxxxxxxxx</body>
<reason>xxxxxxxxxxxxxxxxxxxx</reason>
</comment>
<comment>
<body>xxxxxxxxxxxxx</body>
<reason>xxxxxxxxxxxxxxx</reason>
</comment>
</comments>
</example>
出力が安定しない場合や、LLMが意図したフォーマットでCompletionを出力しない場合は、サンプルを提示することでLLMに出力フォーマットを理解させます。
このテクニックでは出力されるXMLタグのフォーマットは安定化できるのですが、前置きのテキストが出力される問題には完璧には対応できません(トークン数が大きくなると、無視されることが多くなりました)。
2. タグを Assistant: の後に配置する
Completionに前置きを含めずにXMLタグだけを出力することを強制したい場合は、 Assistant: の後にタグを含める方法が有効です。
1)Assistant: の後に '<' のみを含める
タグの最初の1文字目である '<' を Assistant: の後に記述しておくことで、前置きテキストが出力されるのを防ぎます。このテクニックでは Completion として出力されるテキストには先頭の '<' が含まれないことに注意して下さい。
次のXMLフォーマットで回答して下さい(正しいXML文書で出力すること。タグ内のテキストに改行は含めない)
<comments>
<comment>
<body>指摘した箇所が分かるように、30文字以内を目安に元文章からコピーする</body>
<reason>修正すべき点とその理由を記載する。できれば代替表現も記載する。</reason>
</comment>
</comments>
# 指示
<document> 内の文書に対して文法的な校正を行い、修正すべき点を報告して下さい。
Assistant:<
=== 以下は出力されるCompletion ====
comments><comment>xxxxx ・・・・ <comments>
2) Assistant: の後に ダミーのタグを含める。
このテクニックは <answer>というダミーのタグを最初に出力しておくことで、XMLタグ以外が出力されることを防ぎます。この方法ではそのまま出力させると最後に </answer>タグが出力されてしまい正しいXML文書とならないので、"Stop sequence" に "Human" に加えて "</answer>" を追加します。"Stop sequence" はLLMによる文字出力を制御するために使用されるパラメータで、"Stop sequence"として登録された文字列をLLMが生成するとそこでCompletionの生成は停止されます。
次のXMLフォーマットで回答して下さい(正しいXML文書で出力すること。タグ内のテキストに改行は含めない)
<answer>
<summary>全体の総評</summary>
<comments>
<comment>
<body>指摘した箇所が分かるように、30文字以内を目安に元文章からコピーする</body>
<reason>修正すべき点とその理由を記載する。できれば代替表現も記載する。</reason>
</comment>
</comments>
</answer>
#指示
<document> 内の文書に対して文法的な校正を行い、修正すべき点を報告して下さい。
Assistant:<answer>
=== 以下は出力されるCompletion ====
<comments><comment>xxxxx ・・・・ <comments>
3. Promptを英語で記述する
Claudeは問題なく日本語のPromptを理解しますが、英語のデータでより多く学習されていますので、比較した場合は英語のPromptの方が精度が高い(指示をより正確に理解する/深い推論を行う)と言われています。Promptは理解し易いこと/修正し易いことが重要ですので、日本語の環境で利用されるPromptはできるだけ日本語で作成することをお勧めしますが、どうしても精度が出ない場合は奥の手としてPromptを英語で記述するテクニックがあります。
英語でPromptを記述する場合に大事なのは、よほど英語に自信がある方でない限りは、DeepLなどの精度の高い自然言語翻訳エンジンを使う方法が良いと思います。LLMは多少文法的におかしくてもTYPOがあってもだいたい意味を理解してくれますが、より正しく分かりやすい英語で記述した方が精度は高まります。
また、Prompt全体を英語化するのではなく、部分的に英語を利用するアプローチでも問題なく機能します。
1) Prompt内で日本語と英語を混在させる例
以下は文書内の誇張された表現をチェックするPromptですが、ルール部分のみを英語化しています。最初は全て日本語で記述していたのですが、LLMの精度における課題で説明したように3万トークン以上ある日本語文書をチェックさせると、ドキュメント本体だけではなくルール部分に記載したサンプルの日本語文字列を回答に含めるようになったため、苦肉の策としてルール部分を英語化することで対応しました。
以下はあなたがこのタスクで参照すべきドキュメントです。
<document>
{doc}
</document>
以下は、誇張表現をチェックするためのルールです。
<rule>
1. Consideration of comparative expressions
- When making comparisons with other products or services, confirm that fair and accurate comparisons are made.
- Clarify the criteria and conditions of comparison.
2. avoid absolute expressions
-Avoid absolute expressions such as "best," "most," "only," etc. If such expressions are used, be specific.
- If such expressions are used, provide specific rationale.
・・・
</rule>
・・・
2) 避けた方がよい日英混在
基本的には英語と日本語を混在させても、ClaudeはPromptの指示を正しく理解してくれると思いますが、英語と日本語を混在させた事で著しく推論の精度が落ちるケースも経験しました。以下は マイクロソフト社が提供する Semantic Kernelライブラリが提供する Sequential Planner という"登録したスキル(外部ツール)の情報を元に与えられたタスクを実現するための計画を立てる"ためのPromptの一部(出力するXMLフォーマットを規定している箇所)なのですが、一部の記述を英語から日本語に変更した所、推論の精度が低下しました。変更したのはコメント部分で、オリジナルでは "reason for taking step" と記載されていたのですが、それを日本語で出力させるために "ステップを選択した理由を記載" と変更しました。
LLMの内部動作はブラックボックスなので推論精度が低下した原因はよく分からないのですが、コメント部分はタスクを実現するために必要なスキルを選択する推論の結果であるため、ここをより賢い英語で処理できず日本語で処理しなければならなくなったことが原因なのかと想像しました。つまり英語で推論してから日本語に翻訳しているのではなく、最初から推論そのものを日本語で処理するように変更したため精度が低下したという仮説です。
All plans take the form of:
<root>
<plan>
<!-- ... ステップを選択した理由を記載 ... -->
<function.{{{{FullyQualifiedFunctionName}}}} type="short" name_jp="xx" ... setContextVariable="XX_XX" />
<!-- ... ステップを選択した理由を記載 ... -->
<function.{{{{FullyQualifiedFunctionName}}}} type="short" name_jp="xx" ... setContextVariable="XX_XX" />
<!-- ... ステップを選択した理由を記載 ... -->
<function.{{{{FullyQualifiedFunctionName}}}} type="short" name_jp="xx" ... />
<!-- ... ステップを選択した理由を記載 ... -->
<function.{{{{FullyQualifiedFunctionName}}}} type="long" name_jp="xx" ... appendToResult="XX_XX” />
(... etc ...)
</plan>
</root>
<!-- END -->
4. 人間が理解し易い表現を使用する
Sequential Planner や ReAct 等のLLMに推論を行わせるAGI的なアプローチをLLMに行わせたいケースでは、自然言語で記載されたロジックをLLMが理解する必要があります。Claude2 は単純な条件分岐だと問題なく理解してくれますが、条件の書き方が悪いと理解の精度が落ちます。
基本的な考え方は、LLMによる思考や推論は人間の脳の動きを模倣していますので、人に分かりやすい表現はLLMも理解し易いと言うことです。
実例を挙げて説明します。以下は Sequential Planner で対象スキルを選択するための条件文です。商品マニュアルに対して、商品の特徴によってどのチェック(スキル)を実施するかを推論します。
基本は以下の観点で商品マニュアルに対して内容の確認を行う。
- 文法のチェック
- 誇張表現のチェック
- ガイドラインによるチェック
商品が 冷蔵庫、洗濯機、テレビ、パソコン、プリンター の場合は、「家庭用電化製品チェックリスト」によるチェックを行う。
商品がこれら以外の場合は「家庭用電化製品チェックリスト」によるチェックは実施しない。
商品分類が"携帯電話とスマートフォン"の場合は、追加で"OS仕様との整合性チェック"を行う。
一般顧客向け商品ではない場合は、"ガイドラインによるチェック"は行わない。
対象とする商品マニュアルの特徴
- リクエストID(request id): {request_id}
- 資料のID: {doc_id}
- 商品: {info1}
- 商品分類: {info2}
- 一般顧客向け商品かどうか? : {decision_of_btoc}
- OS仕様のID: {os_doc_id}
上記の表現だとたまに正しくない判断を行うことがあり、精度を向上させるために以下のように条件文を修正してみました。
商品マニュアルの特徴は以下の通りです。
- リクエストID(request id): {request_id}
- 資料のID: {doc_id}
- 対象とする商品は、一般顧客向け商品かどうか? : {decision_of_btoc}
次のルールに従って、マニュアルに対するチェックを行って下さい。
- 文法をチェックする。
- 誇張表現がないかをチェックする。
- 「ユーザー向けチェックリスト」を使用してチェックする。
- ガイドラインによるチェックを行う。ただし、一般顧客向け商品ではない場合はこのチェックは行なわない。
対象とする商品は {info1} です。以下のルールを適用してチェックを追加して下さい。
- 商品が 冷蔵庫、洗濯機、テレビ、パソコン、プリンターの場合、「家庭用電化製品チェックリスト」を使用してチェックする。
商品分類は {info2} です。以下のルールを適用してチェックを追加して下さい。
- 商品分類が"携帯電話とスマートフォン"の場合は、追加で"OS仕様との整合性チェック"を行う。
- OS仕様のID: {os_doc_id}
この記述に変更してからはほぼ確実に意図通りの判断をしてくれるようになりました。
いかがでしょうか? 同じ条件を表している(つもり)ですが、どちらの表現の方が明快で分かりやすいと思いますか。ちなみに、一緒に開発していた同僚からはずいぶんと分かりやすくなったとのコメントをもらいました。通常のドキュメント作成と同じように、記述したテキストを他のメンバーにレビューしてもらう事も複雑なPromptの開発では大事なのかと考えます。
5.<scratchpad> の利用
大きな入力トークンを処理する際にClaudeの回答の精度を上げる方法としては <scratchpad>タグを利用する方法があります。このスクラッチパッドは人間で言えばメモ書きのような使い方で、LLMが読んだテキストを忘れないように一度 <scratchpad> </scratchpad> 領域に記録を残しておくように指示を出します。
参考: Prompt engineering for Claude's long context window
こちらも具体例で説明します。さきほどから何回か登場している日本語の文法をチェックするPromptです。
以下はあなたがこのタスクで参照すべき文書です。
<document>
xxx
</document>
以下は文書を校正する際に考慮すべき基本ルールです。
<rule>
・・・(省略)
</rule>
あなたは日本語の先生で文法の専門家です。
次のXMLフォーマットで回答して下さい(正しいXML文書で出力すること。タグ内のテキストに改行は含めない)
<comments>
<comment>
<body>指摘した箇所が分かるように、30文字以内を目安に元文章からコピーする</boby>
<reason>修正すべき点とその理由を記載する。できれば代替表現も記載する。</reason>
</comment>
</comments>
Human: <document> 内の文書に対してルールに従って文法的な校正を行い、修正すべき点を報告して下さい。
軽微な修正点は無視して重要な修正点を5つ報告して下さい。
Assistant:
上記のPromptの実行結果のサンプルです。
Assistant:
<comments>
<comment>
<body>AIシステムの高度な能力は、悪意を持つ個人や組織による誤用の懸念を引き起こしているます。</body>
<reason>「懸念を引き起こしている」はやや不自然な表現です。「懸念を招いている」などの表現がより適切です。</reason>
</comment>
<comment>
<body>偽の情報を拡散する手段として悪用される可能性があるます。</body>
<reason>「可能性がある」は必要以上に弱い表現です。「悪用される恐れがある」などとするとより適切です。</reason>
</comment>
<comment>
<body>AIを使って作られた偽の情報が社会を混乱させる可能性があります。</body>
<reason>「可能性がある」は必要以上に弱い表現です。「社会を混乱させる恐れがある」などとするとより適切です。</reason>
</comment>
</comments>
以下のように <scratchpad> を使うようにPromptを変更してみます。
Human: <document> 内の広告文書に対してルールに従って文法的な校正を行い、修正すべき点を報告して下さい。
最初に<scratchpad>に修正候補になりそうな箇所を全て書き出して、その後に重要な修正点を5つ報告して下さい。
このバージョンでの実行結果(Completion)は以下のようになり、<scratchpad> に中間結果を記述するように指示を出すだけで指摘事項の内容と表現が大きく変更されることが分かると思います。指摘事項も私にはより的確になったように思えますが、いかがでしょうか。この例ですと微妙な差かもしれませんが、Inputトークン数が大きい場合に少しでもLLMの精度を上げたい時には試して見る価値はあるテクニックだと思います。
Assistant:
<scratchpad>
・「AIシステムの高度な能力は、悪意を持つ個人や組織による誤用の懸念を引き起こしているます」→「懸念を引き起こしております」など、一貫した文体にする
・「不正な行為を罰するべきです」→「不正な行為を罰すべきである」など、です・ます調に統一
・「AIを使って作られた偽の情報が社会を混乱させる可能性があります」→「AIを使って作られた偽の情報により社会が混乱する可能性があります」など、主語と動詞の一致を確認
・「政府、研究機関の協力」→「政府や研究機関の協力」など、助詞「や」を使用して整理
</scratchpad>
<comments>
<comment>
<body>AIを使って作られた偽の情報が社会を混乱させる可能性があります</body>
<reason>主語と動詞が一致していないため、「AIを使って作られた偽の情報により社会が混乱する可能性があります」などとする。</reason>
</comment>
<comment>
<body>AI技術が広まるにつれて、法律や規制を整備し、不正な行為を罰するべきです</body>
<reason>文体が他の部分と異なるため、「不正な行為を罰すべきである」などです・ます調に統一する。</reason>
</comment>
<comment>
<body>政府、研究機関の協力</body>
<reason>助詞「や」を使用して「政府や研究機関の協力」などとすることで整理しやすくする。</reason>
</comment>
</comments>
まとめ
現状のtokenizerでは日本語は英語と比べてトークン効率が悪いため、少し大きなドキュメントをPromptで処理しようとすると直ぐにInputが数万トークン以上になります。大容量のトークンを取り扱える Claude はこの点で非常に有利なLLMなのですが、トークン数が増えると精度の影響を受けやすくなります。本稿で紹介したようなテクニックを使って精度を向上させることも Prompt Engineering の醍醐味ではありますが、基本はできるだけInputトークン数を少なくする方向に努力される事をお勧めします。Inputトークン数を小さくすることで、精度が安定するだけでなくコストと処理時間も小さくなり、さらにTPM(Token per minutes)のようなクォータ制限にも強くなりメリットばかりではないでしょうか。
本稿で試したテクニックは Claude2 で有効なものですが、LLMの進化のスピードはとても速いので、最新のClaude2.1では既に陳腐化しているものがあるかもしれません。基本的にはバージョンが上がれば精度は向上していくものなので、よりシンプルなPromptで同様なことができるようになっていくと思います。ここで紹介したテクニックが皆さんのPromptで有効なものかどうかは、必ず実際の環境で試してみることを忘れないで下さい。
おまけ
Claude2.1 で登場した system prompts ですが、system prompts に指示を記述することで、Claudeがルールや指示に従う能力が向上するとガイドされています。実際にClaude2までの Human: xx 、Assistat: xx で記載された従来のPromptを system prompts 対応に書き直して検証してみた所、本稿で挙げた課題である "正しくないXMLフォーマットで出力される"、"指示を無視する"に対して効果があるように感じられました(軽く試した程度なので、定量的な確認までは行えていません)。利用にあたってデメリットは特にないと思いますので、今後はこの system prompts を積極的に利用すべきと考えます。
[元のプロンプト]
Human:あなたは企業のxx担当者でxxの専門家です。<document> 内の文書に対してルールに従って文法的な校正を行って下さい。
<document>
{doc}
</document>
以下は文書を校正する際に考慮すべき基本ルールです。
<rule>
・・・
</rule>
## その他のルール
- 次のXMLフォーマットで回答して下さい(正しいXML文書で出力すること。タグ内のテキストに改行は含めない)
<comments>
<comment>
<body>指摘した箇所が分かるように、30文字以内を目安に元文章からコピーする</body>
<reason>修正すべき点とその理由を記載する。できれば代替表現も記載する。</reason>
</comment>
</comments>
##指示
<document> 内の文書に対してルールに従って文法的な校正を行い、修正すべき点を報告して下さい。
軽微な修正は無視して、重要な修正点から順に報告して下さい(指摘の数は最大15で、それ以上は回答不要)。
Assistant:
[system prompts対応版]
以下はあなたがこのタスクで参照すべき文書です。
<document>
{doc}
</document>
以下は文書を校正する際に考慮すべき基本ルールです。
<rule>
・・・
</rule>
あなたは企業のxx担当者でxxの専門家です。
次のXMLフォーマットで回答して下さい(正しいXML文書で出力すること。タグ内のテキストに改行は含めない)
<comments>
<comment>
<body>指摘した箇所が分かるように、30文字以内を目安に元文章からコピーする</body>
<reason>修正すべき点とその理由を記載する。できれば代替表現も記載する。</reason>
</comment>
</comments>
Human: <document> 内の文書に対してルールに従って文法的な校正を行い、修正すべき点を報告して下さい。
軽微な修正は無視して、重要な修正点から順に報告して下さい(指摘の数は最大15で、それ以上は回答不要)。
Assistant: