NLP——BLEU指标和ROUGE指标


BLEU 和 ROUGE 指标整体说明

  • 在 NLP 领域,BLEU (Bilingual Evaluation Understudy)ROUGE (Recall-Oriented Understudy for Gisting Evaluation) 指标广泛应用于机器翻译文本摘要任务
  • 核心思路:BLEUROUGE 通过比较模型生成的文本与人工参考文本之间的相似性衡量文本质量
  • 这两个指标通常没有严格的、官方的中文名,在中文学术界和工业界,大家普遍直接使用它们的英文缩写
    • BLEU :通常直接称为 BLEU 值BLEU 分数
      • 注:BLEU 全称 Bilingual Evaluation Understudy 可以直译为“双语评估替补”(Understudy 常翻译为“替补”含义),但这种直译并不常用
    • ROUGE :通常直接称为 ROUGE 值ROUGE 分数
      • 注:ROUGE 全称 Recall-Oriented Understudy for Gisting Evaluation 可以直译为“面向召回的摘要评估替补”(Gisting 翻译为“摘要”),这种直译也很少被使用
  • BLEU精确率(Precision)导向,常用于机器翻译 ,关注生成文本的忠实度
  • ROUGE召回率(Recall)导向,常用于文本摘要 ,关注生成文本对参考文本信息的覆盖度
  • 两者都是衡量文本相似性的重要指标,但它们的侧重点和适用场景有所不同。在实际应用中,通常会结合使用多种评估指标来全面评估模型性能

BLEU 指标

  • 音标:/bluː/(类似英文单词 “blue” 的发音)
  • BLEU 主要用于评估机器翻译的质量,侧重于精确率 (Precision)
  • 核心思想:机器翻译的文本与人工翻译的参考文本越相似 ,其质量越高
  • 计算方式: BLEU 的计算涉及以下几个步骤:
  • 第一步:N-gram 精度 (N-gram Precision)
    • 首先,将候选翻译和参考翻译都进行分词
    • 计算不同长度的 N-gram(例如,unigram (1-gram), bigram (2-gram), trigram (3-gram), 甚至 higher N-grams,通常到 4-gram)
    • 对于每个 N-gram 长度 \(n\),计算修改后的 N-gram 精度 \(p_n\)
      • 注:修改后的精度是为了避免候选翻译中重复词语过多而导致分数虚高,比如全都翻译为 \(the\) 这种常见词语,可能导致 N-gram 精度虚假的被为“100%”
      • 修改动作:通过计算候选翻译中与参考翻译匹配的 N-gram 数量,并将其限制(裁剪)为该 N-gram 在任何一个参考翻译(可能有多个参考翻译,命中任意翻译都算正确)中出现的最大次数
    • 公式为:
      $$p_n = \frac{\sum_{C \in \text{Candidates}} \sum_{\text{n-gram} \in C} \text{Count}_{\text{clip}}(\text{n-gram})}{\sum_{C’ \in \text{Candidates}} \sum_{\text{n-gram}’ \in C’} \text{Count}(\text{n-gram}’)}$$
      • \(\text{Count}_{\text{clip}}(\text{n-gram})\) 是 N-gram 在候选翻译中出现并被限制为在参考翻译中最大出现次数的计数
      • \(\text{Count}(\text{n-gram}’)\) 是 N-gram 在候选翻译中出现的总计数
      • 理解:相当于统计候选翻译的所有 N-gram 数量,看命中参考翻译 N-gram 精确度是多少(在参考翻译中存在则视为准确,否则不准确)
  • 第二步:短句惩罚 (Brevity Penalty, BP)
    • N-gram 精度 倾向于奖励更短的翻译,因为它的分母更小(比如只翻译一个 “the”,N-gram 精度为100%)
    • 为了惩罚那些过短的翻译(即使它们完美匹配了部分 N-gram),引入了短句惩罚
    • 如果候选翻译的总长度 \(c\) 小于参考翻译中最接近的参考长度 \(r\),则应用惩罚
    • 公式为:
      $$BP = \begin{cases}
      1 & \text{if } c > r \\
      e^{(1 - r/c)} & \text{if } c \le r
      \end{cases}$$
      • 长度比参考文本短越多,惩罚越大
  • 第三步:最终 BLEU 分数
    • 将不同 N-gram 长度的修改精度取对数,然后加权平均,再乘以短句惩罚
    • 公式为:
      $$\text{BLEU} = BP \cdot \exp\left(\sum_{n=1}^{N} w_n \log p_n\right)$$
      • 其中,\(n\) 是最大 N-gram 长度(通常为 4),\(w_n\) 是每个 N-gram 长度的权重(通常均匀分配)
      • 实践经验:在实际使用时,一些特定的例子中可能出现高阶匹配不上而导致 BLEU 分数为 0(比如 4-gram 匹配数量为0,这时候看起来取对数的精度会几乎为负无穷),此时可以 NLTK 库中通过 smoothing_function 传入一些平滑策略来解决问题
    • 通常,每个 N-gram 长度的权重是 \(\frac{1}{N}\)(例如,对于 1-gram 到 4-gram,每个权重为 0.25)
      • 该值在 NLTK 库的 sentence_bleu 中是可以通过参数修改的

