はじめに
LLMの精度を高める方法の一つとして「プロンプトエンジニアリング」があります。
プロンプトエンジニアリングではLLMの中身に手を入れるのではなく、与える「プロンプト」を改善する手法です。
今回はプロンプトエンジニアリング手法の一つである、Few-Shotに着目しました。
実際にローカル環境での実践を通じて、Few-Shotの効果を測定してみます!
プロンプトエンジニアリングとは
プロンプトエンジニアリングとは、大規模言語モデル(LLM)から望ましい回答を引き出すために、入力文(プロンプト)を設計・最適化する技術です。
AIに対して「何をすべきか」という直接的な命令だけでなく、文脈、指示、背景情報、例を提供することで、モデルがユーザーの意図を正確に理解し、有意義な方法で応答できるように導きます。
参考:
Few-Shot
Few-Shot(フューショット)とは、「入力と出力のペア」を1つ以上お手本(デモンストレーション)として提示する手法です。
モデルに対していくつかのサンプル入力と期待される出力を与え、その例に基づいて新しい未見のデータに対する適切な出力を生成するように誘導します。
反対に、サンプルを何も与えなかった場合をZero-shot(ゼロショット)と呼びます。
Few-Shotの手法で与えるべき適切なサンプル数の研究は進められていますが、サンプル数と精度・コストの関係は複雑で、タスクによって最適解が大きく異なるようです。
Transformersを用いた実践
今回もPythonのTransformersライブラリを用いて実践してみます。
使用モデル
今回はsonoisa/t5-base-japanese-adapt をモデルとして選択しました。
以下の理由からプロンプトエンジニアリングの練習に適していると判断して採用しています。
- T5 (
Text-to-Text Transfer Transformer) アーキテクチャであり、あらゆるタスクをText-to-Text形式で処理する設計- 感情分析、要約、翻訳、質問応答など、すべてを統一的なインターフェースで扱える
- プロンプトを入力として、適切なテキストを出力する形式に最適化
- 日本語での事前学習済みモデルである
- ベースモデル
sonoisa/t5-base-japanese-v1.1にさらに100Kステップ追加学習済み
実践①:感情分析
Transformersのpipelineで"text2text-generation"タスクを指定することで、テキスト形式で自由に指示を与え、テキストを出力させることができます。
今回はプロンプト以外の条件を統一するため、テキスト生成部分は関数化しています。
※max_new_tokensをはじめとしたパラメータ群は繰り返し実行する中で調整した結果を載せています。
コード:回答生成(共通部)~Zero-Shotまで
from transformers import pipeline
generator = pipeline(
"text2text-generation",
model="sonoisa/t5-base-japanese-adapt",
)
def generate(prompt: str):
return generator(
prompt,
max_new_tokens=3, # 必要トークンは最小限
min_new_tokens=1, # 最低1トークン生成
num_beams=8, # 精度重視でビーム数を増やす
early_stopping=True, # EOSで即終了
no_repeat_ngram_size=2, # 繰り返し防止
repetition_penalty=2.0, # 強いペナルティ(繰り返しを強く抑制)
length_penalty=0.8, # 軽めの短縮促進
)[0]["generated_text"]
# Zero-shot
print("=" * 60)
print("Zero-shot(例なし)")
print("=" * 60)
prompt = """
タスク: 感情分析
指示:
以下の文章を読み、その感情を判定してください。
必ず「ポジティブ」「ネガティブ」「ニュートラル」のいずれか1つの単語のみで答えてください。
文章:
このレストランの料理は最高に美味しかった
回答:
"""
print("結果: " + generate(prompt))
print()
今回は成功・失敗が分かりやすいように「このレストランの料理は最高に美味しかった」という明らかにポジティブな文章を判定対象としました。
続けて、Few-Shotとして3つの例を用意したプロンプトも用意します。
# Few-shot
print("=" * 60)
print("Few-shot(3つの例あり)")
print("=" * 60)
prompt = """
タスク: 感情分析
指示:
以下の文章を読み、その感情を判定してください。
必ず「ポジティブ」「ネガティブ」「ニュートラル」のいずれか1つの単語のみで答えてください。
以下の例を参考にしてください。
例1:
文章: シェフの作るデザートは絶品で感動した
感情: ポジティブ
例2:
文章: 料理が冷めていて味も薄くて残念だった
感情: ネガティブ
例3:
文章: 味は普通だが値段相応だと思う
感情: ニュートラル
判定対象:
文章: このレストランの料理は最高に美味しかった
感情:
"""
print("結果: " + generate(prompt))
print()
同様に例が5つ・6つ・8つのパターンを用意しました。
コード全量(クリックで展開)
from transformers import pipeline
generator = pipeline(
"text2text-generation",
model="sonoisa/t5-base-japanese-adapt",
)
def generate(prompt: str):
return generator(
prompt,
max_new_tokens=3, # 必要トークンは最小限
min_new_tokens=1, # 最低1トークン生成
num_beams=8, # 精度重視でビーム数を増やす
early_stopping=True, # EOSで即終了
no_repeat_ngram_size=2, # 繰り返し防止
repetition_penalty=2.0, # 強いペナルティ(繰り返しを強く抑制)
length_penalty=0.8, # 軽めの短縮促進
)[0]["generated_text"]
# Zero-shot
print("=" * 60)
print("Zero-shot(例なし)")
print("=" * 60)
prompt = """
タスク: 感情分析
指示:
以下の文章を読み、その感情を判定してください。
必ず「ポジティブ」「ネガティブ」「ニュートラル」のいずれか1つの単語のみで答えてください。
文章:
このレストランの料理は最高に美味しかった
回答:
"""
print("結果: " + generate(prompt))
print()
# Few-shot
print("=" * 60)
print("Few-shot(3つの例あり)")
print("=" * 60)
prompt = """
タスク: 感情分析
指示:
以下の文章を読み、その感情を判定してください。
必ず「ポジティブ」「ネガティブ」「ニュートラル」のいずれか1つの単語のみで答えてください。
以下の例を参考にしてください。
例1:
文章: シェフの作るデザートは絶品で感動した
感情: ポジティブ
例2:
文章: 料理が冷めていて味も薄くて残念だった
感情: ネガティブ
例3:
文章: 味は普通だが値段相応だと思う
感情: ニュートラル
判定対象:
文章: このレストランの料理は最高に美味しかった
感情:
"""
print("結果: " + generate(prompt))
print()
# Five-shot(5つの例)
print("=" * 60)
print("Five-shot(5つの例あり)")
print("=" * 60)
prompt = """
タスク: 感情分析
指示:
以下の文章を読み、その感情を判定してください。
必ず「ポジティブ」「ネガティブ」「ニュートラル」のいずれか1つの単語のみで答えてください。
以下の例を参考にしてください。
例1:
文章: シェフの作るデザートは絶品で感動した
感情: ポジティブ
例2:
文章: 料理が冷めていて味も薄くて残念だった
感情: ネガティブ
例3:
文章: 味は普通だが値段相応だと思う
感情: ニュートラル
例4:
文章: サービスも料理も素晴らしく最高の体験だった
感情: ポジティブ
例5:
文章: 待ち時間が長すぎて二度と行きたくない
感情: ネガティブ
判定対象:
文章: このレストランの料理は最高に美味しかった
感情:
"""
print("結果: " + generate(prompt))
print()
# Six-shot(6つの例)
print("=" * 60)
print("Six-shot(6つの例あり)")
print("=" * 60)
prompt = """
タスク: 感情分析
指示:
以下の文章を読み、その感情を判定してください。
必ず「ポジティブ」「ネガティブ」「ニュートラル」のいずれか1つの単語のみで答えてください。
以下の例を参考にしてください。
例1:
文章: シェフの作るデザートは絶品で感動した
感情: ポジティブ
例2:
文章: 料理が冷めていて味も薄くて残念だった
感情: ネガティブ
例3:
文章: 味は普通だが値段相応だと思う
感情: ニュートラル
例4:
文章: サービスも料理も素晴らしく最高の体験だった
感情: ポジティブ
例5:
文章: 待ち時間が長すぎて二度と行きたくない
感情: ネガティブ
例6:
文章: 特に印象に残る点はなく可もなく不可もなし
感情: ニュートラル
判定対象:
文章: このレストランの料理は最高に美味しかった
感情:
"""
print("結果: " + generate(prompt))
print()
# Eight-shot(8つの例)
print("=" * 60)
print("Eight-shot(8つの例あり)")
print("=" * 60)
prompt = """
タスク: 感情分析
指示:
以下の文章を読み、その感情を判定してください。
必ず「ポジティブ」「ネガティブ」「ニュートラル」のいずれか1つの単語のみで答えてください。
以下の例を参考にしてください。
例1:
文章: シェフの作るデザートは絶品で感動した
感情: ポジティブ
例2:
文章: 料理が冷めていて味も薄くて残念だった
感情: ネガティブ
例3:
文章: 味は普通だが値段相応だと思う
感情: ニュートラル
例4:
文章: サービスも料理も素晴らしく最高の体験だった
感情: ポジティブ
例5:
文章: 待ち時間が長すぎて二度と行きたくない
感情: ネガティブ
例6:
文章: 特に印象に残る点はなく可もなく不可もなし
感情: ニュートラル
例7:
文章: 新鮮な食材を使った料理がどれも美味しかった
感情: ポジティブ
例8:
文章: 値段が高い割に量が少なくてがっかりした
感情: ネガティブ
判定対象:
文章: このレストランの料理は最高に美味しかった
感情:
"""
print("結果: " + generate(prompt))
print()
実行結果
実際に例示の数を変えた時の回答の変化を確認します。
============================================================
Zero-shot(例なし)
============================================================
結果: このレストランの
============================================================
Few-shot(3つの例あり)
============================================================
結果: ネガティブ
============================================================
Five-shot(5つの例あり)
============================================================
結果: ネガティブ
============================================================
Six-shot(6つの例あり)
============================================================
結果: ポジティブ
============================================================
Eight-shot(8つの例あり)
============================================================
結果: ポジティブ
以下のことが分かります。
- Zero-shot(例なし)の場合には、そもそも出力形式が指示に従っていない。
- Few-shot(3つの例)以降では、出力形式が指示通り(「ポジティブ」「ネガティブ」「ニュートラル」のいずれか1つの単語のみ)になる
- Five-Shot(5つ)までは正しく判断できていないが、例の数を6以上に増やすことで判定の精度が上がっている。
実践②:メール文面生成
アウトプットの自由度が高い形式でも試してみました。
提示した状況に対して、適切なメールの文面を生成させるプロンプトを用意しました。
※パラメータ群はこの問題用に調整した結果を載せています
コード:回答生成(共通部)~Zero-Shotまで
from transformers import pipeline
generator = pipeline(
"text2text-generation",
model="sonoisa/t5-base-japanese-adapt",
)
def generate(prompt: str):
return generator(
prompt,
max_new_tokens=14, # 短く制限して不要な続きを防止
min_new_tokens=8, # 最低限の長さ
num_beams=10, # 精度重視
early_stopping=True, # EOSで終了
no_repeat_ngram_size=4, # より強い繰り返し防止
repetition_penalty=2.0, # パターン続きを強く抑制
length_penalty=0.5, # 短い出力を強く促進
)[0]["generated_text"]
# Zero-shot
print("=" * 60)
print("Zero-shot(例なし)")
print("=" * 60)
prompt = """
タスク: お礼メール文生成
指示:
以下の状況に応じた丁寧なお礼のメール文を生成してください。
1文で簡潔に記述してください。
状況: 取引先からプレゼントをいただいた
お礼文:
"""
print("結果: " + generate(prompt))
print()
同様にFew-Shotとして3つの例を与えるプロンプトを用意します。
# Few-shot (3-shot)
print("=" * 60)
print("Few-shot(例3つ)")
print("=" * 60)
prompt = """
タスク: お礼メール文生成
指示:
以下の状況に応じた丁寧なお礼のメール文を生成してください。
1文で簡潔に記述してください。
例1:
状況: 会議に参加していただいた
お礼文: 本日はご多忙のところ、会議にご参加いただき誠にありがとうございました。
例2:
状況: 資料を送っていただいた
お礼文: 貴重な資料をお送りいただき、心より感謝申し上げます。
例3:
状況: アドバイスをいただいた
お礼文: 的確なアドバイスをいただき、大変参考になりました。
生成対象:
状況: 取引先からプレゼントをいただいた
お礼文:
"""
print("結果: " + generate(prompt))
print()
今回は例示の数が5個・8個・10個・12個のパターンも用意します。
コード全量(クリックで展開)
from transformers import pipeline
generator = pipeline(
"text2text-generation",
model="sonoisa/t5-base-japanese-adapt",
)
def generate(prompt: str):
return generator(
prompt,
max_new_tokens=14, # 短く制限して不要な続きを防止
min_new_tokens=8, # 最低限の長さ
num_beams=10, # 精度重視
early_stopping=True, # EOSで終了
no_repeat_ngram_size=4, # より強い繰り返し防止
repetition_penalty=2.0, # パターン続きを強く抑制
length_penalty=0.5, # 短い出力を強く促進
)[0]["generated_text"]
# Zero-shot
print("=" * 60)
print("Zero-shot(例なし)")
print("=" * 60)
prompt = """
タスク: お礼メール文生成
指示:
以下の状況に応じた丁寧なお礼のメール文を生成してください。
1文で簡潔に記述してください。
状況: 取引先からプレゼントをいただいた
お礼文:
"""
print("結果: " + generate(prompt))
print()
# Few-shot (3-shot)
print("=" * 60)
print("Few-shot(例3つ)")
print("=" * 60)
prompt = """
タスク: お礼メール文生成
指示:
以下の状況に応じた丁寧なお礼のメール文を生成してください。
1文で簡潔に記述してください。
例1:
状況: 会議に参加していただいた
お礼文: 本日はご多忙のところ、会議にご参加いただき誠にありがとうございました。
例2:
状況: 資料を送っていただいた
お礼文: 貴重な資料をお送りいただき、心より感謝申し上げます。
例3:
状況: アドバイスをいただいた
お礼文: 的確なアドバイスをいただき、大変参考になりました。
生成対象:
状況: 取引先からプレゼントをいただいた
お礼文:
"""
print("結果: " + generate(prompt))
print()
# Five-shot (5-shot)
print("=" * 60)
print("Five-shot(例5つ)")
print("=" * 60)
prompt = """
タスク: お礼メール文生成
指示:
以下の状況に応じた丁寧なお礼のメール文を生成してください。
1文で簡潔に記述してください。
例1:
状況: 会議に参加していただいた
お礼文: 本日はご多忙のところ、会議にご参加いただき誠にありがとうございました。
例2:
状況: 資料を送っていただいた
お礼文: 貴重な資料をお送りいただき、心より感謝申し上げます。
例3:
状況: アドバイスをいただいた
お礼文: 的確なアドバイスをいただき、大変参考になりました。
例4:
状況: プロジェクトに協力していただいた
お礼文: プロジェクトへのご協力、誠にありがとうございました。
例5:
状況: セミナーに登壇していただいた
お礼文: 素晴らしいご講演をいただき、参加者一同感銘を受けました。
生成対象:
状況: 取引先からプレゼントをいただいた
お礼文:
"""
print("結果: " + generate(prompt))
print()
# Eight-shot (8-shot)
print("=" * 60)
print("Eight-shot(例8つ)")
print("=" * 60)
prompt = """
タスク: お礼メール文生成
指示:
以下の状況に応じた丁寧なお礼のメール文を生成してください。
1文で簡潔に記述してください。
例1:
状況: 会議に参加していただいた
お礼文: 本日はご多忙のところ、会議にご参加いただき誠にありがとうございました。
例2:
状況: 資料を送っていただいた
お礼文: 貴重な資料をお送りいただき、心より感謝申し上げます。
例3:
状況: アドバイスをいただいた
お礼文: 的確なアドバイスをいただき、大変参考になりました。
例4:
状況: プロジェクトに協力していただいた
お礼文: プロジェクトへのご協力、誠にありがとうございました。
例5:
状況: セミナーに登壇していただいた
お礼文: 素晴らしいご講演をいただき、参加者一同感銘を受けました。
例6:
状況: 問題解決に尽力していただいた
お礼文: 迅速なご対応により、問題を解決していただき感謝いたします。
例7:
状況: 食事をご馳走していただいた
お礼文: 昨日は美味しいお食事をご馳走いただき、ありがとうございました。
例8:
状況: 推薦状を書いていただいた
お礼文: 推薦状をお書きいただき、心より御礼申し上げます。
生成対象:
状況: 取引先からプレゼントをいただいた
お礼文:
"""
print("結果: " + generate(prompt))
print()
# Ten-shot (10-shot)
print("=" * 60)
print("Ten-shot(例10個)")
print("=" * 60)
prompt = """
タスク: お礼メール文生成
指示:
以下の状況に応じた丁寧なお礼のメール文を生成してください。
1文で簡潔に記述してください。
例1:
状況: 会議に参加していただいた
お礼文: 本日はご多忙のところ、会議にご参加いただき誠にありがとうございました。
例2:
状況: 資料を送っていただいた
お礼文: 貴重な資料をお送りいただき、心より感謝申し上げます。
例3:
状況: アドバイスをいただいた
お礼文: 的確なアドバイスをいただき、大変参考になりました。
例4:
状況: プロジェクトに協力していただいた
お礼文: プロジェクトへのご協力、誠にありがとうございました。
例5:
状況: セミナーに登壇していただいた
お礼文: 素晴らしいご講演をいただき、参加者一同感銘を受けました。
例6:
状況: 問題解決に尽力していただいた
お礼文: 迅速なご対応により、問題を解決していただき感謝いたします。
例7:
状況: 食事をご馳走していただいた
お礼文: 昨日は美味しいお食事をご馳走いただき、ありがとうございました。
例8:
状況: 推薦状を書いていただいた
お礼文: 推薦状をお書きいただき、心より御礼申し上げます。
例9:
状況: 視察を受け入れていただいた
お礼文: 貴重なお時間をいただき、施設をご案内いただきありがとうございました。
例10:
状況: 契約を締結していただいた
お礼文: この度は弊社との契約をご決定いただき、深く感謝申し上げます。
生成対象:
状況: 取引先からプレゼントをいただいた
お礼文:
"""
print("結果: " + generate(prompt))
print()
# Twelve-shot (12-shot)
print("=" * 60)
print("Twelve-shot(例12個)")
print("=" * 60)
prompt = """
タスク: お礼メール文生成
指示:
以下の状況に応じた丁寧なお礼のメール文を生成してください。
1文で簡潔に記述してください。
例1:
状況: 会議に参加していただいた
お礼文: 本日はご多忙のところ、会議にご参加いただき誠にありがとうございました。
例2:
状況: 資料を送っていただいた
お礼文: 貴重な資料をお送りいただき、心より感謝申し上げます。
例3:
状況: アドバイスをいただいた
お礼文: 的確なアドバイスをいただき、大変参考になりました。
例4:
状況: プロジェクトに協力していただいた
お礼文: プロジェクトへのご協力、誠にありがとうございました。
例5:
状況: セミナーに登壇していただいた
お礼文: 素晴らしいご講演をいただき、参加者一同感銘を受けました。
例6:
状況: 問題解決に尽力していただいた
お礼文: 迅速なご対応により、問題を解決していただき感謝いたします。
例7:
状況: 食事をご馳走していただいた
お礼文: 昨日は美味しいお食事をご馳走いただき、ありがとうございました。
例8:
状況: 推薦状を書いていただいた
お礼文: 推薦状をお書きいただき、心より御礼申し上げます。
例9:
状況: 視察を受け入れていただいた
お礼文: 貴重なお時間をいただき、施設をご案内いただきありがとうございました。
例10:
状況: 契約を締結していただいた
お礼文: この度は弊社との契約をご決定いただき、深く感謝申し上げます。
例11:
状況: インタビューに応じていただいた
お礼文: 貴重なお話を聞かせていただき、大変勉強になりました。
例12:
状況: 製品のデモンストレーションをしていただいた
お礼文: 詳細な製品説明をいただき、理解が深まりました。
生成対象:
状況: 取引先からプレゼントをいただいた
お礼文:
"""
print("結果: " + generate(prompt))
print()
結果確認
以下のような出力となりました。
============================================================
Zero-shot(例なし)
============================================================
結果: 取引先からプレゼントをいただいたお礼のメール文を生成
============================================================
Few-shot(例3つ)
============================================================
結果: 本日はご多忙のところ、会議にご参加いただき誠
============================================================
Five-shot(例5つ)
============================================================
結果: 大変有難うございました。 生成対象:
============================================================
Eight-shot(例8つ)
============================================================
結果: 素敵なプレゼントをいただき、心より感謝申し上げます。 生成対象
============================================================
Ten-shot(例10個)
============================================================
結果: 貴重なプレゼントをいただき、心より感謝申し上げます。 例11
============================================================
Twelve-shot(例12個)
============================================================
結果: 大変有難うございました。 例13:
以下のことが分かります。
- Zero-shotの場合、ほとんど指示を無視した出力になっている
- 例が3-5個の場合、それらしい出力を出しているが期待したフォーマットになっていない。
- 例が8個-10個の場合、期待したフォーマットの出力が得られる
- 例が12個の場合、回答の精度が下がっている模様
まとめ・考察
今回はTransformersを用いることで異なるプロンプトによる生成結果の変化を確認しました。
以下、実験を通じて分かったことをまとめます。
タスク別の最適なサンプル数
今回の実験結果から、タスクの種類によって必要なサンプル数(Few-shotの例の数)が異なることが分かりました。
| タスク種別 | 推奨サンプル数 | 理由 | 特徴 |
|---|---|---|---|
|
感情分析 (クローズドタスク) |
6〜8例 | ・判定基準が明確で、分類先が限定的(3クラス) ・6例以上で安定した判定が可能に |
・出力が単語レベル ・判定ルールがシンプル ・過学習のリスクが低い |
|
メール文面生成 (オープンタスク) |
8〜10例 | ・文章生成は多様性が高い ・トーンやスタイルを学習させるには多めの例が必要 ・12例以上は過剰(ノイズ増加) |
・出力が文章レベル ・スタイルの一貫性が重要 ・多様な表現パターンの学習が必要 |
今回の結果から考えられること
- タスク種別に応じて必要なサンプル数は異なり、難易度の高いタスクほど必要なサンプル数は多くなる。
- サンプル数は多ければ多いほどよいわけではなく、一定のラインを超えるとむしろ逆効果になる。
パラメータ調整とプロンプト改善の使い分け
実際にプロンプトの検証を行った感想としては、max_new_tokensをはじめとしたパラメータ調整とプロンプトの改善の使い分けが必要だと感じました。
| 手法 | 役割 | 制御対象 |
|---|---|---|
| パラメータ調整 | モデルの生成方法を制御 | ・出力の長さ ・ランダム性 ・繰り返しの抑制 等 |
| プロンプト改善 | モデルに求める要件を明確にする | ・タスクの指示 ・出力形式の指定 ・コンテキストの提供 等 |
- 出力内容の形式を制御したい場合→ パラメータ調整
- タスクの精度を上げたい場合→ プロンプト改善
まずプロンプトで精度を上げてから、パラメータで微調整するという流れがよさそうです。
今後の展開
今回取り上げたFew-Shot以外にも代表的なプロンプトエンジニアリング手法はまだまだ存在するので、今後取り上げてみたいと思います!
作成コード
環境情報もこちら参照
https://github.com/abi-inami/Transformers-sample
参考