经典指标:exact-match、F1、accuracy、BLEU-4、ROUGE-L


文档摘要

经典指标:exact-match、F1、accuracy、BLEU-4、ROUGE-L 本节摘要:BLEU、ROUGE-L、F1、exact-match、accuracy——五个至今仍占已发表 LLM 评估数字大多数的指标。本节从第一性原理实现每一个,让你知道那个数字意味着什么。标准库加 numpy 就够:exact-match 与 accuracy 各一行,F1 是 token 集合求交,BLEU-4 是计数加钳制加几何平均,ROUGE-L 是最长公共子序列的动态规划。难点只有一个:选定一个分词器并承诺不变。读完本节,你能指着「分词器在哪决定、平滑在哪施加」的那两行代码,跨论文比数字变成读指标设置而非争论库。 对应原课程:Phase 19 · Lesson 71 · (原英文 )。

经典指标:exact-match、F1、accuracy、BLEU-4、ROUGE-L

本节摘要:BLEU、ROUGE-L、F1、exact-match、accuracy——五个至今仍占已发表 LLM 评估数字大多数的指标。本节从第一性原理实现每一个,让你知道那个数字意味着什么。标准库加 numpy 就够:exact-match 与 accuracy 各一行,F1 是 token 集合求交,BLEU-4 是计数加钳制加几何平均,ROUGE-L 是最长公共子序列的动态规划。难点只有一个:选定一个分词器并承诺不变。读完本节,你能指着「分词器在哪决定、平滑在哪施加」的那两行代码,跨论文比数字变成读指标设置而非争论库。

对应原课程:Phase 19 · Lesson 71 · classical-metrics(原英文 phases/19-capstone-projects/71-classical-metrics/docs/en.md)。本节属第 20 章「毕业项目」的评估赛道。

学习目标

阅读完本节,你应当能够:

  1. 用显式分词规则实现 token 级 exact-match、F1、accuracy
  2. 从零实现 BLEU-4:修正 n-gram 精度、n=1..4 的几何平均、长度惩罚。
  3. 用**最长公共子序列(LCS)**实现 ROUGE-L,以 F-beta 组合精度与召回。
  4. 在第 70 节的 metric_name 字段上分派,让运行器对指标一无所知。
  5. 手算样例钉死行为,而非依赖第三方库。

一、问题与直觉

你会读到一篇论文报 BLEU 28.3,另一篇报 BLEU 0.283;你会发现两个库的 ROUGE-L 差 10 分,只因为一个转小写、一个不转。停止困惑最快的方法是自己写指标,然后指着分词器在哪决定、平滑在哪施加的那行代码。之后,跨论文比数字就是读指标设置,而非争论库。

标准库加 numpy 足矣:BLEU 是计数加钳制,ROUGE-L 是动态规划,F1 是 token 求交,最难的部分是选分词器并承诺不变。

二、分词器

分词器是 re.findall(r"\w+", text.lower())——小写、字母数字连续串、丢标点。本节每个指标都用这个确定的分词器,运行器无权选择。换分词器就是跑另一个基准。

TOKEN_RE = re.compile(r"\w+", re.UNICODE) def tokenize(text): return TOKEN_RE.findall(text.lower())

⚠️ 这是有意的简化。生产要在意 CJK、缩写、代码标识符。本节的要点是:分词器是契约,不是旋钮

三、从零实现

Exact match

def exact_match(pred, targets): return float(any(pred.strip() == t.strip() for t in targets))

每任务返回 1.0 或 0.0,数据集聚合是均值。这是算术、MCQ、短分类的主力。

Token 级 F1

为预测与 target 各建 token 多重集。精度 = 多重集交 / 预测多重集;召回 = 同一交集 / target 多重集;F1 = 调和平均。实现处理空预测、空 target 的边界。多 target 任务取 target 列表上最佳 F1,匹配 SQuAD 风格的广泛行为。

def f1_score(pred, target): p, t = tokenize(pred), tokenize(target) common = Counter(p) & Counter(t) inter = sum(common.values()) if inter == 0: return 0.0 prec, rec = inter / len(p), inter / len(t) return 2 * prec * rec / (prec + rec)

BLEU-4

BLEU 是经典机器翻译指标,至今出现在摘要工作里。我们用语料级 BLEU-4,带标准长度惩罚与修正 n-gram 计数上的加一平滑(避免单个缺失 4-gram 把分数压到零)。对每个候选-参考对,计 n=1..4 的修正 n-gram 精度:修正精度把候选 n-gram 计数钳制到任意参考里该 n-gram 的最大计数,使候选不能靠重复一个短语膨胀。四精度的几何平均外包长度惩罚。

平滑规则是 Lin 与 Och 称为「方法 1」的:对每个 n-gram 精度的分子分母各加一再取 log。这避免了参考无匹配 4-gram 时的 log 0,并在长候选上接近未平滑值。

def bleu4(pred, target): c, r = tokenize(pred), tokenize(target) if not c: return 0.0 precs = [] for n in (1, 2, 3, 4): cand = Counter(zip(*[c[i:] for i in range(n)])) ref = Counter(zip(*[r[i:] for i in range(n)])) clipped = sum(min(cnt, ref.get(ng, 0)) for ng, cnt in cand.items()) total = max(len(c) - n + 1, 0) precs.append((clipped + 1) / (total + 1)) # 加一平滑 geo = math.exp(sum(math.log(p) for p in precs) / 4) bp = 1.0 if len(c) >= len(r) else math.exp(1 - len(r) / len(c)) return bp * geo