代码实现 (使用 NLTK 库):

  • 基于 NLTK 库实现 BLUE 值统计
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    from nltk.translate.bleu_score import sentence_bleu # 评估句子
    # from nltk.translate.bleu_score import corpus_bleu # 对于 corpus_bleu,可以评估整个语料库
    from nltk.tokenize import word_tokenize
    import nltk

    nltk.download('punkt_tab') # 若本地不存在该库,这里会从远程下载,'punkt_tab' 数据包包含了 Punkt 分词器(Tokenizer)所需的预训练模型数据

    def calculate_bleu(reference, candidate):
    """
    计算单个候选句子的BLEU分数
    :param reference: 一个参考句子列表,每个参考句子是一个词语列表
    例如:[['this', 'is', 'a', 'test'], ['this', 'is', 'test']]
    :param candidate: 一个候选句子,是一个词语列表
    例如:['this', 'is', 'a', 'test']
    :return: BLEU分数
    """
    # NLTK 的 sentence_bleu 函数期望参考是列表的列表,候选是列表
    # 如果只有一个参考,也要将其包装在列表中:[reference_sentence]
    # 如果有多个参考,则直接传入参考列表

    # 示例:
    # reference = [['The', 'cat', 'sat', 'on', 'the', 'mat'], [...], ...]
    # candidate = ['The', 'cat', 'was', 'on', 'the', 'mat']
    # score = sentence_bleu(reference, candidate)

    # 实际使用时,通常对文本进行分词
    tokenized_reference = [word_tokenize(ref) for ref in reference]
    tokenized_candidate = word_tokenize(candidate) # 分词结果:['The', 'cat', 'was', 'on', 'the', 'mat', '.']

    # NLTK 提供了 weights 参数来控制不同 N-gram 的权重,默认是 (0.25, 0.25, 0.25, 0.25)
    score = sentence_bleu(tokenized_reference, tokenized_candidate)

    # weights = [1,0,0,0] # 为不同 n-gram 设置不同的权重
    # score = sentence_bleu(tokenized_reference, tokenized_candidate, weights=weights)
    return score

    # 示例
    references = [
    "The cat is on the mat.",
    "There is a cat on the mat.",
    "A cat sat on the mat."
    ]
    candidate = "The cat was on the mat."

    bleu_score = calculate_bleu(references, candidate) # 实现:先分词,再评估 n-gram
    print(f"BLEU Score: {bleu_score}")

    # BLEU Score: 0.488923022434901

BLEU 使用说明

* BLEU 倾向于精确率,对于短的(已经有惩罚了)、精确匹配的句子可能给出高分,但可能忽略了语义的完整性或流畅性
* 更多的参考翻译通常会提高 BLEU 分数
* BLEU 在单句评估上指标不太稳定(修改单个单词可能出现非常大的变化),更适合评估整个语料库的平均表现

