为什么评估 AI 模型这么难
评估传统软件很简单:输入 A,期望输出 B,实际输出 B,测试通过。但评估 AI 模型完全不同——同一个问题可以有无数种”正确”的回答。
问题: "用 Python 写一个排序函数"
回答 A: 用冒泡排序实现 → 正确但低效
回答 B: 用快速排序实现 → 正确且高效
回答 C: 直接调用 sorted() → 正确且实用
回答 D: 用归并排序实现 → 正确且稳定
哪个"更好"?取决于上下文。
我们需要一套系统化的评估方法,而不是凭感觉判断。
传统 NLP 评估指标
BLEU(Bilingual Evaluation Understudy)
BLEU 最初用于机器翻译评估,衡量生成文本与参考文本的 n-gram 重叠程度。
# BLEU 的核心思想
reference = "the cat sat on the mat"
candidate = "the cat is on the mat"
# 1-gram 匹配: the(2), cat(1), on(1), the(1), mat(1) = 5/6
# 2-gram 匹配: "the cat"(1), "on the"(1), "the mat"(1) = 3/5
# 最终 BLEU 是各 n-gram 精确率的几何平均
from collections import Counter
def simple_bleu(reference: str, candidate: str, n: int = 4) -> float:
"""简化版 BLEU 计算"""
ref_tokens = reference.lower().split()
cand_tokens = candidate.lower().split()
scores = []
for i in range(1, n + 1):
# 提取 n-gram
ref_ngrams = Counter(
tuple(ref_tokens[j:j+i]) for j in range(len(ref_tokens) - i + 1)
)
cand_ngrams = Counter(
tuple(cand_tokens[j:j+i]) for j in range(len(cand_tokens) - i + 1)
)
# 计算裁剪后的匹配数
matches = sum(
min(count, ref_ngrams[ngram])
for ngram, count in cand_ngrams.items()
)
total = sum(cand_ngrams.values())
if total == 0:
scores.append(0)
else:
scores.append(matches / total)
# 几何平均
if 0 in scores:
return 0.0
import math
return math.exp(sum(math.log(s) for s in scores) / len(scores))
BLEU 的局限性:
| 问题 | 说明 |
|---|---|
| 不考虑语义 | ”我喜欢猫” 和 “我爱猫” BLEU 分数低 |
| 不考虑流畅性 | 词序打乱后 n-gram 可能仍然匹配 |
| 需要参考答案 | 开放式生成没有标准答案 |
| 对长度敏感 | 短文本容易获得高分 |
ROUGE(Recall-Oriented Understudy for Gisting Evaluation)
ROUGE 主要用于文本摘要评估,关注召回率(参考文本中有多少内容被生成文本覆盖)。
def rouge_n(reference: str, candidate: str, n: int = 1) -> dict:
"""计算 ROUGE-N"""
ref_tokens = reference.lower().split()
cand_tokens = candidate.lower().split()
# 提取 n-gram
ref_ngrams = Counter(
tuple(ref_tokens[i:i+n]) for i in range(len(ref_tokens) - n + 1)
)
cand_ngrams = Counter(
tuple(cand_tokens[i:i+n]) for i in range(len(cand_tokens) - n + 1)
)
# 计算匹配
matches = sum(
min(ref_ngrams[ng], cand_ngrams[ng]) for ng in ref_ngrams
)
precision = matches / sum(cand_ngrams.values()) if cand_ngrams else 0
recall = matches / sum(ref_ngrams.values()) if ref_ngrams else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
return {"precision": precision, "recall": recall, "f1": f1}
ROUGE 的变体:
| 变体 | 方法 | 适用场景 |
|---|---|---|
| ROUGE-1 | 1-gram 匹配 | 词级别覆盖 |
| ROUGE-2 | 2-gram 匹配 | 短语级别覆盖 |
| ROUGE-L | 最长公共子序列 | 句子级别结构 |
| ROUGE-S | Skip-bigram | 允许跳跃的词对匹配 |
Perplexity(困惑度)
Perplexity 衡量模型对文本的”惊讶程度”。困惑度越低,说明模型对文本的预测越准确。
import math
def perplexity(log_probs: list[float]) -> float:
"""
计算困惑度
log_probs: 每个 token 的对数概率
"""
avg_log_prob = sum(log_probs) / len(log_probs)
return math.exp(-avg_log_prob)
# 示例
# 模型对 "我 喜欢 编程" 的预测概率
log_probs = [-1.2, -0.8, -2.1] # 对数概率
ppl = perplexity(log_probs)
print(f"Perplexity: {ppl:.2f}")
# 较低的值 = 模型对这段文本更"有信心"
Perplexity 的直觉理解:
Perplexity = 10 → 模型在每个位置平均有 10 个等概率的选择
Perplexity = 100 → 模型在每个位置平均有 100 个等概率的选择
Perplexity 越低 → 模型越"确定"下一个词是什么 → 语言建模能力越强
LLM 评估的特殊挑战
传统指标对 LLM 来说远远不够。LLM 的输出是开放式的,评估维度也更加复杂。
评估维度
| 维度 | 描述 | 传统指标能衡量吗 |
|---|---|---|
| 事实准确性 | 信息是否正确 | 部分(需要事实核查) |
| 逻辑一致性 | 推理是否合理 | 否 |
| 有用性 | 回答是否解决了问题 | 否 |
| 安全性 | 是否包含有害内容 | 否 |
| 创造性 | 回答是否有创意 | 否 |
| 格式遵循 | 是否按要求的格式输出 | 部分 |
| 语言质量 | 表达是否流畅自然 | 部分 |
人工评估
人工评估仍然是 LLM 评估的金标准,但成本高、速度慢。
# 人工评估模板
evaluation_template = {
"task": "评估 AI 回答的质量",
"criteria": [
{
"name": "准确性",
"description": "回答中的信息是否准确",
"scale": "1-5",
"guidelines": {
1: "包含严重错误",
3: "基本准确,有小错误",
5: "完全准确"
}
},
{
"name": "有用性",
"description": "回答是否真正帮助了用户",
"scale": "1-5",
"guidelines": {
1: "完全没有帮助",
3: "部分有帮助",
5: "非常有帮助"
}
},
{
"name": "安全性",
"description": "回答是否安全无害",
"scale": "1-5",
"guidelines": {
1: "包含有害内容",
3: "基本安全",
5: "完全安全"
}
},
]
}
LLM-as-Judge(用 LLM 评估 LLM)
用一个强大的 LLM 来评估另一个 LLM 的输出,是目前最流行的自动化评估方法。
import anthropic
client = anthropic.Anthropic()
def llm_judge(question: str, answer: str, criteria: list[str]) -> dict:
"""使用 Claude 作为评估者"""
criteria_text = "\n".join(f"- {c}" for c in criteria)
judge_prompt = f"""请评估以下 AI 回答的质量。
## 用户问题
{question}
## AI 回答
{answer}
## 评估标准
{criteria_text}
请为每个标准打分(1-5),并给出简短理由。
以 JSON 格式输出:
{{
"scores": {{
"标准名": {{"score": 分数, "reason": "理由"}},
...
}},
"overall": 总分,
"summary": "总体评价"
}}"""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": judge_prompt}]
)
return parse_json(response.content[0].text)
LLM-as-Judge 的注意事项:
| 问题 | 描述 | 缓解方法 |
|---|---|---|
| 位置偏见 | 倾向于选择第一个/最后一个选项 | 随机化选项顺序 |
| 冗长偏见 | 倾向于给更长的回答更高分 | 在评估标准中明确”简洁性” |
| 自我偏见 | 倾向于给自己生成的内容更高分 | 使用不同模型评估 |
| 一致性 | 同一内容多次评估结果不同 | 多次评估取平均 |
主流 Benchmark
通用能力评估
| Benchmark | 评估内容 | 题目数量 | 特点 |
|---|---|---|---|
| MMLU | 57 个学科的多选题 | ~15,000 | 知识广度 |
| HellaSwag | 常识推理 | ~10,000 | 日常推理 |
| ARC | 科学推理 | ~7,800 | 科学知识 |
| TruthfulQA | 事实准确性 | ~800 | 抗幻觉能力 |
| Winogrande | 常识理解 | ~44,000 | 语言理解 |
代码能力评估
| Benchmark | 评估内容 | 语言 | 特点 |
|---|---|---|---|
| HumanEval | 函数级代码生成 | Python | 164 题,经典 |
| MBPP | 基础编程问题 | Python | 974 题 |
| MultiPL-E | 多语言代码生成 | 18 种语言 | HumanEval 多语言版 |
| SWE-bench | 真实 GitHub Issue 修复 | Python | 最接近实际开发 |
数学推理评估
| Benchmark | 评估内容 | 难度 |
|---|---|---|
| GSM8K | 小学数学应用题 | 基础 |
| MATH | 竞赛级数学 | 高级 |
| AIME | 美国数学邀请赛 | 极高 |
如何解读 Benchmark 分数
模型 A 在 MMLU 上得分 90%
模型 B 在 MMLU 上得分 85%
这意味着模型 A 更好吗?不一定。
需要考虑:
1. MMLU 只测试多选题,不测试开放式生成
2. 分数差异可能来自特定学科
3. Benchmark 可能被"刷分"(训练数据污染)
4. 实际使用体验可能完全不同
构建自己的评估流水线
对于实际项目,通用 Benchmark 往往不够。我们需要针对自己的场景构建评估流水线。
第一步:定义评估数据集
# 评估数据集结构
eval_dataset = [
{
"id": "001",
"category": "客服问答",
"input": "你们的退货政策是什么?",
"reference": "我们支持 7 天无理由退货...",
"criteria": ["准确性", "完整性", "语气"],
"metadata": {"difficulty": "easy", "topic": "退货"}
},
{
"id": "002",
"category": "技术支持",
"input": "我的订单显示异常,怎么办?",
"reference": None, # 开放式问题,没有标准答案
"criteria": ["有用性", "步骤清晰度", "语气"],
"metadata": {"difficulty": "medium", "topic": "订单"}
},
]
第二步:实现评估器
interface EvalResult {
id: string;
model: string;
scores: Record<string, number>;
latency: number;
tokenCount: number;
}
async function evaluateModel(
model: ModelClient,
dataset: EvalItem[],
judge: JudgeClient
): Promise<EvalResult[]> {
const results: EvalResult[] = [];
for (const item of dataset) {
const startTime = Date.now();
// 生成回答
const response = await model.generate(item.input);
const latency = Date.now() - startTime;
// 自动评估
const scores: Record<string, number> = {};
// 如果有参考答案,计算 ROUGE
if (item.reference) {
const rouge = calculateRouge(item.reference, response);
scores["rouge_l"] = rouge.f1;
}
// LLM-as-Judge 评估
const judgeResult = await judge.evaluate(
item.input,
response,
item.criteria
);
Object.assign(scores, judgeResult.scores);
results.push({
id: item.id,
model: model.name,
scores,
latency,
tokenCount: estimateTokens(response),
});
}
return results;
}
第三步:对比分析
def compare_models(results_a: list, results_b: list) -> dict:
"""对比两个模型的评估结果"""
comparison = {
"model_a": {"avg_scores": {}, "avg_latency": 0},
"model_b": {"avg_scores": {}, "avg_latency": 0},
"winner_by_category": {},
}
# 按类别汇总
for category in set(r["category"] for r in results_a):
cat_a = [r for r in results_a if r["category"] == category]
cat_b = [r for r in results_b if r["category"] == category]
avg_a = sum(r["overall_score"] for r in cat_a) / len(cat_a)
avg_b = sum(r["overall_score"] for r in cat_b) / len(cat_b)
comparison["winner_by_category"][category] = {
"model_a": avg_a,
"model_b": avg_b,
"winner": "A" if avg_a > avg_b else "B",
}
return comparison
第四步:持续监控
部署前评估 → 基线分数
↓
上线后监控 → 用户反馈 + 自动评估
↓
定期回归测试 → 确保模型更新不降低质量
↓
A/B 测试 → 新模型 vs 旧模型的真实对比
评估的常见陷阱
- Benchmark 过拟合:模型在 Benchmark 上表现好,实际使用体验差
- 评估数据泄露:评估数据出现在训练集中
- 单一指标迷信:只看一个分数,忽略其他维度
- 忽略延迟和成本:准确率高但延迟 10 秒的模型可能不适合实时场景
- 静态评估:只评估一次,不持续监控
总结
评估 AI 模型没有银弹,但我们可以建立一套系统化的方法:
- 传统指标(BLEU、ROUGE、Perplexity)适合特定任务的快速评估
- LLM-as-Judge 是目前最实用的自动化评估方法
- 公开 Benchmark 用于横向对比,但不要迷信分数
- 针对自己的场景构建评估流水线才是最重要的
不能衡量的东西就无法改进。建立评估体系的过程,本身就是深入理解问题的过程。
相关文章
评论
加载中...
评论
加载中...