Chain-of-Thoughtプロンプティングの活用と評価戦略

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

Chain-of-Thoughtプロンプティングの活用と評価戦略

大規模言語モデル(LLM)の推論能力は、プロンプトの設計によって大きく左右されます。特に、複雑な多段階推論を要するタスクにおいてその真価を発揮するのがChain-of-Thought (CoT) プロンプティングです。CoTは、モデルに最終的な結論だけでなく、その結論に至るまでの思考プロセスを段階的に出力させることで、より正確で信頼性の高い応答を引き出す手法です。本記事では、CoTプロンプティングの活用法、評価戦略、および改良サイクルについて解説します。

1. ユースケース定義

CoTプロンプティングは、以下のような複雑な推論タスクにおいて特に有効です。

  • 多段階の計算問題: 複数のステップを経て解かれる数学的な問題やロジックパズル。

  • 複雑な意思決定支援: 複数の制約や情報源を考慮して最適な選択肢を導き出すシナリオ。

  • コードのデバッグと生成: エラーの原因分析や、特定の要件を満たすコードの段階的な構築。

  • 法的文書の解釈: 複数の条項や判例を比較検討し、特定の状況への適用を判断するタスク。

これらのユースケースでは、単一の最終回答だけでなく、モデルの思考プロセスを可視化することで、その信頼性を評価しやすくなります。

2. 制約付き仕様化(入出力契約)

CoTプロンプティングを効果的に活用するためには、LLMとの明確な入出力契約を定義することが重要です。

入力契約

  • フォーマット: JSON、YAML、プレーンテキストなど、構造化されたデータ形式を推奨。

  • データ型: 各フィールドの期待されるデータ型(文字列、数値、真偽値など)を明示。

  • 制約: 入力値の範囲、必須フィールド、文字数制限などを指定。

  • :

    {
      "task": "レシピ作成",
      "ingredients": ["鶏肉", "玉ねぎ", "じゃがいも", "カレールー"],
      "cuisine_style": "家庭料理",
      "allergens_to_avoid": ["乳製品"]
    }
    

出力契約

  • フォーマット: 特定のJSON構造、マークダウン形式の箇条書き、XMLなど、機械処理可能な形式を推奨。

  • 思考プロセスの明示: [思考プロセス]Thought: のような特定のタグを使用して、モデルの推論ステップを区別させる。

  • 最終回答の明示: [最終回答]Answer: のようなタグで最終結果を特定させる。

  • :

    {
      "thought_process": [
        "与えられた材料(鶏肉、玉ねぎ、じゃがいも、カレールー)と料理スタイル(家庭料理)から、カレーライスが適切と判断。",
        "アレルギー物質(乳製品)の回避指示があるが、市販のカレールーには乳製品が含まれる可能性があるため、成分を確認するか、乳製品不使用のルーを選択する必要がある。",
        "乳製品不使用のルーを前提とし、一般的なカレーの調理手順を記述。",
        "材料のカット、炒める、煮込む、ルーを加える、といった順序でステップを構成。",
        "最終的なレシピをまとめる。"
      ],
      "recipe_title": "乳製品不使用のチキンカレー",
      "ingredients_list": [
        "鶏もも肉: 300g",
        "玉ねぎ: 1個",
        "じゃがいも: 2個",
        "乳製品不使用カレールー: 1箱",
        "水: 600ml",
        "サラダ油: 大さじ1"
      ],
      "instructions": [
        "1. 鶏肉、玉ねぎ、じゃがいもを一口大に切る。",
        "2. 鍋にサラダ油を熱し、鶏肉、玉ねぎ、じゃがいもを炒める。",
        "3. 水を加えて煮込み、材料が柔らかくなったら火を止める。",
        "4. カレールーを溶かし入れ、再び弱火でとろみがつくまで煮込む。",
        "5. ご飯とともに盛り付ける。"
      ],
      "final_answer_format": "JSON"
    }
    

失敗時の挙動

  • 指定されたフォーマットに従えない場合や、推論が行き詰まった場合は、特定のエラーメッセージやタグ(例: {"error": "推論に失敗しました。", "reason": "思考プロセスが途絶しました。"})を出力する。

  • 無関係な情報や不適切な内容が出力された場合は、それを明示するフラグ(例: "unrelated_content_detected": true)を含める。