ROUGE 指标

  • 音标:/ruːʒ/(类似法语单词 “rouge”)
  • ROUGE 主要用于评估文本摘要的质量,侧重于召回率 (Recall)
  • 核心思想:模型生成的摘要包含了多少人工参考摘要中的重要信息
  • ROUGE 有多种变体,最常用的是:
    • ROUGE-N :基于 N-gram 的重叠
      • ROUGE-1 :Unigram(单个词)的召回率
      • ROUGE-2 :Bigram(两个词序列)的召回率
    • ROUGE-L :基于最长公共子序列 (Longest Common Subsequence, LCS) 的召回率
      • 它不要求 N-gram 必须连续,但要求保持相对顺序,更能捕捉句子的结构相似性
    • ROUGE-SU :基于跳跃二元组 (Skip-bigram) 和 unigram 的重叠,允许 N-gram 中间跳过词语
  • 计算方式:ROUGE-N 为例(其他变体类似,但匹配方式不同):
  • 召回率 (Recall)
    $$R_{\text{N}} = \frac{\sum_{\text{n-gram} \in \text{Ref}} \text{Count}_{\text{match}}(\text{n-gram})}{\sum_{\text{n-gram} \in \text{Ref}} \text{Count}(\text{n-gram})}$$
    • \(\text{Count}_{\text{match}}(\text{n-gram})\) 是在候选摘要和参考摘要中都出现的 N-gram 数量
    • \(\text{Count}(\text{n-gram})\) 是参考摘要中 N-gram 的总数量
  • 精确率 (Precision)
    $$P_{\text{N}} = \frac{\sum_{\text{n-gram} \in \text{Cand}} \text{Count}_{\text{match}}(\text{n-gram})}{\sum_{\text{n-gram} \in \text{Cand}} \text{Count}(\text{n-gram})}$$
    • \(\text{Count}_{\text{match}}(\text{n-gram})\) 同上
    • \(\text{Count}(\text{n-gram})\) 是候选摘要中 N-gram 的总数量
  • F1 分数 (F1-score) :通常使用 F1 分数来综合召回率和精确率
    $$F_1 = \frac{(1 + \beta^2) \cdot P \cdot R}{\beta^2 \cdot P + R}$$
    • \(\beta\) 通常取 1,表示精确率和召回率同等重要,此时 \(F_1 = \frac{2 \cdot P \cdot R}{P + R}\)
  • ROUGE-L (LCS-based): ROUGE-L 的计算基于最长公共子序列 (LCS) 的长度
    • \(LCS(X, Y)\) 表示序列 \(X\) 和 \(Y\) 的最长公共子序列的长度
    • 召回率:
      $$R_{LCS} = \frac{LCS(\text{candidate}, \text{reference})}{\text{length}(\text{reference})}$$
    • 精确率:
      $$P_{LCS} = \frac{LCS(\text{candidate}, \text{reference})}{\text{length}(\text{candidate})}$$
    • F1 分数:
      $$F_{LCS} = \frac{2 \cdot P_{LCS} \cdot R_{LCS}}{P_{LCS} + R_{LCS}}$$

代码实现 (使用 rouge-score 库)

  • rouge-score 是一个常用的 Python 库,用于计算 ROUGE 分数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    from rouge_score import rouge_scorer

    def calculate_rouge(reference, candidate):
    """
    计算 ROUGE-1, ROUGE-2, ROUGE-L 分数
    :param reference: 参考摘要字符串
    :param candidate: 候选摘要字符串
    :return: 包含 ROUGE 分数的字典
    """
    # scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL', 'rougeLsum'], use_stemmer=True)
    # 通常使用 rouge1, rouge2, rougeL
    scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=False)
    # use_stemmer=True 可以对词语进行词干化,可能会影响结果,取决于具体需求,例如 "running" 和 "ran" 可能会被视为匹配

    scores = scorer.score(reference, candidate)

    # scores 返回的是一个字典,例如:
    # {'rouge1': Score(precision=..., recall=..., fmeasure=...),
    # 'rouge2': Score(precision=..., recall=..., fmeasure=...),
    # 'rougeL': Score(precision=..., recall=..., fmeasure=...)}

    # 我们可以提取 F-measure 分数
    result = {}
    for key, score in scores.items():
    result[key] = {
    'precision': score.precision,
    'recall': score.recall,
    'fmeasure': score.fmeasure
    }
    return result

    # 示例
    reference_summary = "The quick brown fox jumps over the lazy dog."
    candidate_summary = "The quick brown fox jumps on the log."

    rouge_scores = calculate_rouge(reference_summary, candidate_summary)

    for metric, scores in rouge_scores.items():
    print(f"{metric.upper()}:")
    print(f" Precision: {scores['precision']:.4f}")
    print(f" Recall: {scores['recall']:.4f}")
    print(f" F-measure: {scores['fmeasure']:.4f}")

    # ROUGE1:
    # Precision: 0.7500
    # Recall: 0.6667
    # F-measure: 0.7059
    # ROUGE2:
    # Precision: 0.5714
    # Recall: 0.5000
    # F-measure: 0.5333
    # ROUGEL:
    # Precision: 0.7500
    # Recall: 0.6667
    # F-measure: 0.7059

ROUGE 使用说明

  • ROUGE 更侧重召回率,因此对于生成式摘要任务更为适用,因为摘要通常比原文短,我们更关心模型是否捕捉到了原文的关键信息
  • ROUGE-L 能够更好地处理词序变化 ,因为它基于最长公共子序列
  • ROUGE-N(特别是 ROUGE-1 和 ROUGE-2)是最常用的 ROUGE 变体

附录:NLTK 库中 BLEU 平滑策略

  • method0(): 无平滑 (No smoothing)
  • method1(): 添加epsilon计数 (Add epsilon counts)
  • method2(): 添加1到分子和分母 (Add 1 to both numerator and denominator)
  • method3(): NIST 几何序列平滑 (NIST geometric sequence smoothing)