DeepSeek BLEU和ROUGE(Recall)的计算
以下是 BLEU Score (Bilingual Evaluation Understudy)和 ROUGE Score(Recall-Oriented Understudy for Gisting Evaluation) 的原生Python实现(不依赖第三方库),通过分步计算逻辑和示例详细说明。
一、BLEU Score 实现
核心逻辑
- n-gram匹配:计算候选文本和参考文本的n-gram匹配数(截断到参考文本中最大出现次数)。
- 精确度计算:各n-gram的匹配数除以候选文本n-gram总数。
- 几何平均:对1-gram到4-gram的精确度取几何平均。
- 长度惩罚:候选文本过短时施加惩罚。
代码实现
import math
def get_ngrams(text, n):
"""生成n-gram列表"""
return [tuple(text[i:i+n]) for i in range(len(text)-n+1)]
def bleu_score(candidate, reference, max_n=4):
"""计算BLEU分数(单参考文本简化版)"""
candidate = candidate.split()
reference = reference.split()
# 长度惩罚
c_len = len(candidate)
r_len = len(reference)
bp = 1.0 if c_len > r_len else math.exp(1 - r_len / c_len) if c_len !=0 else 0.0
# 计算各n-gram的精确度
precisions = []
for n in range(1, max_n+1):
# 生成候选和参考的n-gram
cand_ngrams = get_ngrams(candidate, n)
ref_ngrams = get_ngrams(reference, n)
# 统计匹配数(截断到参考中的最大出现次数)
match_count = 0
ref_counts = {}
for gram in ref_ngrams:
ref_counts[gram] = ref_counts.get(gram, 0) + 1
for gram in cand_ngrams:
if ref_counts.get(gram, 0) > 0:
match_count += 1
ref_counts[gram] -= 1 # 避免重复匹配
# 计算精确度
if len(cand_ngrams) == 0:
p_n = 0.0
else:
p_n = match_count / len(cand_ngrams)
precisions.append(p_n)
# 几何平均(避免零值)
geom_mean = math.exp(sum(math.log(p + 1e-10) for p in precisions) / max_n)
bleu = bp * geom_mean
return bleu
示例
reference = "the cat is on the mat"
candidate1 = "the the the the the" # 低质量候选
candidate2 = "the cat is on the mat" # 完美匹配
print(bleu_score(candidate1, reference)) # 输出接近0.0
print(bleu_score(candidate2, reference)) # 输出1.0(完美匹配)
二、ROUGE Score 实现(以ROUGE-N和ROUGE-L为例)
1. ROUGE-N(基于n-gram的召回率)
def rouge_n(candidate, reference, n=1):
"""计算ROUGE-N分数(召回率)"""
cand_ngrams = get_ngrams(candidate.split(), n)
ref_ngrams = get_ngrams(reference.split(), n)
# 统计匹配数
ref_gram_counts = {}
for gram in ref_ngrams:
ref_gram_counts[gram] = ref_gram_counts.get(gram, 0) + 1
match_count = 0
for gram in cand_ngrams:
if ref_gram_counts.get(gram, 0) > 0:
match_count += 1
ref_gram_counts[gram] -= 1 # 避免重复匹配
# 召回率 = 匹配数 / 参考文本中的n-gram总数
rouge = match_count / len(ref_ngrams) if len(ref_ngrams) > 0 else 0.0
return rouge
2. ROUGE-L(基于最长公共子序列,LCS)
def lcs_length(x, y):
"""计算最长公共子序列长度"""
m, n = len(x), len(y)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if x[i-1] == y[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
def rouge_l(candidate, reference):
"""计算ROUGE-L分数(F1值)"""
cand_tokens = candidate.split()
ref_tokens = reference.split()
lcs = lcs_length(cand_tokens, ref_tokens)
# 精确率 = LCS长度 / 候选文本长度
precision = lcs / len(cand_tokens) if len(cand_tokens) > 0 else 0.0
# 召回率 = LCS长度 / 参考文本长度
recall = lcs / len(ref_tokens) if len(ref_tokens) > 0 else 0.0
# F1值
f1 = 2 * precision * recall / (precision + recall + 1e-10)
return f1
示例
reference = "the cat is on the mat"
candidate = "the cat sits" # 部分匹配
# ROUGE-1
print(rouge_n(candidate, reference, n=1)) # 输出 2/6 ≈ 0.333
# ROUGE-L
print(rouge_l(candidate, reference)) # LCS长度=2("the cat")
# 精确率=2/3≈0.666,召回率=2/6≈0.333 → F1≈0.444
三、对比说明
指标 | 核心公式 | 示例输出(候选文本:the cat sits ) |
---|---|---|
BLEU | 几何平均+长度惩罚 | 接近0.0(因4-gram不匹配) |
ROUGE-1 | 匹配1-gram数 / 参考1-gram数 | 2/6 ≈ 0.333 |
ROUGE-L | LCS的F1值 | 0.444(精确率0.666,召回率0.333) |
四、关键点总结
- BLEU:强调生成文本的精确度和长度匹配。
- ROUGE-N:关注参考文本的内容覆盖度(召回率)。
- ROUGE-L:通过最长公共子序列衡量语义连贯性。
通过原生Python实现,可以更深入理解这些指标的计算逻辑,适用于定制化需求或教学场景。