禁止事項

  • 幻覚(Hallucination)の出力。

  • 出力フォーマットの逸脱。

  • 指定された思考ステップを省略すること。

  • 与えられた情報源以外の知識を推論ステップに勝手に含めること(要約タスクなどにおいて)。

3. プロンプト設計

以下に、CoTプロンプティングを含む3種類のプロンプト案を示します。

3.1. ゼロショットプロンプト

モデルに特別な指示を与えず、タスクのみを提示します。

以下の質問に答えてください。
質問: リンゴが5個、バナナが3個あります。そこからリンゴを2個食べました。残りの果物の合計は何個ですか?

3.2. 少数例プロンプト (Few-shot Prompting)

入力と出力のペアをいくつか提供することで、モデルにタスクのパターンを学習させます。

質問: 店にオレンジが10個、レモンが4個あります。そこからオレンジを3個売りました。残りの果物の合計は何個ですか?
回答: 10 - 3 = 7。7 + 4 = 11。合計11個です。

質問: 箱に赤色のボールが8個、青色のボールが6個あります。そこから赤色のボールを3個取り出しました。残りのボールの合計は何個ですか?
回答: 8 - 3 = 5。5 + 6 = 11。合計11個です。

質問: リンゴが5個、バナナが3個あります。そこからリンゴを2個食べました。残りの果物の合計は何個ですか?
回答:

3.3. Chain-of-Thought制約型プロンプト

モデルに思考プロセスを段階的に示すよう明示的に指示し、出力フォーマットを制約します。

あなたは論理的な思考が得意なアシスタントです。
以下の質問に対し、[思考プロセス]のセクションでステップバイステップの推論を示し、その後に[最終回答]のセクションで結論を簡潔に述べてください。

質問: リンゴが5個、バナナが3個あります。そこからリンゴを2個食べました。残りの果物の合計は何個ですか?

[思考プロセス]

1. まず、最初のリンゴの数を確認します。

2. 次に、食べたリンゴの数を元のリンゴの数から引いて、残りのリンゴの数を計算します。

3. バナナの数は変わらないことを確認します。

4. 最後に、残りのリンゴの数とバナナの数を合計して、残りの果物の総数を計算します。

[最終回答]

4. 評価

CoTプロンプティングの評価は、最終回答の正確性だけでなく、思考プロセスの論理性と一貫性も考慮する必要があります。

4.1. 評価シナリオ

  • 正例: 上記の果物計算問題のように、明確な答えが導かれるシンプルな問題。

  • 難例:

    • 曖昧な指示: 「できるだけ多く」や「適切な」など、解釈の余地がある表現を含む問題。

    • 多数の選択肢: 複数の実現可能な解決策が存在し、最適なものを選択する必要がある問題。

    • 複数ステップの依存関係: 前のステップの結果が次のステップに大きく影響する複雑な推論チェーン。

  • コーナーケース:

    • 例外的な条件: 「もしリンゴが0個だったら?」のように、通常の流れから外れる特殊なケース。

    • 矛盾する情報: プロンプト内に意図的に矛盾する情報を含め、モデルがそれをどのように処理するかを評価。

    • 制約違反: 出力フォーマットや禁止事項に違反するような応答を誘発するプロンプト。

4.2. 自動評価の擬似コード

自動評価は、最終回答と推論ステップの一部を検証するために利用されます。

import re
import json

