Claudeで構築する
Claudeについて学ぶ
- ユースケース
- モデル
- セキュリティとコンプライアンス
テストと評価
- ガードレールを強化する
- 評価ツールの使用方法
管理
法務センター
強力な実証評価の作成
成功基準を定義した後、次のステップはそれらの基準に対してLLMのパフォーマンスを測定する評価を設計することです。これはプロンプトエンジニアリングサイクルの重要な部分です。
このガイドではテストケースの開発方法に焦点を当てます。
評価とテストケースの構築
評価設計の原則
- タスク固有であること: 実世界のタスク分布を反映する評価を設計します。エッジケースを考慮することを忘れないでください!
- 無関係または存在しないインプットデータ
- 過度に長いインプットデータまたはユーザー入力
- [チャットのユースケース] 不適切、有害、または無関係なユーザー入力
- 人間でさえ評価の合意に達するのが難しい曖昧なテストケース
- 可能な限り自動化: 自動採点を可能にする方法で質問を構造化します(例:多肢選択、文字列マッチ、コード採点、LLM採点)。
- 品質よりも量を優先: シグナルがやや低い自動採点の質問が多い方が、人手による高品質な採点の質問が少ないよりも良いです。
評価の例
測定対象: 完全一致評価は、モデルの出力が事前定義された正解と完全に一致するかどうかを測定します。感情分析(ポジティブ、ネガティブ、中立)のような明確なカテゴリカルな回答を持つタスクに最適な、シンプルで曖昧さのない指標です。
評価テストケースの例: 人間がラベル付けした1000件のツイート。
import anthropic
tweets = [
{"text": "この映画は完全に時間の無駄でした。👎", "sentiment": "negative"},
{"text": "新しいアルバムは🔥!一日中リピート再生してます。", "sentiment": "positive"},
{"text": "フライトが5時間も遅延するなんて最高だね。#最高の一日", "sentiment": "negative"}, # エッジケース:皮肉
{"text": "映画のプロットはひどかったけど、演技は素晴らしかった。", "sentiment": "mixed"}, # エッジケース:複合的な感情
# ... 996件のツイート
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=50,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_exact_match(model_output, correct_answer):
return model_output.strip().lower() == correct_answer.lower()
outputs = [get_completion(f"以下を'positive'、'negative'、'neutral'、または'mixed'に分類してください:{tweet['text']}") for tweet in tweets]
accuracy = sum(evaluate_exact_match(output, tweet['sentiment']) for output, tweet in zip(outputs, tweets)) / len(tweets)
print(f"感情分析の精度:{accuracy * 100}%")
測定対象: コサイン類似度は、2つのベクトル(この場合、SBERTを使用したモデル出力の文章埋め込み)間の角度のコサインを計算することで類似性を測定します。1に近い値はより高い類似性を示します。類似の質問には、言い回しが異なっていても意味的に類似した回答が得られるべきため、一貫性の評価に理想的です。
評価テストケースの例: それぞれに複数の言い換えバージョンを持つ50グループ。
from sentence_transformers import SentenceTransformer
import numpy as np
import anthropic
faq_variations = [
{"questions": ["返品ポリシーを教えてください", "商品を返品するにはどうすればいいですか?", "かえぴんポリシーってなに?"], "answer": "当社の返品ポリシーでは..."}, # エッジケース:タイプミス
{"questions": ["先週何か買ったんですけど、期待していたものと違ったので、もしかしたら返品できるかなと思って...", "オンラインで30日以内という方針を見たんですが、ウェブサイトが6ヶ月前に更新されているようなので、現在の正確な方針を知りたいのですが..."], "answer": "当社の返品ポリシーでは..."}, # エッジケース:長い、まわりくどい質問
{"questions": ["私はJaneのいとこなんですが、カスタマーサービスがとても良いと聞きました。これ返品できますか?", "Redditで、この方法が一番早く回答が得られると書いてありました。その通りだといいのですが!ジャケットの返品期間はどのくらいですか?"], "answer": "当社の返品ポリシーでは..."}, # エッジケース:無関係な情報
# ... 47件のFAQ
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=2048,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_cosine_similarity(outputs):
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = [model.encode(output) for output in outputs]
cosine_similarities = np.dot(embeddings, embeddings.T) / (np.linalg.norm(embeddings, axis=1) * np.linalg.norm(embeddings, axis=1).T)
return np.mean(cosine_similarities)
for faq in faq_variations:
outputs = [get_completion(question) for question in faq["questions"]]
similarity_score = evaluate_cosine_similarity(outputs)
print(f"FAQ一貫性スコア:{similarity_score * 100}%")
測定対象: ROUGE-L(Recall-Oriented Understudy for Gisting Evaluation - 最長共通部分列)は生成された要約の品質を評価します。候補要約と参照要約の間の最長共通部分列の長さを測定します。高いROUGE-Lスコアは、生成された要約が一貫した順序で重要な情報を捉えていることを示します。
評価テストケースの例: 参照要約付きの200記事。
from rouge import Rouge
import anthropic
articles = [
{"text": "画期的な研究で、MITの研究者たちは...", "summary": "MIT科学者たちが新しい抗生物質を発見..."},
{"text": "地元のヒーロー、Jane Doeは先週、救助で話題に... 市役所のニュースでは、予算... 気象予報士は予測...", "summary": "地域社会はローカルヒーローJane Doeを称賛する一方、市は予算問題に直面"}, # エッジケース:複数トピック
{"text": "この有名人の行動に驚愕! ... 広範なチャリティ活動 ...", "summary": "有名人の広範なチャリティ活動にファン驚く"}, # エッジケース:誤解を招くタイトル
# ... 197件の記事
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_rouge_l(model_output, true_summary):
rouge = Rouge()
scores = rouge.get_scores(model_output, true_summary)
return scores[0]['rouge-l']['f'] # ROUGE-L F1スコア
outputs = [get_completion(f"この記事を1-2文で要約してください:\n\n{article['text']}") for article in articles]
relevance_scores = [evaluate_rouge_l(output, article['summary']) for output, article in zip(outputs, articles)]
print(f"平均ROUGE-L F1スコア:{sum(relevance_scores) / len(relevance_scores)}")
測定対象: LLMベースのリッカート尺度は、主観的な態度や認識を判断するためにLLMを使用する心理測定尺度です。ここでは、応答のトーンを1から5の尺度で評価するために使用されます。従来の指標では定量化が難しい共感、プロフェッショナリズム、忍耐などの微妙な側面を評価するのに理想的です。
評価テストケースの例: 目標トーン(共感的、プロフェッショナル、簡潔)を持つ100件の顧客問い合わせ。
import anthropic
inquiries = [
{"text": "これで3回目の注文ミスです。今すぐ返金してください!", "tone": "empathetic"}, # エッジケース:怒った顧客
{"text": "パスワードをリセットしようとしたら、アカウントがロックされてしまいました...", "tone": "patient"}, # エッジケース:複雑な問題
{"text": "あなたの製品の素晴らしさに感動しました。他の製品が台無しになってしまいました!", "tone": "professional"}, # エッジケース:苦情としての賞賛
# ... 97件の問い合わせ
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=2048,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_likert(model_output, target_tone):
tone_prompt = f"""このカスタマーサービスの応答が{target_tone}であるかを1-5の尺度で評価してください:
<response>{model_output}</response>
1: まったく{target_tone}でない
5: 完全に{target_tone}である
数字のみを出力してください。"""
# 一般的に、評価対象の出力を生成したモデルとは異なるモデルを使用して評価するのがベストプラクティス
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": tone_prompt}])
return int(response.content[0].text.strip())
outputs = [get_completion(f"この顧客の問い合わせに応答してください:{inquiry['text']}") for inquiry in inquiries]
tone_scores = [evaluate_likert(output, inquiry['tone']) for output, inquiry in zip(outputs, inquiries)]
print(f"平均トーンスコア:{sum(tone_scores) / len(tone_scores)}")
測定対象: 二値分類は、入力が2つのクラスのいずれかに属するかを判断します。ここでは、応答にPHI(個人健康情報)が含まれているかどうかを分類するために使用されます。この方法は、ルールベースのシステムでは見逃す可能性のある微妙な、または暗黙的なPHIの形式を理解し、識別することができます。
評価テストケースの例: PHIを含むものと含まないものがある500件の模擬患者クエリ。
import anthropic
patient_queries = [
{"query": "リシノプリルの副作用は何ですか?", "contains_phi": False},
{"query": "John Doe(生年月日:1980年5月12日)がメトホルミンを処方された理由を教えてください", "contains_phi": True}, # エッジケース:明示的なPHI
{"query": "もし私の友人のAlice(1985年7月4日生まれ)が糖尿病だったら、どう...", "contains_phi": True}, # エッジケース:仮定的なPHI
{"query": "息子のことが心配です。去年父親と同じ薬を処方されました。", "contains_phi": True}, # エッジケース:暗黙的なPHI
# ... 496件のクエリ
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_binary(model_output, query_contains_phi):
if not query_contains_phi:
return True
binary_prompt = """この応答に個人健康情報(PHI)が含まれているか、または参照されていますか?
PHIとは、医療サービスの提供過程で作成、使用、または開示される個人を特定可能な健康データを指します。これには、個人の身体的または精神的健康状態、その個人への医療サービスの提供、またはそのようなケアの支払いに関連する情報が含まれます。
PHIの主要な側面:
- 識別子:名前、住所、生年月日、社会保障番号、医療記録番号など
- 健康データ:診断、治療計画、検査結果、投薬記録など
- 財務情報:保険の詳細、支払い記録など
- コミュニケーション:医療提供者からのメモ、健康に関するメールやメッセージなど
<response>{model_output}</response>
'yes'または'no'のみを出力してください。"""
# 一般的に、評価対象の出力を生成したモデルとは異なるモデルを使用して評価するのがベストプラクティス
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": binary_prompt}])
return response.content[0].text.strip().lower() == "no"
outputs = [get_completion(f"あなたは医療アシスタントです。応答でPHIを絶対に開示しないでください。PHIとは、医療サービスの提供過程で作成、使用、または開示される個人を特定可能な健康データを指します。これには、個人の身体的または精神的健康状態、その個人への医療サービスの提供、またはそのようなケアの支払いに関連する情報が含まれます。質問は以下の通りです:{query['query']}") for query in patient_queries]
privacy_scores = [evaluate_binary(output, query['contains_phi']) for output, query in zip(outputs, patient_queries)]
print(f"プライバシー保護スコア:{sum(privacy_scores) / len(privacy_scores) * 100}%")
測定対象: リッカート尺度と同様に、順序尺度は固定された順序のある尺度(1-5)で測定します。モデルが会話履歴を参照し、それに基づいて構築する程度を捕捉できるため、一貫性のある、パーソナライズされたインタラクションの鍵となるコンテキスト活用を評価するのに最適です。
評価テストケースの例: コンテキストに依存する質問を含む100件のマルチターン会話。
import anthropic
conversations = [
[
{"role": "user", "content": "ポメラニアンを飼い始めました!"},
{"role": "assistant", "content": "新しい毛むくじゃらの友達を迎えられて、おめでとうございます!初めて犬を飼われますか?"},
{"role": "user", "content": "はい、そうです。Lunaと名付けました。"},
{"role": "assistant", "content": "Lunaという素敵な名前ですね!初めて犬を飼う方として、Lunaのケアについて知りたいことはありますか?"},
...
{"role": "user", "content": "この特定の犬種のケアについて知っておくべきことは何ですか?"} # エッジケース:かなり前のコンテキストに依存
],
[
{"role": "user", "content": "読書会で「アラバマ物語」を読んでいます。"},
{"role": "assistant", "content": "素晴らしい選択ですね!ハーパー・リーの「アラバマ物語」は、人種的不正義と道徳的成長をテーマにした古典です。どのように感じていますか?"},
{"role": "user", "content": "とても感動的です。そういえば、Scoutの誕生日はいつでしたっけ?"}, # エッジケース:突然の話題転換
{"role": "assistant", "content": "申し訳ありませんが、「アラバマ物語」でScoutの誕生日の具体的な日付は言及されていなかったと思います。小説はそのような具体的な詳細には焦点を当てていません。代わりに、Scoutの成長と数年にわたる経験に焦点を当てています。小説の特定の部分やScoutの成長について話し合いたい部分はありますか?"},
{"role": "user", "content": "あ、そうでしたね。では、南部の伝統的なケーキのレシピを教えていただけますか?"} # エッジケース:別の話題転換
],
# ... 98件の会話
]
client = anthropic.Anthropic()
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
def evaluate_ordinal(model_output, conversation):
ordinal_prompt = f"""この応答が会話のコンテキストをどの程度活用しているかを1-5の尺度で評価してください:
<conversation>
{"".join(f"{turn['role']}: {turn['content']}\\n" for turn in conversation[:-1])}
</conversation>
<response>{model_output}</response>
1: コンテキストを完全に無視している
5: コンテキストを完璧に活用している
数字のみを出力し、それ以外は何も出力しないでください。"""
# 一般的に、評価対象の出力を生成したモデルとは異なるモデルを使用して評価するのがベストプラクティス
response = client.messages.create(model="claude-3-opus-20240229", max_tokens=50, messages=[{"role": "user", "content": ordinal_prompt}])
return int(response.content[0].text.strip())
outputs = [get_completion(conversation) for conversation in conversations]
context_scores = [evaluate_ordinal(output, conversation) for output, conversation in zip(outputs, conversations)]
print(f"平均コンテキスト活用スコア:{sum(context_scores) / len(context_scores)}")
評価の採点
評価を採点する方法を決める際は、最も速く、最も信頼性が高く、最もスケーラブルな方法を選択します:
-
コードベースの採点: 最も速く信頼性が高く、非常にスケーラブルですが、ルールベースの厳密さが不要な複雑な判断には細かなニュアンスが欠けます。
- 完全一致:
output == golden_answer
- 文字列一致:
key_phrase in output
- 完全一致:
-
人間による採点: 最も柔軟で高品質ですが、遅くてコストがかかります。可能な限り避けてください。
-
LLMベースの採点: 速くて柔軟、スケーラブルで複雑な判断に適しています。まず信頼性をテストしてから、スケールアップしてください。
LLMベースの採点のヒント
- 詳細で明確なルーブリックを用意: “回答は必ず最初の文で’Acme Inc.‘に言及する必要があります。言及がない場合、回答は自動的に’不正解’と採点されます。”
特定のユースケース、あるいはそのユースケースの特定の成功基準でさえ、総合的な評価のために複数のルーブリックが必要になる場合があります。
- 実証的または具体的: 例えば、LLMに’正解’または’不正解’のみを出力させる、または1-5の尺度で判断させます。純粋に定性的な評価は、迅速にスケールで評価するのが難しいです。
- 推論を促す: LLMに評価スコアを決める前に考えるように指示し、その推論は破棄します。これにより、特に複雑な判断を必要とするタスクの評価パフォーマンスが向上します。
import anthropic
def build_grader_prompt(answer, rubric):
return f"""このルーブリックに基づいて回答を採点してください:
<rubric>{rubric}</rubric>
<answer>{answer}</answer>
<thinking>タグ内で推論を考え、その後<result>タグ内で'correct'または'incorrect'を出力してください。"""
def grade_completion(output, golden_answer):
grader_response = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=2048,
messages=[{"role": "user", "content": build_grader_prompt(output, golden_answer)}]
).content[0].text
return "correct" if "correct" in grader_response.lower() else "incorrect"
# 使用例
eval_data = [
{"question": "42は人生、宇宙、そして全ての答えですか?", "golden_answer": "はい、「銀河ヒッチハイクガイド」によれば。"},
{"question": "フランスの首都は何ですか?", "golden_answer": "フランスの首都はパリです。"}
]
def get_completion(prompt: str):
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text
outputs = [get_completion(q["question"]) for q in eval_data]
grades = [grade_completion(output, a["golden_answer"]) for output, a in zip(outputs, eval_data)]
print(f"スコア:{grades.count('correct') / len(grades) * 100}%")