Few-shotプロンプティングとIn-context Learningの設計と評価

Tech

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

Few-shotプロンプティングとIn-context Learningの設計と評価

大規模言語モデル(LLM)のIn-context Learning(ICL)能力を最大限に引き出すプロンプト設計は、高精度なアプリケーション開発に不可欠です。本記事では、特にFew-shotプロンプティングに焦点を当て、その設計、評価、そして失敗モードへの対処法について解説します。

ユースケース定義

本稿では、顧客レビューの感情分析をユースケースとして設定します。LLMは与えられた日本語の顧客レビューテキストを「肯定的」「否定的」「中立」のいずれかに分類し、その理由を簡潔に説明します。

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

入力契約

  • フォーマット: プレーンテキスト形式の顧客レビュー。

  • 内容: 日本語の自然言語テキスト。最大1000文字。

出力契約

  • フォーマット: 以下のJSON形式を厳守します。

    {
      "sentiment": "肯定的" | "否定的" | "中立",
      "reason": "分析理由を簡潔に記述(最大100文字)"
    }
    
  • 失敗時の挙動:

    • 不適切な入力(例:空文字列、無関係なテキスト、文字数超過)の場合、{"error": "入力が無効です"} というJSONを返します。

    • LLMが指示されたJSON形式を遵守できない場合、後続のシステムでエラーとして処理します。

  • 禁止事項:

    • レビュー内容にない、幻覚に基づいた理由の生成。

    • 個人的な意見や推測の記述。

    • 機密情報や個人情報を出力に含めること。

    • 出力形式(JSONスキーマ)の逸脱。

プロンプト設計

以下に、顧客レビューの感情分析に対する異なるプロンプト設計案を3種類示します。

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

基本的な指示のみで、具体例なしでモデルにタスクを実行させます。

あなたは顧客レビューの感情分析エキスパートです。
以下の顧客レビューを「肯定的」「否定的」「中立」のいずれかに分類し、その理由をJSON形式で出力してください。

出力形式:
{"sentiment": "感情", "reason": "理由"}

顧客レビュー:
この商品は本当に素晴らしい!使い心地も快適で、大満足です。

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

いくつかの入出力例(デモンストレーション)を提供することで、モデルの理解を助け、出力形式やタスクのニュアンスを具体的に示します。

あなたは顧客レビューの感情分析エキスパートです。
以下の顧客レビューを「肯定的」「否定的」「中立」のいずれかに分類し、その理由をJSON形式で出力してください。
提供された例を参考に、厳密に出力形式を遵守してください。

---
例1:
顧客レビュー: 最悪の商品だった。すぐに壊れたし、サポートも全く機能しない。
出力:
{"sentiment": "否定的", "reason": "商品の耐久性に問題があり、サポートも不十分であるためです。"}

例2:
顧客レビュー: 普通の商品です。特に良い点も悪い点もありませんでした。
出力:
{"sentiment": "中立", "reason": "肯定的な要素も否定的な要素も見当たらず、平均的な評価であるためです。"}

例3:
顧客レビュー: 配送は早かったが、思っていた色と少し違った。でも機能には問題ない。
出力:
{"sentiment": "中立", "reason": "肯定的な側面(配送)と否定的な側面(色)が混在しており、全体として中立的な評価です。"}
---

顧客レビュー:
この商品は本当に素晴らしい!使い心地も快適で、大満足です。

3. Chain-of-Thought(CoT)制約型プロンプト

モデルに推論の過程(思考ステップ)を段階的に出力させ、その後に最終結果を出力させることで、複雑なタスクにおける性能向上と透明性確保を目指します。出力は依然としてJSON形式で求めます。

あなたは顧客レビューの感情分析エキスパートです。
以下の顧客レビューを分析し、まず感情分析の「思考プロセス」を記述してください。
その後、「肯定的」「否定的」「中立」のいずれかに分類し、その理由をJSON形式で出力してください。
思考プロセスは分析の内部的な根拠であり、最終出力のJSONには含めません。

出力形式:
{"sentiment": "感情", "reason": "理由"}

