llm-evaluation について
llm-evaluationは、LLMアプリケーションの評価を支援するスキルです。モデルやプロンプトの比較、性能低下の追跡、出力品質の分析に向け、自動指標、人手評価、LLM-as-Judge、A/Bテスト、ベンチマークを扱います。
主な機能
- 01生成・分類・検索の指標計算
- 02LLM-as-Judgeによる出力評価
- 03人手評価の観点設定
- 04A/Bテストと統計的比較
- 05ベースラインに対する性能低下の検出
- 06ベンチマークの実行
活用シーン
- 01モデルやプロンプトのバージョン比較
- 02デプロイ前の性能低下検出
- 03RAGの検索品質評価
- 04人手評価と自動指標による出力分析
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ ovachiever/ droid-tings --skill llm-evaluationインストール後、Claude Code で/llm-evaluationを入力して読み込まれたか確認します。
詳細
LLM の評価
自動評価指標から人手による評価、A/B テストまで、LLM アプリケーションを包括的に評価するための戦略を習得します。
このスキルを使用する場面
- LLM アプリケーションのパフォーマンスを体系的に測定する
- 異なるモデルやプロンプトを比較する
- デプロイ前にパフォーマンスの低下を検出する
- プロンプトの変更による改善を検証する
- 本番システムへの信頼性を高める
- ベースラインを確立し、経時的に進捗を追跡する
- 予期しないモデルの動作をデバッグする
評価の主な種類
1. 自動評価指標
計算スコアを用いた、高速で再現性があり、スケーラブルな評価です。
テキスト生成:
- BLEU:N グラムの重複度(翻訳)
- ROUGE:再現率重視(要約)
- METEOR:意味的類似度
- BERTScore:埋め込みベースの類似度
- Perplexity:言語モデルの信頼度
分類:
- Accuracy:正解率
- Precision/Recall/F1:クラスごとのパフォーマンス
- Confusion Matrix:エラーパターン
- AUC-ROC:ランキング品質
検索(RAG):
- MRR:平均逆順位
- NDCG:正規化割引累積利得
- Precision@K:上位 K 件における適合率
- Recall@K:上位 K 件における網羅率
2. 人手による評価
自動化が難しい品質面を手作業で評価します。
評価軸:
- Accuracy:事実の正確性
- Coherence:論理的な流れ
- Relevance:質問への回答になっているか
- Fluency:自然な言語表現の品質
- Safety:有害なコンテンツがないこと
- Helpfulness:ユーザーにとって有用であること
3. LLM-as-Judge
より強力な LLM を使って、性能の低いモデルの出力を評価します。
アプローチ:
- Pointwise:個々の回答を採点する
- Pairwise:2 つの回答を比較する
- Reference-based:正解データと比較する
- Reference-free:正解データなしで評価する
クイックスタート
from llm_eval import EvaluationSuite, Metric
# Define evaluation suite
suite = EvaluationSuite([
Metric.accuracy(),
Metric.bleu(),
Metric.bertscore(),
Metric.custom(name="groundedness", fn=check_groundedness)
])
# Prepare test cases
test_cases = [
{
"input": "What is the capital of France?",
"expected": "Paris",
"context": "France is a country in Europe. Paris is its capital."
},
# ... more test cases
]
# Run evaluation
results = suite.evaluate(
model=your_model,
test_cases=test_cases
)
print(f"Overall Accuracy: {results.metrics['accuracy']}")
print(f"BLEU Score: {results.metrics['bleu']}")
自動評価指標の実装
BLEU スコア
from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction
def calculate_bleu(reference, hypothesis):
"""Calculate BLEU score between reference and hypothesis."""
smoothie = SmoothingFunction().method4
return sentence_bleu(
[reference.split()],
hypothesis.split(),
smoothing_function=smoothie
)
# Usage
bleu = calculate_bleu(
reference="The cat sat on the mat",
hypothesis="A cat is sitting on the mat"
)
ROUGE スコア
from rouge_score import rouge_scorer
def calculate_rouge(reference, hypothesis):
"""Calculate ROUGE scores."""
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
scores = scorer.score(reference, hypothesis)
return {
'rouge1': scores['rouge1'].fmeasure,
'rouge2': scores['rouge2'].fmeasure,
'rougeL': scores['rougeL'].fmeasure
}
BERTScore
from bert_score import score
def calculate_bertscore(references, hypotheses):
"""Calculate BERTScore using pre-trained BERT."""
P, R, F1 = score(
hypotheses,
references,
lang='en',
model_type='microsoft/deberta-xlarge-mnli'
)
return {
'precision': P.mean().item(),
'recall': R.mean().item(),
'f1': F1.mean().item()
}
カスタム評価指標
def calculate_groundedness(response, context):
"""Check if response is grounded in provided context."""
# Use NLI model to check entailment
from transformers import pipeline
nli = pipeline("text-classification", model="microsoft/deberta-large-mnli")
result = nli(f"{context} [SEP] {response}")[0]
# Return confidence that response is entailed by context
return result['score'] if result['label'] == 'ENTAILMENT' else 0.0
def calculate_toxicity(text):
"""Measure toxicity in generated text."""
from detoxify import Detoxify
results = Detoxify('original').predict(text)
return max(results.values()) # Return highest toxicity score
def calculate_factuality(claim, knowledge_base):
"""Verify factual claims against knowledge base."""
# Implementation depends on your knowledge base
# Could use retrieval + NLI, or fact-checking API
pass
LLM-as-Judge のパターン
単一出力の評価
def llm_judge_quality(response, question):
"""Use GPT-5 to judge response quality."""
prompt = f"""Rate the following response on a scale of 1-10 for:
1. Accuracy (factually correct)
2. Helpfulness (answers the question)
3. Clarity (well-written and understandable)
Question: {question}
Response: {response}
Provide ratings in JSON format:
{{
"accuracy": <1-10>,
"helpfulness": <1-10>,
"clarity": <1-10>,
"reasoning": "<brief explanation>"
}}
"""
result = openai.ChatCompletion.create(
model="gpt-5",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
return json.loads(result.choices[0].message.content)
ペアワイズ比較
def compare_responses(question, response_a, response_b):
"""Compare two responses using LLM judge."""
prompt = f"""Compare these two responses to the question and determine which is better.
Question: {question}
Response A: {response_a}
Response B: {response_b}
Which response is better and why? Consider accuracy, helpfulness, and clarity.
Answer with JSON:
{{
"winner": "A" or "B" or "tie",
"reasoning": "<explanation>",
"confidence": <1-10>
}}
"""
result = openai.ChatCompletion.create(
model="gpt-5",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
return json.loads(result.choices[0].message.content)
人手による評価のフレームワーク
アノテーションのガイドライン
class AnnotationTask:
"""Structure for human annotation task."""
def __init__(self, response, question, context=None):
self.response = response
self.question = question
self.context = context
def get_annotation_form(self):
return {
"question": self.question,
"context": self.context,
"response": self.response,
"ratings": {
"accuracy": {
"scale": "1-5",
"description": "Is the response factually correct?"
},
"relevance": {
"scale": "1-5",
"description": "Does it answer the question?"
},
"coherence": {
"scale": "1-5",
"description": "Is it logically consistent?"
}
},
"issues": {
"factual_error": False,
"hallucination": False,
"off_topic": False,
"unsafe_content": False
},
"feedback": ""
}
評価者間の一致度
from sklearn.metrics import cohen_kappa_score
def calculate_agreement(rater1_scores, rater2_scores):
"""Calculate inter-rater agreement."""
kappa = cohen_kappa_score(rater1_scores, rater2_scores)
interpretation = {
kappa < 0: "Poor",
kappa < 0.2: "Slight",
kappa < 0.4: "Fair",
kappa < 0.6: "Moderate",
kappa < 0.8: "Substantial",
kappa <= 1.0: "Almost Perfect"
}
return {
"kappa": kappa,
"interpretation": interpretation[True]
}
A/B テスト
統計的検定のフレームワーク
from scipy import stats
import numpy as np
class ABTest:
def __init__(self, variant_a_name="A", variant_b_name="B"):
self.variant_a = {"name": variant_a_name, "scores": []}
self.variant_b = {"name": variant_b_name, "scores": []}
def add_result(self, variant, score):
"""Add evaluation result for a variant."""
if variant == "A":
self.variant_a["scores"].append(score)
else:
self.variant_b["scores"].append(score)
def analyze(self, alpha=0.05):
"""Perform statistical analysis."""
a_scores = self.variant_a["scores"]
b_scores = self.variant_b["scores"]
# T-test
t_stat, p_value = stats.ttest_ind(a_scores, b_scores)
# Effect size (Cohen's d)
pooled_std = np.sqrt((np.std(a_scores)**2 + np.std(b_scores)**2) / 2)
cohens_d = (np.mean(b_scores) - np.mean(a_scores)) / pooled_std
return {
"variant_a_mean": np.mean(a_scores),
"variant_b_mean": np.mean(b_scores),
"difference": np.mean(b_scores) - np.mean(a_scores),
"relative_improvement": (np.mean(b_scores) - np.mean(a_scores)) / np.mean(a_scores),
"p_value": p_value,
"statistically_significant": p_value < alpha,
"cohens_d": cohens_d,
"effect_size": self.interpret_cohens_d(cohens_d),
"winner": "B" if np.mean(b_scores) > np.mean(a_scores) else "A"
}
@staticmethod
def interpret_cohens_d(d):
"""Interpret Cohen's d effect size."""
abs_d = abs(d)
if abs_d < 0.2:
return "negligible"
elif abs_d < 0.5:
return "small"
elif abs_d < 0.8:
return "medium"
else:
return "large"
回帰テスト
回帰の検出
class RegressionDetector:
def __init__(self, baseline_results, threshold=0.05):
self.baseline = baseline_results
self.threshold = threshold
def check_for_regression(self, new_results):
"""Detect if new results show regression."""
regressions = []
for metric in self.baseline.keys():
baseline_score = self.baseline[metric]
new_score = new_results.get(metric)
if new_score is None:
continue
# Calculate relative change
relative_change = (new_score - baseline_score) / baseline_score
# Flag if significant decrease
if relative_change < -self.threshold:
regressions.append({
"metric": metric,
"baseline": baseline_score,
"current": new_score,
"change": relative_change
})
return {
"has_regression": len(regressions) > 0,
"regressions": regressions
}
ベンチマーク
ベンチマークの実行
class BenchmarkRunner:
def __init__(self, benchmark_dataset):
self.dataset = benchmark_dataset
def run_benchmark(self, model, metrics):
"""Run model on benchmark and calculate metrics."""
results = {metric.name: [] for metric in metrics}
for example in self.dataset:
# Generate prediction
prediction = model.predict(example["input"])
# Calculate each metric
for metric in metrics:
score = metric.calculate(
prediction=prediction,
reference=example["reference"],
context=example.get("context")
)
results[metric.name].append(score)
# Aggregate results
return {
metric: {
"mean": np.mean(scores),
"std": np.std(scores),
"min": min(scores),
"max": max(scores)
}
for metric, scores in results.items()
}
リソース
- references/metrics.md:評価指標の包括的なガイド
- references/human-evaluation.md:アノテーションのベストプラクティス
- references/benchmarking.md:標準的なベンチマーク
- references/a-b-testing.md:統計的検定のガイド
- references/regression-testing.md:CI/CD との統合
- assets/evaluation-framework.py:完全な評価ハーネス
- assets/benchmark-dataset.jsonl:データセットの例
- scripts/evaluate-model.py:自動評価の実行スクリプト
ベストプラクティス
- 複数の評価指標:包括的に把握するため、多様な評価指標を使用する
- 代表的なデータ:実環境に即した多様な例でテストする
- ベースライン:常にベースラインのパフォーマンスと比較する
- 統計的な厳密さ:比較には適切な統計的検定を使用する
- 継続的な評価:CI/CD パイプラインに組み込む
- 人手による検証:自動評価指標と人間の判断を組み合わせる
- エラー分析:失敗を調査し、弱点を把握する
- バージョン管理:評価結果を経時的に追跡する
よくある落とし穴
- 単一の評価指標への固執:他の指標を犠牲にして、1 つの指標だけを最適化する
- サンプルサイズが小さい:少なすぎる例から結論を導く
- データ汚染:トレーニングデータを使ってテストする
- ばらつきの無視:統計的な不確実性を考慮しない
- 評価指標の不一致:ビジネス目標に沿わない評価指標を使用する
コメント
Markdown 対応