AI 评估方法
大约 11 分钟约 3447 字
AI 评估方法
简介
模型评估的作用不是为了在报告里填几个分数,而是回答三个关键问题:模型到底解决了什么问题、结果是否可信、上线后会不会出事。不同任务的评估方法差异很大:分类任务关注 Precision/Recall/F1,检索关注 Recall@K/MRR,生成任务还需要结合 BLEU/ROUGE、人工评测、事实性和业务成功率一起看。
评估体系的建设应该与模型开发同步启动,而不是在模型训练完成后才补充。好的评估体系能够回答:新模型比旧模型好在哪?差在哪?差在哪些样本上?这些差异对业务意味着什么?没有这些问题的答案,模型迭代就是在黑暗中摸索。
特点
评估体系架构
class EvaluationFramework:
"""
完整的评估框架应包含:
1. 数据集管理(版本化、分层、更新)
2. 指标计算(自动指标、业务指标)
3. 错误分析(失败样本归类、模式识别)
4. 在线监控(A/B Test、实时指标)
5. 报告生成(对比报告、趋势分析)
"""
def __init__(self, name, dataset_version, model_version):
self.name = name
self.dataset_version = dataset_version
self.model_version = model_version
self.results = {}
self.error_cases = []
def add_metric(self, metric_name, value, threshold=None):
"""添加评估指标"""
self.results[metric_name] = {
"value": value,
"threshold": threshold,
"pass": threshold is None or value >= threshold
}
def add_error_case(self, input_data, expected, actual, error_type):
"""记录失败样本"""
self.error_cases.append({
"input": input_data,
"expected": expected,
"actual": actual,
"error_type": error_type
})
def summary(self):
"""生成评估摘要"""
total = len(self.results)
passed = sum(1 for r in self.results.values() if r["pass"])
return {
"name": self.name,
"dataset_version": self.dataset_version,
"model_version": self.model_version,
"metrics_passed": f"{passed}/{total}",
"error_count": len(self.error_cases),
"details": self.results
}
eval = EvaluationFramework("faq_qa", "v2026.04.12", "model_v3")
eval.add_metric("accuracy", 0.92, threshold=0.90)
eval.add_metric("latency_p95_ms", 850, threshold=1000)
eval.add_metric("hallucination_rate", 0.03, threshold=0.05)
print(eval.summary())实现
分类任务评估
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, classification_report, confusion_matrix
import numpy as np
y_true = np.array([1, 0, 1, 1, 0, 0, 1, 0])
y_pred = np.array([1, 0, 1, 0, 0, 1, 1, 0])
metrics = {
"accuracy": accuracy_score(y_true, y_pred),
"precision": precision_score(y_true, y_pred),
"recall": recall_score(y_true, y_pred),
"f1": f1_score(y_true, y_pred)
}
print(metrics)
print(confusion_matrix(y_true, y_pred))
print(classification_report(y_true, y_pred, digits=4))# 类别不平衡时更关注 precision / recall / f1,而不是只看 accuracy
fraud_true = np.array([0, 0, 0, 0, 0, 1, 0, 0, 0, 1])
fraud_pred = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
print("accuracy:", accuracy_score(fraud_true, fraud_pred))
print("recall:", recall_score(fraud_true, fraud_pred, zero_division=0))
# accuracy 很高,但 recall=0,说明模型完全没抓到欺诈样本多分类与宏平均/微平均
from sklearn.metrics import precision_recall_fscore_support
# 多分类评估:宏平均 vs 微平均 vs 加权平均
y_true_multi = np.array([0, 1, 2, 0, 1, 2, 0, 1, 2])
y_pred_multi = np.array([0, 1, 1, 0, 2, 2, 0, 1, 0])
precision_macro, recall_macro, f1_macro, _ = precision_recall_fscore_support(
y_true_multi, y_pred_multi, average='macro'
)
precision_micro, recall_micro, f1_micro, _ = precision_recall_fscore_support(
y_true_multi, y_pred_multi, average='micro'
)
precision_weighted, recall_weighted, f1_weighted, _ = precision_recall_fscore_support(
y_true_multi, y_pred_multi, average='weighted'
)
print(f"宏平均 (macro): P={precision_macro:.4f} R={recall_macro:.4f} F1={f1_macro:.4f}")
print(f"微平均 (micro): P={precision_micro:.4f} R={recall_micro:.4f} F1={f1_micro:.4f}")
print(f"加权平均 (weighted): P={precision_weighted:.4f} R={recall_weighted:.4f} F1={f1_weighted:.4f}")
print("""
宏平均:每个类别同等重要,适合类别均衡的场景
微平均:每个样本同等重要,适合关注整体正确率的场景
加权平均:按类别样本数加权,适合类别不均衡的场景
""")AUC-ROC 与阈值调优
# 阈值调优
from sklearn.metrics import precision_recall_curve
probs = np.array([0.91, 0.12, 0.78, 0.31, 0.22, 0.65, 0.84, 0.11])
precision, recall, thresholds = precision_recall_curve(y_true, probs)
for p, r, t in zip(precision[:-1], recall[:-1], thresholds):
print({"threshold": round(float(t), 2), "precision": round(float(p), 2), "recall": round(float(r), 2)})from sklearn.metrics import roc_auc_score, roc_curve
# AUC-ROC:衡量模型在不同阈值下的整体区分能力
y_scores = np.array([0.91, 0.12, 0.78, 0.31, 0.22, 0.65, 0.84, 0.11])
auc = roc_auc_score(y_true, y_scores)
print(f"AUC-ROC: {auc:.4f}")
print("AUC = 1.0 完美区分,AUC = 0.5 等于随机猜测")检索与推荐任务评估
# Recall@K / MRR
queries = [
{"query": "退款规则", "gold": [3], "pred": [5, 3, 8, 2, 1]},
{"query": "电子发票", "gold": [7], "pred": [7, 4, 9, 1, 2]}
]
recall_hits = 0
mrr_total = 0.0
for item in queries:
found_rank = None
for rank, doc_id in enumerate(item["pred"], start=1):
if doc_id in item["gold"]:
found_rank = rank
break
if found_rank is not None:
recall_hits += 1
mrr_total += 1 / found_rank
recall_at_5 = recall_hits / len(queries)
mrr = mrr_total / len(queries)
print({"recall@5": recall_at_5, "mrr": round(mrr, 4)})# NDCG 示例(推荐排序质量)
import math
def dcg(rels):
return sum(rel / math.log2(idx + 2) for idx, rel in enumerate(rels))
actual_relevance = [3, 2, 0, 1]
ideal_relevance = sorted(actual_relevance, reverse=True)
ndcg = dcg(actual_relevance) / dcg(ideal_relevance)
print("ndcg:", round(ndcg, 4))# 对 RAG / 搜索系统,至少分两层评估:
# 1. 检索是否召回正确文档
# 2. 最终答案是否正确、有引用、无幻觉生成任务与人工评估
from rouge_score import rouge_scorer
references = ["订单签收后7天内可以申请退款"]
predictions = ["用户在签收后 7 天内可退款"]
scorer = rouge_scorer.RougeScorer(["rouge1", "rougeL"], use_stemmer=False)
score = scorer.score(references[0], predictions[0])
print({
"rouge1_f": round(score["rouge1"].fmeasure, 4),
"rougeL_f": round(score["rougeL"].fmeasure, 4)
})# BLEU 示例
from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction
reference = ["the", "product", "can", "be", "returned", "within", "7", "days"]
candidate = ["the", "item", "can", "be", "returned", "within", "7", "days"]
bleu = sentence_bleu([reference], candidate, smoothing_function=SmoothingFunction().method1)
print("bleu:", round(bleu, 4))BERTScore 与语义相似度评估
def bertscore_evaluation(references, predictions):
"""
BERTScore:基于 BERT 嵌入的语义相似度评估
比 BLEU/ROUGE 更能捕捉语义等价性
"""
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
ref_embeddings = model.encode(references)
pred_embeddings = model.encode(predictions)
from sklearn.metrics.pairwise import cosine_similarity
similarities = cosine_similarity(ref_embeddings, pred_embeddings)
avg_similarity = np.diag(similarities).mean()
print(f"BERTScore 平均相似度: {avg_similarity:.4f}")
return avg_similarity
print("BERTScore 评估函数已定义")LLM-as-a-Judge 评估
def llm_judge_evaluator(model, question, answer, criteria):
"""
使用大模型作为评判者(LLM-as-a-Judge)
适用于无法用自动指标衡量的生成任务
评估维度:
- 正确性:答案是否正确
- 完整性:是否覆盖所有要点
- 相关性:是否与问题相关
- 清晰性:表达是否清晰
- 安全性:是否包含有害内容
"""
judge_prompt = f"""请评估以下回答的质量。
问题:{question}
回答:{answer}
评估标准:
{criteria}
请按以下 JSON 格式输出评估结果:
{{
"correctness": {{"score": 1-5, "reason": "原因"}},
"completeness": {{"score": 1-5, "reason": "原因"}},
"relevance": {{"score": 1-5, "reason": "原因"}},
"overall": 1-5
}}
"""
# response = model.generate(judge_prompt)
# return json.loads(response)
print("LLM-as-a-Judge 评估模板已定义")
print("""
LLM-as-a-Judge 的注意事项:
1. 评判模型应与被评估模型不同
2. 需要与人工评估做校准
3. 存在位置偏差、长度偏差等系统性偏差
4. 适合作为辅助工具,不应完全替代人工评估
""")人工评测模板
human_eval = [
{
"question": "退款后发票怎么办?",
"answer": "退款成功后原发票作废,如需重开需联系财务。",
"dimension": {
"correctness": 5,
"helpfulness": 4,
"groundedness": 5,
"safety": 5
}
}
]
print(human_eval)线上评估与错误分析
# 在线 A/B Test 关注业务指标
ab_metrics = {
"variant_a": {
"ctr": 0.121,
"conversion": 0.028,
"avg_response_ms": 820,
"complaint_rate": 0.004
},
"variant_b": {
"ctr": 0.137,
"conversion": 0.031,
"avg_response_ms": 910,
"complaint_rate": 0.003
}
}
print(ab_metrics)# 统计显著性检验
from scipy import stats
import numpy as np
def ab_test_significance(conversions_a, total_a, conversions_b, total_b):
"""A/B Test 统计显著性检验"""
rate_a = conversions_a / total_a
rate_b = conversions_b / total_b
# Z 检验
se = np.sqrt(rate_a * (1 - rate_a) / total_a + rate_b * (1 - rate_b) / total_b)
z_score = (rate_b - rate_a) / se
p_value = 1 - stats.norm.cdf(z_score)
return {
"rate_a": rate_a, "rate_b": rate_b,
"lift": (rate_b - rate_a) / rate_a,
"z_score": z_score,
"p_value": p_value,
"significant": p_value < 0.05
}
result = ab_test_significance(280, 10000, 310, 10000)
print(f"A/B Test 结果: 提升 {result['lift']*100:.1f}%, p={result['p_value']:.4f}, 显著: {result['significant']}")# 失败样本分类比平均分更有价值
error_cases = [
{"input": "我要退货", "pred": "咨询发票", "true": "退货流程", "category": "intent_confusion"},
{"input": "发票重开", "pred": "未知", "true": "财务流程", "category": "knowledge_missing"},
{"input": "优惠券过期", "pred": "退款规则", "true": "营销规则", "category": "retrieval_error"}
]
# 错误分类统计
from collections import Counter
error_categories = Counter(case["category"] for case in error_cases)
print("错误分类统计:", dict(error_categories))
for case in error_cases:
print(case)# 评估日志建议字段
trace = {
"model_version": "v2026.04.12",
"dataset_version": "eval_set_20260412",
"task": "faq_qa",
"latency_ms": 734,
"cost": 0.012,
"human_score": 4.5,
"retrieval_hit": True,
"hallucination": False
}
print(trace)评估集管理与版本控制
class EvaluationDataset:
"""评估集版本管理"""
def __init__(self, name, version):
self.name = name
self.version = version
self.samples = []
self.metadata = {
"created_at": "2026-04-12",
"total_samples": 0,
"categories": {},
"difficulty_distribution": {}
}
def add_sample(self, input_data, expected_output, category="general", difficulty="medium"):
"""添加评估样本"""
self.samples.append({
"input": input_data,
"expected": expected_output,
"category": category,
"difficulty": difficulty
})
self.metadata["total_samples"] = len(self.samples)
self.metadata["categories"][category] = \
self.metadata["categories"].get(category, 0) + 1
def get_by_category(self, category):
"""按类别获取样本"""
return [s for s in self.samples if s["category"] == category]
def summary(self):
"""数据集概览"""
return self.metadata
# 使用示例
eval_set = EvaluationDataset("faq_eval", "v2.0")
eval_set.add_sample("退款规则", "签收后7天内可退款", "退款", "easy")
eval_set.add_sample("发票重开流程", "联系财务部门申请", "发票", "medium")
eval_set.add_sample("跨店铺满减规则", "...", "营销", "hard")
print(eval_set.summary())RAG 专项评估
class RAGEvaluator:
"""
RAG 系统专项评估
分三层:检索质量 → 上下文质量 → 最终答案质量
"""
def evaluate_retrieval(self, queries, gold_docs, pred_docs, k=5):
"""检索层评估:Recall@K, MRR, NDCG"""
recall_scores = []
mrr_scores = []
for query, gold, pred in zip(queries, gold_docs, pred_docs):
pred_k = pred[:k]
# Recall@K
hits = len(set(gold) & set(pred_k))
recall_scores.append(hits / len(gold) if gold else 0)
# MRR
for rank, doc_id in enumerate(pred_k, 1):
if doc_id in gold:
mrr_scores.append(1.0 / rank)
break
else:
mrr_scores.append(0.0)
return {
"recall@k": np.mean(recall_scores),
"mrr": np.mean(mrr_scores)
}
def evaluate_answer(self, questions, answers, references):
"""答案层评估:正确性、完整性、幻觉检测"""
results = []
for q, a, ref in zip(questions, answers, references):
# 使用 LLM-as-a-Judge 评估
results.append({
"question": q,
"has_hallucination": self._detect_hallucination(a, ref),
"is_relevant": self._check_relevance(a, q),
"has_citation": self._check_citation(a)
})
return results
def _detect_hallucination(self, answer, reference):
"""幻觉检测:检查答案是否包含参考文本不支持的信息"""
# 简化实现:实际应使用 NLI 模型或 LLM 评判
return False
def _check_relevance(self, answer, question):
"""相关性检查"""
return True
def _check_citation(self, answer):
"""引用检查:答案是否引用了来源"""
return "[" in answer and "]" in answer
print("RAG 专项评估器已定义")优点
缺点
总结
AI 评估不是给模型打一个总分,而是建立一套可持续比较、可解释、能指导优化的体系。最常见的做法是:离线指标保证基础质量,人工评测补充真实性与可用性,在线实验检验业务价值,再通过错误分析找到下一轮优化方向。
关键知识点
- 不同任务必须使用不同指标,不能拿 accuracy 评价一切。
- 对类别不平衡问题,Precision/Recall/F1 通常比 Accuracy 更重要。
- 生成式任务需要自动指标 + 人工评测 + 业务指标联合判断。
- 错误样本分析比单看平均分更能指导后续改进。
- 评估集需要定期更新,防止数据漂移导致评估结果失真。
项目落地视角
- FAQ 问答系统:先评估召回,再评估最终答案正确性。
- 风控/欺诈识别:重点看召回率和误杀率,而不是表面高准确率。
- 推荐系统:离线看 NDCG,在线看 CTR/转化率。
- 文本生成:自动指标只能参考,最终还得看人工质量与事实性。
常见误区
- 拿一个单一指标决定模型上线与否。
- 评估集长期不更新,导致与真实业务脱节。
- 离线分数提升一点点,就默认线上一定更好。
- 不记录失败样本,导致模型一直在重复犯同类错误。
- 评估集泄漏到训练集,导致评估结果虚高。
进阶路线
- 建立任务级标准评估集和回归测试流水线。
- 引入 LLM-as-a-Judge 辅助评测,但保留人工抽检。
- 把评估结果接入实验平台、监控平台和模型注册中心。
- 针对 RAG、Agent、多模态分别建立专项评估维度。
适用场景
- 分类、回归、检索、推荐、生成、对话等各类 AI 任务。
- 模型迭代上线前的版本比较与风险评估。
- A/B 测试和生产环境质量追踪。
- 企业内部模型治理与效果复盘。
落地建议
- 每个任务都明确主指标、辅助指标和否决指标。
- 固定评估集版本,并记录数据和模型版本号。
- 为失败样本建立分类体系,定期做 error review。
- 不要只看离线分数,要同步观察延迟、成本、投诉率等上线指标。
排错清单
- 检查评估集是否泄漏到训练集。
- 检查指标计算脚本和标签映射是否正确。
- 检查离线/在线输入分布是否存在明显偏差。
- 检查失败样本是否集中在某类意图、某类用户或某类数据源。
复盘问题
- 当前模型最重要的评估指标,真的对应你的业务目标吗?
- 你能否明确区分"分数变差"是数据变化还是模型退化?
- 哪些高价值失败样本应该优先进入下一轮训练或规则补充?
- 如果今天必须上线,你依据的评估证据是否足够充分?