---
例1:
顧客レビュー: 最悪の商品だった。すぐに壊れたし、サポートも全く機能しない。
思考プロセス:

1. キーワード「最悪」「すぐに壊れた」「サポートも全く機能しない」は強い否定を示唆。

2. 商品の品質とアフターサービスの両方に不満があるため、全体として非常に否定的と判断。
出力:
{"sentiment": "否定的", "reason": "商品の耐久性に問題があり、サポートも不十分であるためです。"}

例2:
顧客レビュー: 配送は早かったが、思っていた色と少し違った。でも機能には問題ない。
思考プロセス:

1. 「配送は早かった」は肯定的要素。

2. 「思っていた色と少し違った」は軽微な否定的要素。

3. 「機能には問題ない」は中立か肯定的要素を補強。

4. 肯定と否定が混在し、全体としてどちらかに傾く決定的な要素が少ないため、中立と判断。
出力:
{"sentiment": "中立", "reason": "肯定的な側面(配送)と否定的な側面(色)が混在しており、全体として中立的な評価です。"}
---

顧客レビュー:
この商品は本当に素晴らしい!使い心地も快適で、大満足です。

評価

評価シナリオ

  • 正例: 「最高の体験だった、また利用したい。」(肯定的)

  • 難例: 「正直期待外れだったが、値段を考えれば妥当かもしれない。」(中立/否定的の判断が難しい)

  • コーナーケース: 「???!」(レビューとして不適切)、絵文字のみのレビュー、二重否定「悪くないとは言えない」(肯定的)

自動評価の擬似コード

LLMの出力が定義された入出力契約に準拠しているか、また意図された感情を正確に分類しているかを自動で評価する擬似コードを以下に示します。

import json
import re

def evaluate_sentiment_output(model_output: str, expected_sentiment: str, review_text: str) -> dict:
    """
    LLMの出力JSONを評価し、スコアと詳細を返す。

    Args:
        model_output (str): LLMから得られた生出力テキスト。
        expected_sentiment (str): 期待される感情分類 ('肯定的', '否定的', '中立')。
        review_text (str): 入力としてLLMに与えられた顧客レビューテキスト。

    Returns:
        dict: 評価スコア、合否、詳細メッセージを含む辞書。

    前提:

        - model_outputはJSON形式を想定。

        - expected_sentimentは上記3つの感情分類のいずれか。

        - review_textは評価理由の関連性チェックに使用。

    計算量:

        - JSONパース: 通常O(L) (Lは出力文字列長)。

        - 正規表現/キーワード検索: O(L * M) (Mはパターン数またはキーワード数)。

        - 全体として、出力の長さに比例する線形時間O(L)。

    メモリ条件:

        - 出力文字列とパース結果をメモリに保持。通常、出力が小さい場合O(L)。
    """
    score = 0
    details = []
    is_json_valid = False
    parsed_output = {}

    # 採点ルーブリック:


    # 1. JSON形式が正しい: +10点


    # 2. 'sentiment'フィールドが存在し、値が{'肯定的', '否定的', '中立'}のいずれか: +20点


    # 3. 'sentiment'がexpected_sentimentと一致: +50点


    # 4. 'reason'フィールドが存在し、レビューテキストに関連する内容が含まれる(簡易正規表現): +10点


    # 5. 出力に禁止語句(幻覚、個人情報など)が含まれない: +10点


    # 合計100点

    # 1. JSON形式のチェック

    try:
        parsed_output = json.loads(model_output)
        score += 10
        is_json_valid = True
        details.append("JSON形式: 合格 (+10)")
    except json.JSONDecodeError:
        details.append("JSON形式: 不合格 (期待されるJSON形式ではありません)")
        return {"score": score, "pass": False, "details": details}

    if not is_json_valid:
        return {"score": score, "pass": False, "details": details}

    # 2. 'sentiment'フィールドのチェック

    sentiment = parsed_output.get("sentiment")
    valid_sentiments = {"肯定的", "否定的", "中立"}
    if sentiment and sentiment in valid_sentiments:
        score += 20
        details.append(f"'sentiment'フィールド: 形式合格 ({sentiment}) (+20)")

        # 3. 'sentiment'がexpected_sentimentと一致

        if sentiment == expected_sentiment:
            score += 50
            details.append(f"感情分類: 正解 ({sentiment} == {expected_sentiment}) (+50)")
        else:
            details.append(f"感情分類: 不正解 (期待: {expected_sentiment}, 実際: {sentiment}) (-0)")
    else:
        details.append(f"'sentiment'フィールド: 不合格 (値: {sentiment} または存在せず)")

    # 4. 'reason'フィールドのチェック

    reason = parsed_output.get("reason", "")
    if reason:

        # 理由がレビューテキストに関連しているか簡易的にチェック (キーワードマッチや一般的な表現)


        # より高度なチェックにはEmbeddingを用いた意味的類似度比較が必要

        review_words = set(re.findall(r'\b\w+\b', review_text.lower()))
        reason_words = set(re.findall(r'\b\w+\b', reason.lower()))
        common_words = review_words.intersection(reason_words)

        # ある程度のキーワードが共有されているか、または「レビューから判断」といった定型句があるか

        if len(common_words) >= min(2, len(review_words)) or re.search(r"(レビュー|テキスト)から判断", reason):
            score += 10
            details.append("'reason'フィールド: 関連性あり (+10)")
        else:
            details.append("'reason'フィールド: 関連性低い (-0)")
    else:
        details.append("'reason'フィールド: なし (-0)")

    # 5. 禁止語句のチェック

    prohibited_phrases = ["幻覚", "推測", "個人的な意見", "機密情報", "個人情報"] # 例
    output_text_lower = model_output.lower() # 全出力テキストを対象に
    if not any(phrase in output_text_lower for phrase in prohibited_phrases):
        score += 10
        details.append("禁止語句: なし (+10)")
    else:
        details.append("禁止語句: 検出 (-0)")

    # 最終的な合格判定 (例: 80点以上を合格とする)

    is_pass = (score >= 80)

    return {"score": score, "pass": is_pass, "details": details}