ROUGE-L

ROUGE-L 比较候选与参考 token 序列的最长公共子序列。LCS 捕捉词序却不强制连续,这正是它成为默认摘要指标的原因。用标准动态规划表算 LCS 长度,再派生召回 = lcs / 参考长度、精度 = lcs / 候选长度,用 beta=1 的 F-beta 组合成对称 F1 形。

def lcs_length(a, b): n, m = len(a), len(b) dp = numpy.zeros((n + 1, m + 1), dtype=int) for i in range(n): for j in range(m): if a[i] == b[j]: dp[i+1, j+1] = dp[i, j] + 1 else: dp[i+1, j+1] = max(dp[i+1, j], dp[i, j+1]) return int(dp[n, m]) def rouge_l(pred, target): p, t = tokenize(pred), tokenize(target) if not p or not t: return 0.0 lcs = lcs_length(p, t) prec, rec = lcs / len(p), lcs / len(t) return 2 * prec * rec / (prec + rec)

numpy 表让实现清晰;纯 Python 列表也行。选 ROUGE-L 的任务付每任务 O(nm) 代价,典型摘要长度下保持亚毫秒。

Accuracy

多 target 分类任务的 accuracy 归约为对单一归一 target 的 exact-match。单独暴露函数,让分派器按 metric_name 分派而无需运行器内做字符串比较。

四、分派契约

唯一入口是 score(metric_name, prediction, targets),返回 [0,1] 的 float。运行器不对指标名分支,只转手并写结果。这是 75 节将粘到 70 节任务规格的接口。

def score(metric_name, pred, targets): if metric_name == "exact_match": return exact_match(pred, targets) if metric_name == "f1": return max(f1_score(pred, t) for t in targets) if metric_name == "bleu_4": return max(bleu4(pred, t) for t in targets) if metric_name == "rouge_l": return max(rouge_l(pred, t) for t in targets) if metric_name == "accuracy": return accuracy(pred, targets) raise ValueError(f"未知 metric_name: {metric_name}")

code_exec 在 72 节处理并在那里插入分派器。

五、框架对比

指标 度量什么 复杂度 来源论文
exact_match 去空白后任一 target 等值 O(1)
F1 token 多重集交的调和平均 O(n) SQuAD
accuracy 归一后单 target 等值 O(1)
BLEU-4 修正 n-gram 精度的几何平均 × BP O(n) Papineni 2002
ROUGE-L LCS 的 F-beta O(nm) Lin 2004

业界对比:sacreBLEU、rouge-score、HuggingFace evaluatenltk.translate.bleu_score 实现同一公式,差异主要在分词(是否区分大小写、是否保留标点、CJK 是否切字)与平滑方法。它们的共识与本节一致:分词器是契约,平滑规则要明示。sacreBLEU 的全部理由就是「可复现、不分词器歧义」——与本节「分词器是契约不是旋钮」同源。

六、本节不做的事

本节不调模型、不在 70 节后处理规则之外归一化生成、不算置信区间、不算 BLEURT 或 BERTScore(那些需要模型,属另一课)。要点是地基:五个指标、一个分词器、一张分派表。main.py 把每个指标定义成自由函数加分派器;参考向量在文件底部 _reference_examples 块;demo 对八个样例跑分派器打印每指标分;code/tests/test_metrics.py 钉死参考向量并压测每个边界(空预测、空参考、无共享 token、精确匹配、重复短语钳制)。

七、可复用产物

  • score(metric_name, pred, targets) 分派器:本节是 70 节任务规格的执行端,也是 75 节运行器调用的唯一指标入口。新增指标只需在此表加一行。
  • 统一分词器:五个指标共用,让跨指标可比。
  • 参考向量 + 边界测试:任何重写都有回归网。

八、练习

  1. 对比平滑:实现 Lin-Och「方法 2」(n=1..N-1 用几何平均,n=N 用 N-gram 精度替换),在短候选上对比「方法 1」。
  2. 加 GLEU(Google BLEU):取 min(精度, 召回)对所有 n,在固定集上对比 BLEU-4。
  3. CJK 分词:把分词器换成字符级,在中文摘要集上重算 ROUGE-L,报告数值变化。
  4. 加置信区间:用 bootstrap 对 ROUGE-L 算 95% CI,报告评估集大小对区间宽度的影响。
  5. 层叠模型指标:在固定集上对同一生成算 BERTScore 与 ROUGE-L,画散点,看两者是否一致。

本节要点回顾

  1. 五个经典指标仍是已发表数字的主力——从第一性原理实现,才知数字含义。
  2. 分词器是契约不是旋钮:re.findall(r"\w+", text.lower()),换分词器即换基准。
  3. exact-match/accuracy 各一行;F1 是 token 多重集交的调和平均(多 target 取最佳)。
  4. BLEU-4 = 修正 n-gram 精度的几何平均 × 长度惩罚,加一平滑避免 log 0。
  5. ROUGE-L = 最长公共子序列的 F-beta,LCS 捕捉词序不强制连续,O(nm) 动态规划。
  6. 单一分派入口 score(metric_name, pred, targets),运行器对指标名一无所知。
  7. 地基先稳:五指标一表一测试网,再层叠模型指标(BLEURT/BERTScore)。

下一节,我们将进入「代码执行指标」——把 code_exec 这个 metric_name 落地为「在沙箱里真跑代码、按通过测试打分」,并补进分派器。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U