浅谈用Python计算文本BLEU分数

BLEU,全称为Bilingual Evaluation Understudy(双语评估替换),是一个比较候选文本翻译与其他一个或多个参考翻译的评价分数。

尽管BLEU一开始是为翻译工作而开发,但它也可以被用于评估文本的质量,这种文本是为一套自然语言处理任务而生成的。

通过本教程,你将探索BLEU评分,并使用Python中的NLTK库对候选文本进行评估和评分。

完成本教程后,你将收获:

  • BLEU评分的简单入门介绍,并直观地感受到到底是什么正在被计算。
  • 如何使用Python中的NLTK库来计算句子和文章的BLEU分数。
  • 如何用一系列的小例子来直观地感受候选文本和参考文本之间的差异是如何影响最终的BLEU分数。

让我们开始吧。

浅谈用Python计算文本BLEU分数

照片由Bernard Spragg. NZ提供,保留所有权。

教程概述

本教程分为4个部分; 他们分别是:

  1. 双语评估替换评分介绍
  2. 计算BLEU分数
  3. 累加和单独的BLEU分数
  4. 运行示例

双语评估替换评分

双语评估替换分数(简称BLEU)是一种对生成语句进行评估的指标。

完美匹配的得分为1.0,而完全不匹配则得分为0.0。

这种评分标准是为了评估自动机器翻译系统的预测结果而开发的。尽管它还没做到尽善尽美,但还是具备了5个引人注目的优点:

  • 计算速度快,计算成本低。
  • 容易理解。
  • 与具体语言无关。
  • 和人类给的评估高度相关。
  • 已被广泛采用。

BLEU评分是由Kishore Papineni等人在他们2002年的论文“ 《BLEU: a Method for Automatic Evaluation of Machine Translation》 ”中提出的。

这种评测方法通过对候选翻译与参考文本中的相匹配的n元组进行计数,其中一元组(称为1-gram或unigram)比较的是每一个单词,而二元组(bigram)比较的将是每个单词对。这种比较是不管单词顺序的。

BLEU编程实现的主要任务是对候选翻译和参考翻译的n元组进行比较,并计算相匹配的个数。匹配个数与单词的位置无关。匹配个数越多,表明候选翻译的质量就越好。

n元组匹配的计数结果会被修改,以确保将参考文本中的单词都考虑在内,而不会对产生大量合理词汇的候选翻译进行加分。在BLEU论文中这被称之为修正的n元组精度。

糟糕的是,机器翻译系统可能会生成过多的“合理”单词,从而导致翻译结果不恰当,尽管其精度高...从直观上这个问题是明显的:在识别出匹配的候选单词之后,相应的参考单词应该被视为用过了。我们将这种直觉定义为修正的单元组精度。

BLEU评分是用来比较语句的,但是又提出了一个能更好地对语句块进行评分的修订版本,这个修订版根据n元组出现的次数来使n元组评分正常化。

我们首先逐句计算n元组匹配数目。接下来,我们为所有候选句子加上修剪过的n元组计数,并除以测试语料库中的候选n元组个数,以计算整个测试语料库修正后的精度分数pn。

实际上,一个完美的分数是不可能存在的,因为这意味着翻译必须完全符合参考。甚至连人类翻译家都不能做到这点。对计算BLEU分数的参考文本的数量和质量的水平要求意味着在不同数据集之间的比较BLEU分数可能会很麻烦。

BLEU评分的范围是从0到1。很少有翻译得分为1,除非它们与参考翻译完全相同。因此,即使是一个人类翻译,也不一定会在一个大约500个句子(也就是40个普通新闻报道的长度)的测试语料上得1分,一个人类翻译在四个参考翻译下的得分为0.3468,在两个参考翻译下的得分为0.2571。

除了翻译之外,我们还可以将BLEU评分用于其他的语言生成问题,通过使用深度学习方法,例如:

  • 语言生成。
  • 图片标题生成。
  • 文本摘要。
  • 语音识别。

以及更多。

计算BLEU分数

Python自然语言工具包库(NLTK)提供了BLEU评分的实现,你可以使用它来评估生成的文本,通过与参考文本对比。

语句BLEU分数

NLTK提供了sentence_bleu()函数,用于根据一个或多个参考语句来评估候选语句。