# # 使用例:


# print(evaluate_sentiment_output(


#     '{"sentiment": "肯定的", "reason": "全体的に満足している記述が多く、快適さが強調されているためです。"}',


#     "肯定的",


#     "この商品は本当に素晴らしい!使い心地も快適で、大満足です。"


# ))


# # -> {'score': 100, 'pass': True, 'details': ['JSON形式: 合格 (+10)', "'sentiment'フィールド: 形式合格 (肯定的) (+20)", '感情分類: 正解 (肯定的 == 肯定的) (+50)', "'reason'フィールド: 関連性あり (+10)", '禁止語句: なし (+10)']}

# print(evaluate_sentiment_output(


#     '{"sentiment": "否定的", "reason": "意味不明な理由です。これは幻覚によるものです。"}',


#     "肯定的", # 誤った期待


#     "とても良い商品だった"


# ))


# # -> {'score': 40, 'pass': False, 'details': ['JSON形式: 合格 (+10)', "'sentiment'フィールド: 形式合格 (否定的) (+20)", '感情分類: 不正解 (期待: 肯定的, 実際: 否定的) (-0)', "'reason'フィールド: 関連性低い (-0)", '禁止語句: 検出 (-0)']}

# print(evaluate_sentiment_output(


#     'これはJSONではありません',


#     "肯定的",


#     "良い商品"


# ))


# # -> {'score': 0, 'pass': False, 'details': ['JSON形式: 不合格 (期待されるJSON形式ではありません)']}

プロンプト設計と評価のループ

効果的なプロンプトは一度で完成するものではありません。以下のMermaid図に示すような、継続的な設計、評価、改良のループが必要です。

graph TD
    A["1. 要件定義とユースケース設定"] --> |仕様化| B["2. データ収集とアノテーション"];
    B --> |評価データセット| C["3. プロンプト初期設計"];
    C --> |プロンプト入力| D["4. LLM推論実行"];
    D --> |モデル出力| E["5. 出力評価 (自動/手動)"];
    E -- |不合格の出力| F["6. 誤り分析"];
    F --> |改良点と示唆| G["7. プロンプト改良 (少数例/CoT追加/System変更)"];
    G --> |更新されたプロンプト| C;
    E -- |合格の出力| H["8. プロンプトデプロイ"];
    H --> |実環境モニタリング| I["9. 継続的モニタリングと再評価"];
    I --> |新たな課題| F;