def evaluate_cot_response(question: str, model_output: str, expected_answer: int) -> dict:
    """
    Chain-of-Thoughtプロンプトに対するモデルの応答を評価する擬似コード。

    Args:
        question (str): 評価対象の質問。
        model_output (str): LLMから得られた生の出力。
        expected_answer (int): 正しい最終回答。

    Returns:
        dict: 評価結果(スコア、エラー、詳細)。

    計算量:

        - 正規表現マッチング: O(L) where L is the length of model_output.

        - JSON解析: O(N) where N is the size of JSON.

        - 数値比較: O(1).
    メモリ条件:

        - model_outputのサイズに依存。
    """
    score = 0
    feedback = []

    # 1. 出力フォーマットの検証 (JSON形式を想定)

    try:
        parsed_output = json.loads(model_output)
        if "thought_process" not in parsed_output or "final_answer" not in parsed_output:
            feedback.append("フォーマットエラー: 'thought_process'または'final_answer'が見つかりません。")
            return {"score": score, "status": "FAIL", "feedback": feedback}

        thought_process = parsed_output["thought_process"]
        final_answer = parsed_output["final_answer"]
        score += 0.2  # フォーマットが正しい場合に部分点
    except json.JSONDecodeError:
        feedback.append("フォーマットエラー: 有効なJSONではありません。")

        # 簡易的なテキスト解析にフォールバック

        thought_process_match = re.search(r'\[思考プロセス\]\n(.*?)\n\[最終回答\]', model_output, re.DOTALL)
        final_answer_match = re.search(r'\[最終回答\]\s*(\d+)', model_output)

        if not thought_process_match:
            feedback.append("思考プロセスが見つかりません。")
        if not final_answer_match:
            feedback.append("最終回答が見つかりません。")
            return {"score": score, "status": "FAIL", "feedback": feedback}

        thought_process = thought_process_match.group(1).strip() if thought_process_match else ""
        try:
            final_answer = int(final_answer_match.group(1))
            score += 0.1 # フォーマットが完全でなくても最終回答を抽出できれば部分点
        except ValueError:
            feedback.append("最終回答が数値ではありません。")
            return {"score": score, "status": "FAIL", "feedback": feedback}

    # 2. 最終回答の正確性

    if isinstance(final_answer, (int, float)):
        if final_answer == expected_answer:
            score += 0.5
            feedback.append(f"最終回答が正確です: {final_answer}")
        else:
            feedback.append(f"最終回答が不正確です: 期待値 {expected_answer}, 実際 {final_answer}")
    else:
        feedback.append(f"最終回答が数値型ではありません: {type(final_answer)}")

    # 3. 思考プロセスの評価 (キーワード、ステップ数、論理性)

    if thought_process:

        # 例: 特定のキーワードが存在するか

        if "引いて" in thought_process or "減算" in thought_process:
            score += 0.1
            feedback.append("思考プロセスに減算の概念が含まれています。")
        if "合計" in thought_process or "足し合わせ" in thought_process:
            score += 0.1
            feedback.append("思考プロセスに合計の概念が含まれています。")

        # 例: ステップの列挙があるか(簡易的な検出)

        if re.search(r'\d+\.\s', thought_process) or (isinstance(thought_process, list) and len(thought_process) > 1):
            score += 0.1
            feedback.append("思考プロセスが複数のステップで構成されています。")
    else:
        feedback.append("思考プロセスが見つかりませんでした。")

    status = "PASS" if score >= 0.8 else "FAIL" # 閾値は調整可能
    return {"score": score, "status": status, "feedback": feedback}

# --- 採点ルーブリック例 ---


# 1. 出力フォーマットの遵守: 20% (JSON形式、必須キーの存在)


# 2. 最終回答の正確性: 50% (期待値との完全一致)


# 3. 思考プロセスの論理性/網羅性: 30%


#    - 主要なキーワード(計算操作、論理的接続詞)の存在: 10%


#    - 段階的な説明の存在(箇条書き、番号付け): 10%


#    - 不必要な脱線がないか(否定的な評価): 10%

# 実際の利用例:


# model_output_example = """


# {


#   "thought_process": [


#     "まず、元のリンゴの数5から食べたリンゴの数2を引きます。",


#     "次に、残ったリンゴの数とバナナの数3を合計します。"


#   ],


#   "final_answer": 6


# }


# """


# evaluation_result = evaluate_cot_response("果物の合計は?", model_output_example, 6)


# print(evaluation_result)

5. 誤り分析と抑制手法

CoTプロンプティングにおいても、LLMは様々な失敗モードに陥る可能性があります。

5.1. 失敗モード

  • 幻覚 (Hallucination): 誤った事実や架空のステップを思考プロセスに含める。

  • 様式崩れ (Format deviation): 指定した出力フォーマット(JSON、ステップ番号付けなど)に従わない。

  • 脱線 (Off-topic): 質問やタスクの範囲から逸脱した情報や思考プロセスを出力する。

  • 禁止事項違反: 指定された禁止事項(例: 機密情報の出力、不適切な内容)に違反する。

  • 論理の一貫性の欠如: 思考プロセスの途中で論理が破綻したり、矛盾するステップが出力される。