参考语句必须作为语句列表来提供,其中每个语句是一个记号列表。候选语句作为一个记号列表被提供。例如:

from nltk.translate.bleu_score import sentence_bleu
reference = [['this', 'is', 'a', 'test'], ['this', 'is' 'test']]
candidate = ['this', 'is', 'a', 'test']
score = sentence_bleu(reference, candidate)
print(score)

运行这个例子,会输出一个满分,因为候选语句完全匹配其中一个参考语句

1.0

语料库BLEU分数

NLTK还提供了一个称为corpus_bleu()的函数来计算多个句子(如段落或文档)的BLEU分数。

参考文本必须被指定为文档列表,其中每个文档是一个参考语句列表,并且每个可替换的参考语句也是记号列表,也就是说文档列表是记号列表的列表的列表。候选文档必须被指定为列表,其中每个文件是一个记号列表,也就是说候选文档是记号列表的列表。

这听起来有点令人困惑; 以下是一个文档的两个参考文档的例子。

# two references for one document
from nltk.translate.bleu_score import corpus_bleu
references = [[['this', 'is', 'a', 'test'], ['this', 'is' 'test']]]
candidates = [['this', 'is', 'a', 'test']]
score = corpus_bleu(references, candidates)
print(score)

运行这个例子就像之前一样输出满分

1.0

累加和单独的BLEU分数

NLTK中提供的BLEU评分方法允许你在计算BLEU分数时为不同的n元组指定权重。

这使你可以灵活地计算不同类型的BLEU分数,如单独和累加的n-gram分数。

让我们来看一下。

单独的N-Gram分数

单独的N-gram分数是对特定顺序的匹配n元组的评分,例如单个单词(称为1-gram)或单词对(称为2-gram或bigram)。

权重被指定为一个数组,其中每个索引对应相应次序的n元组。仅要计算1-gram匹配的BLEU分数,你可以指定1-gram权重为1,对于2元,3元和4元指定权重为0,也就是权重为(1,0,0,0)。例如:

# 1-gram individual BLEU
from nltk.translate.bleu_score import sentence_bleu
reference = [['this', 'is', 'small', 'test']]
candidate = ['this', 'is', 'a', 'test']
score = sentence_bleu(reference, candidate, weights=(1, 0, 0, 0))
print(score)

运行此例将输出得分为0.5。

0.75

我们可以重复这个例子,对于从1元到4元的各个n-gram运行语句如下所示:

# n-gram individual BLEU
from nltk.translate.bleu_score import sentence_bleu
reference = [['this', 'is', 'a', 'test']]
candidate = ['this', 'is', 'a', 'test']
print('Individual 1-gram: %f' % sentence_bleu(reference, candidate, weights=(1, 0, 0, 0)))
print('Individual 2-gram: %f' % sentence_bleu(reference, candidate, weights=(0, 1, 0, 0)))
print('Individual 3-gram: %f' % sentence_bleu(reference, candidate, weights=(0, 0, 1, 0)))
print('Individual 4-gram: %f' % sentence_bleu(reference, candidate, weights=(0, 0, 0, 1))

运行该示例,结果如下所示:

Individual 1-gram: 1.000000
Individual 2-gram: 1.000000
Individual 3-gram: 1.000000
Individual 4-gram: 1.000000

虽然我们可以计算出单独的BLEU分数,但这并不是使用这个方法的初衷,而且得出的分数也没有过多的含义,或者看起来具有说明性。

累加的N-Gram分数

累加分数是指对从1到n的所有单独n-gram分数的计算,通过计算加权几何平均值来对它们进行加权计算。

默认情况下,sentence_bleu()corpus_bleu()分数计算累加的4元组BLEU分数,也称为BLEU-4分数。

BLEU-4对1元组,2元组,3元组和4元组分数的权重为1/4(25%)或0.25。例如:

# 4-gram cumulative BLEU
from nltk.translate.bleu_score import sentence_bleu
reference = [['this', 'is', 'small', 'test']]
candidate = ['this', 'is', 'a', 'test']
score = sentence_bleu(reference, candidate, weights=(0.25, 0.25, 0.25, 0.25))
print(score) 

运行这个例子,输出下面的分数:

0.707106781187

累加的和单独的1元组BLEU使用相同的权重,也就是(1,0,0,0)。计算累加的2元组BLEU分数为1元组和2元组分别赋50%的权重,计算累加的3元组BLEU为1元组,2元组和3元组分别为赋33%的权重。

让我们通过计算BLEU-1,BLEU-2,BLEU-3和BLEU-4的累加得分来具体说明:

# cumulative BLEU scores
from nltk.translate.bleu_score import sentence_bleu
reference = [['this', 'is', 'small', 'test']]
candidate = ['this', 'is', 'a', 'test']
print('Cumulative 1-gram: %f' % sentence_bleu(reference, candidate, weights=(1, 0, 0, 0)))
print('Cumulative 2-gram: %f' % sentence_bleu(reference, candidate, weights=(0.5, 0.5, 0, 0)))
print('Cumulative 3-gram: %f' % sentence_bleu(reference, candidate, weights=(0.33, 0.33, 0.33, 0)))
print('Cumulative 4-gram: %f' % sentence_bleu(reference, candidate, weights=(0.25, 0.25, 0.25, 0.25)))

运行该示例输出下面的分数。结果的差别很大,比单独的n-gram分数更具有表达性。

Cumulative 1-gram: 0.750000
Cumulative 2-gram: 0.500000
Cumulative 3-gram: 0.632878
Cumulative 4-gram: 0.707107

在描述文本生成系统的性能时,通常会报告从BLEU-1到BLEU-4的累加分数。

运行示例

在这一节中,我们试图通过一些例子来进一步获取对BLEU评分的直觉。

我们在语句层次上通过用下面的一条参考句子来说明:

the quick brown fox jumped over the lazy dog

首先,我们来看一个完美的分数。

# prefect match
from nltk.translate.bleu_score import sentence_bleu
reference = [['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']]
candidate = ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']
score = sentence_bleu(reference, candidate)
print(score)

运行例子输出一个完美匹配的分数。

1.0

接下来,让我们改变一个词,把“ quick ”改成“ fast ”。

# one word different
from nltk.translate.bleu_score import sentence_bleu
reference = [['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']]
candidate = ['the', 'fast', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']
score = sentence_bleu(reference, candidate)
print(score)

结果是分数略有下降。

 0.7506238537503395

尝试改变两个词,把“ quick ”改成“ fast ”,把“ lazy ”改成“ sleepy ”。

# two words different
from nltk.translate.bleu_score import sentence_bleu
reference = [['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']]
candidate = ['the', 'fast', 'brown', 'fox', 'jumped', 'over', 'the', 'sleepy', 'dog']
score = sentence_bleu(reference, candidate)
print(score)

运行这个例子,我们可以看到得分线性下降。

0.4854917717073234

如果候选语句的所有单词与参考语句的都不一样呢?

# all words different
from nltk.translate.bleu_score import sentence_bleu
reference = [['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']]
candidate = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
score = sentence_bleu(reference, candidate)
print(score)

我们得到了一个更糟糕的分数。

0.0

现在,让我们尝试一个比参考语句的词汇更少(例如,放弃最后两个词)的候选语句,但这些单词都是正确的。

# shorter candidate
from nltk.translate.bleu_score import sentence_bleu
reference = [['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']]
candidate = ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the']
score = sentence_bleu(reference, candidate)
print(score)

结果和之前的有两个单词错误的情况很相似。

0.7514772930752859

如果我们把候选语句调整为比参考语句多两个单词,那又会怎么样?

# longer candidate
from nltk.translate.bleu_score import sentence_bleu
reference = [['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']]
candidate = ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog', 'from', 'space']
score = sentence_bleu(reference, candidate)
print(score)

再一次,我们可以看到,我们的直觉是成立的,得分还是有点像“ 有两个错字 ”的情况。

  0.7860753021519787 

最后,我们来比较一个很短的候选语句:只有两个单词的长度。

# very short
from nltk.translate.bleu_score import sentence_bleu
reference = [['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog']]
candidate = ['the', 'quick']
score = sentence_bleu(reference, candidate)
print(score)

运行此示例首先会打印一条警告消息,指出不能执行评估3元组及以上部分(直到4元组)。这是合乎情理的,因为在候选语句中我们最多只能用2元组来运行。

UserWarning:
Corpus/Sentence contains 0 counts of 3-gram overlaps.
BLEU scores might be undesirable; use SmoothingFunction().
  warnings.warn(_msg)

接下来,我们会得到一个非常低的分数。

0.0301973834223185

你可以继续用这些例子来进行其他试验。

BLEU包含的数学知识非常简单,我也鼓励你阅读这篇论文,并在自己电子表格程序中探索计算语句评估分数的方法。

进一步阅读

如果你要深入研究,本节将提供更多有关该主题的资源。

总结

在本教程中,你探索了BLEU评分,根据在机器翻译和其他语言生成任务中的参考文本对候选文本进行评估和评分。

具体来说,你学到了:

  • BLEU评分的简单入门介绍,并直观地感受到到底是什么正在被计算。
  • 如何使用Python中的NLTK库来计算语句和文章的BLEU分数。
  • 如何使用一系列的小例子来直观地感受候选文本和参考文本的差异是如何影响最终的BLEU分数。

本文的版权归 小蝌蚪 所有,如需转载请联系作者。

发表于

我来说两句

2 条评论
登录 后参与评论

相关文章

来自专栏编程

隐马尔科夫模型 python 实现简单拼音输入法

关键时刻,第一时间送达! ? 在网上看到一篇关于隐马尔科夫模型的介绍,觉得简直不能再神奇,又在网上找到大神的一篇关于如何用隐马尔可夫模型实现中文拼音输入的博客(...

2820
来自专栏崔庆才的专栏

自然语言处理中句子相似度计算的几种方法

在做自然语言处理的过程中,我们经常会遇到需要找出相似语句的场景,或者找出句子的近似表达,这时候我们就需要把类似的句子归到一起,这里面就涉及到句子相似度计算的问题...

1.5K4
来自专栏文武兼修ing——机器学习与IC设计

基于sklearn的K邻近分类器概念代码实现

概念 KNN(K临近)分类器应该算是概率派的机器学习算法中比较简单的。基本的思想为在预测时,计算输入向量到每个训练样本的欧氏距离(几何距离),选取最近的K个训练...

3106
来自专栏来自地球男人的部落格

浅谈Attention-based Model【源码篇】

源码不可能每一条都详尽解释,主要在一些关键步骤上加了一些注释和少许个人理解,如有不足之处,请予指正。 计划分为三个部分: 浅谈Attention-bas...

24310
来自专栏ml

调参过程中的参数 学习率,权重衰减,冲量(learning_rate , weight_decay , momentum)

无论是深度学习还是机器学习,大多情况下训练中都会遇到这几个参数,今天依据我自己的理解具体的总结一下,可能会存在错误,还请指正. learning_rate , ...

5058
来自专栏崔庆才的专栏

自然语言处理中句子相似度计算的几种方法

在做自然语言处理的过程中,我们经常会遇到需要找出相似语句的场景,或者找出句子的近似表达,这时候我们就需要把类似的句子归到一起,这里面就涉及到句子相似度计算的问题...

2043
来自专栏深度学习与计算机视觉

特征提取方法(二):LBP原理与OpenCV实现

LBP简介 LBP(Local Binary Pattern)算法是一种描述图像特征像素点与各个像素点之间的灰度关系的局部特征的非参数算法,同时也是一张高效的纹...

4228
来自专栏Fish

TensorFlow编程入门(一)

写在最前 深度学习辣么火,感觉应该学习学习以免以后人家讲座什么的听不懂。因此想要从应用层面出发,学习学习,那就看看怎么用tensorflow(以下简称tf)做神...

1756
来自专栏QQ大数据团队的专栏

海量短文本场景下的去重算法

在大多数情况下,大量的重复文本一般不会是什么好事情,比如互相抄袭的新闻,群发的垃圾短信,铺天盖地的广告文案等,这些都会造成网络内容的同质化并加重数据库的存储负担...

10.7K2
来自专栏文武兼修ing——机器学习与IC设计

基于sklearn的线性回归器理论代码实现

理论 线性回归器 相比于线性分类器,线性回归器更加自然。回归任务的label是连续的变量(不像分类任务label是离散变量),线性回归器就是直接通过权值与输入对...

3377

扫码关注云+社区