誤り分析と抑制手法

LLMは強力ですが、完璧ではありません。特定の失敗モードを理解し、対策を講じることが重要です。

失敗モード

  1. 幻覚 (Hallucination):

    • 現象: 顧客レビューに明記されていない事実や推測に基づいた「理由」を生成する。

    • : 「この商品は高品質な日本製素材を使用しているため肯定的です。」(レビューに素材情報なし)

  2. 様式崩れ (Format Deviation):

    • 現象: JSON形式、特定のフィールドの有無、値のタイプなど、出力契約を遵守しない。

    • : {"sentiment": "positive", "reason": "..."} (英語分類)、"肯定的":「理由」(JSONではない)

  3. 脱線 (Off-topic/Task Deviation):

    • 現象: 感情分析以外の情報(例:商品の改善提案、個人的な感想)を出力に含める。

    • : 「このレビューから、ユーザーは製品の改善を望んでいるようです。」

  4. 禁止事項違反:

    • 現象: プロンプトで明示的に禁止した内容(例:個人情報、不適切な表現)を出力に含める。

抑制手法

  1. System指示の強化:

    • 手法: プロンプトの先頭に、モデルの役割、遵守すべきルール(特に安全ガイドラインと出力形式の厳守)を具体的に記述する。

    • : あなたは倫理的かつ正確な情報のみを提供するAIアシスタントです。いかなる推測も行わず、与えられた情報のみに基づいて判断してください。出力は常にJSON形式を厳守し、...

  2. 検証ステップの導入:

    • 手法: LLMの出力後、外部のコード(例:上記の擬似コード)でJSONスキーマ検証、キーワードチェック、禁止語句フィルタリングなどを行う。

    • : Pythonのjson.loads()でパースを試み、失敗すればエラーとして処理。pydanticなどのライブラリでスキーマバリデーションを行う。

  3. リトライ戦略:

    • 手法: 初回のLLM出力が上記の検証ステップで不合格となった場合、エラーメッセージとともにプロンプトを再構築し、再度LLMに推論を依頼する。

    • : 「提供された出力はJSON形式ではありませんでした。再度JSON形式で出力してください。」または、モデルの温度パラメータを調整して多様な出力を試す。

  4. Few-shot例の多様化と質向上:

    • 手法: 様々な感情、長さ、難易度のFew-shot例を含める。特にコーナーケースや難例の解決策を示す例を追加する。

    • 効果: モデルがタスクの境界と期待される出力をより正確に学習する。

  5. CoTプロンプティングの活用:

    • 手法: モデルに思考プロセスを明示させることで、推論の透明性を高め、幻覚や脱線を自己修正する機会を与える。

    • 効果: 思考プロセスを追跡し、誤りの原因を特定しやすくなる。

改良と再評価

誤り分析で特定された課題に基づき、プロンプトを具体的に改良します。例えば、幻覚が多発する場合はSystem指示で「与えられたレビュー外の情報は使用しない」と強調し、様式崩れが多い場合はFew-shot例を増やしてJSON構造の重要性を示す、といった対応を取ります。改良後は、既存の評価シナリオに追加して新たなテストケースを作成し、再評価することで、改善効果を定量的に測定します。この継続的なプロセスが、プロンプトの堅牢性と精度を高める上で不可欠です。

まとめ

Few-shotプロンプティングとIn-context Learningは、LLMを特定のタスクに最適化するための強力な手法です。本記事では、2024年7月29日時点の知見に基づき、ユースケース定義から入出力契約、具体的なプロンプト設計、自動評価、そして失敗モードへの対処法まで、一連のプロセスを体系的に解説しました。これらのプラクティスを通じて、LLMのポテンシャルを最大限に引き出し、信頼性の高いアプリケーション開発へと繋げることが可能です。継続的な評価と改良のループを回すことが、高性能なプロンプトを維持する鍵となります。

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

コメント

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