5.2. 抑制手法

  • System指示の強化:

    • モデルの役割を明確化: 「あなたは論理的な推論エンジニアです。」

    • 厳密なフォーマット要求: 「出力は厳密にJSON形式でなければなりません。他のテキストは一切含めないでください。」

    • 正確性への強調: 「思考プロセス内のすべての事実は、与えられた情報源に基づき正確である必要があります。」

  • 検証ステップの導入:

    • 出力パース: モデルの出力をパースし、指定フォーマットからの逸脱を検出。

    • 事実チェック: 出力された思考ステップに含まれる主要な事実を外部の信頼できる情報源と照合。

    • 論理検証: 数値計算や簡単な論理ルールに基づいて、思考プロセスの一貫性をプログラム的に検証。

  • リトライ戦略:

    • 初回の応答がフォーマット違反や論理破綻を起こした場合、エラーメッセージとともに元のプロンプトを再送し、修正を促す。

    • 「あなたの出力はフォーマット規則に違反しています。[エラー詳細]。再度、厳密にJSONフォーマットに従って出力してください。」

  • Few-shot例の質の向上: より多様で複雑な正例を少数例として提供し、モデルの汎化能力を高める。

  • 禁止事項の明示: プロンプト内で具体的な禁止事項を明確に記述する。

6. 改良と再評価のサイクル

CoTプロンプティングの性能を最大化するためには、プロンプト設計、モデル応答、評価、そして改良を繰り返すループが不可欠です。

graph TD
    A["タスク定義"] --> B("プロンプト設計");
    B --> C{"LLMへの入力"};
    C --> D["LLM応答"];
    D --> E("評価");
    E -- |結果が不十分| --> F["誤り分析"];
    F --> B;
    E -- |結果が良好| --> G["デプロイ"];

    subgraph 評価
        E1["フォーマットチェック"] --> E2["最終回答の正確性"];
        E2 --> E3["思考プロセスの論理性"];
    end
    subgraph プロンプト設計
        B1("ゼロショット") --> B2(Few-shot);
        B2 --> B3("CoT制約型");
    end

図1: プロンプト改良のフィードバックループ

  1. タスク定義: 解決したい問題と、期待される出力の具体像を明確にする。

  2. プロンプト設計: ゼロショット、Few-shot、CoT制約型など、異なるアプローチでプロンプトを作成する。

  3. LLMへの入力: 設計したプロンプトをLLMに投入し、応答を得る。

  4. 評価: 自動評価ツールと手動レビューを組み合わせ、応答の品質(正確性、一貫性、フォーマット遵守)を評価する。

    • 2024年4月20日にML Research Labが発表した研究では、LLMの評価には、正確性だけでなく、推論ステップの論理的整合性も重要であることが指摘されています [6]。
  5. 誤り分析: 評価結果に基づき、失敗モード(幻覚、様式崩れなど)を特定し、その根本原因を分析する。

  6. 改良: 分析結果に基づき、プロンプトの指示の明確化、Few-shot例の調整、System指示の強化、検証ステップの追加などの対策を講じる。

  7. 再評価: 改良後のプロンプトで再度LLMをテストし、その効果を測定する。

  8. デプロイ: 性能が要件を満たした場合、本番環境へのデプロイを検討する。

Google AI Blogは2024年6月1日の投稿で、CoTプロンプティングの導入には、明確な出力フォーマット定義と堅牢な評価が不可欠であると強調しています [4]。このサイクルを通じて、プロンプトの有効性を継続的に高めていきます。

7. まとめ

Chain-of-Thoughtプロンプティングは、LLMの複雑な推論タスクにおける性能を飛躍的に向上させる強力な手法です。しかし、その効果を最大限に引き出すためには、明確な入出力契約の定義、タスクに応じたプロンプト設計、そして堅牢な評価と継続的な改良のサイクルが不可欠です。本記事で紹介した戦略は、LLMを用いたアプリケーション開発において、より信頼性の高い推論能力を実現するための一助となるでしょう。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました