赞
踩
BLEU(Bilingual Evaluation Understudy),即双语评估替补。所谓替补就是代替人类来评估机器翻译的每一个输出结果。Bleu score 所做的,给定一个机器生成的翻译,自动计算一个分数,衡量机器翻译的好坏。取值范围是[0, 1],越接近1,表明翻译质量越好。
机器翻译的一大难题是,一句法语句子,可以有多种英文翻译,这些翻译都是非常好的那怎么去评估一个机器翻译系统的质量是不是好呢?这不像图像识别,只有一个正确答案。通常我们有 BLEU score 来解决。
原论文为 BLEU: a Method for Automatic Evaluation of Machine Translation
话不多说,先上公式:
也可以写成:
(BLEU的原型系统采用的是均匀加权,即Wn=1/N 。N的上限取值为4,即最多只统计4-gram的精度。)
其中,BP是简短惩罚因子,惩罚一句话的长度过短,防止训练结果倾向短句的现象,其表达式为:
还有Pn,是基于n-gram的精确度,其表达公式为:
公式的解释及使用都在以下例子中体现。
Reference 1 和 Reference 2这两句都是很不错的人工进行的翻译,那么如何利用这两句话对其他机器翻译得出的结果进行评估呢?
一般衡量机器翻译输出质量的方法之一是观察输出结果的每一个词,看其是否出现在参考(也就是人工翻译结果)中,这被称为是机器翻译的精确度。而这样计算得出的精确度很快就能被一个反例打破,那就是:the the the the the the the.
首先 我们要做的是,看机器翻译 MT 的结果中,有多少词在人类的翻译参考中出现:
对于1中的计算方式的不足之处进行改良,于是把每一个单词的计分上限定为它在参考句子中出现最多的次数。在reference1中,the出现了两次,在reference2中,the只出现了一次。所以会说单词the的得分上限是2。
有了这个改良后的精确度,我们就说,这个输出句子的得分为2/7,因为在7个词中,我们最多只能给它2分。所以这里分母就是7个词中单词the总共出现的次数,而分子就是单词the出现的计数。我们在达到上限时截断计数,这就是改良后的精确度评估(modified precision measure)。
其次 在 Bleu score 中,我们不仅关注 单词,还关注词组(其实就是相邻的词对 bigrams)
可以将其公式化,这里的 P1 下标 1 表示的是 一元词组,推广到 n 元词组。
如果机器翻译的结果和参考翻译中的某一句完全相同,那么 P1 = P2 = … = Pn = 1
也有可能通过某种组合各个参考翻译得到结果为 1。
最终,我们把以上结合起来,获得最后的 Bleu score
即求所有 P 值的平均,然后取个 e 指数
然后在前面加上一个 BP 参数(brevity penalty 简短惩罚)
在NLP机器翻译任务中,我们如果想对一个语句进行翻译,然后对翻译结果进行评价打分,则需要使用到BLEU score。在使用时需要有候选语句和语句库进行对比,从而根据两者的差异来计算得分。
(同时简单介绍一下MindSpore,MindSpore,新一代AI开源计算框架。创新编程范式,AI科学家和工程师更易使用,便于开放式创新;该计算框架可满足终端、边缘计算、云全场景需求,能更好保护数据隐私;可开源,形成广阔应用生态。
2020年3月28日,华为在开发者大会2020上宣布,全场景AI计算框架MindSpore在码云正式开源。MindSpore着重提升易用性并降低AI开发者的开发门槛,MindSpore原生适应每个场景包括端、边缘和云,并能够在按需协同的基础上,通过实现AI算法即代码,使开发态变得更加友好,显著减少模型开发时间,降低模型开发门槛。
通过MindSpore自身的技术创新及MindSpore与华为昇腾AI处理器的协同优化,实现了运行态的高效,大大提高了计算性能;MindSpore也支持GPU、CPU等其它处理器。 )
- """BleuScore."""
- from collections import Counter
- import numpy as np
- from mindspore._checkparam import Validator as validator
- from .metric import Metric
-
-
- class BleuScore(Metric):
-
- def __init__(self, n_gram=4, smooth=False):
- super().__init__()
- # validator.check_value_type为参数的校验
- self.n_gram = validator.check_value_type("n_gram", n_gram, [int])
- if self.n_gram > 4 or self.n_gram < 1:
- raise ValueError('The n_gram value ranged from 1 to 4, but got {}'.format(n_gram))
-
- self.smooth = validator.check_value_type("smooth", smooth, [bool])
- self.clear()
-
- def clear(self):
- """清除历史数据"""
- self._numerator = np.zeros(self.n_gram)
- self._denominator = np.zeros(self.n_gram)
- self._precision_scores = np.zeros(self.n_gram)
- self._c = 0.0
- self._r = 0.0
- self._trans_len = 0
- self._ref_len = 0
- self._is_update = False
-
- # 用ngram计算每个单词在给定文本中出现的次数。
- def _count_ngram(self, ngram_input_list, n_gram):
- # 先构造一个计数器。
- ngram_counter = Counter()
-
- for i in range(1, n_gram + 1):
- # 遍历翻译文本或参考文本的列表。
- for j in range(len(ngram_input_list) - i + 1):
- # 构造ngram-key字典
- ngram_key = tuple(ngram_input_list[j:(i + j)])
- ngram_counter[ngram_key] += 1
-
- return ngram_counter
-
- def update(self, *inputs):
- # 先进行输入个数的判断
- if len(inputs) != 2:
- raise ValueError('The bleu_score need 2 inputs (candidate_corpus, reference_corpus), '
- 'but got {}'.format(len(inputs)))
- # 更新输入,一个为候选句子,一个为参考句子或参考列表
- candidate_corpus = inputs[0]
- reference_corpus = inputs[1]
- # 进行输入的校验
- if len(candidate_corpus) != len(reference_corpus):
- raise ValueError('translate_corpus and reference_corpus should be equal in length, '
- 'but got {} {}'.format(len(candidate_corpus), len(reference_corpus)))
- # 遍历两个输入的每一个单词,使用计数器进行统计
- for (candidate, references) in zip(candidate_corpus, reference_corpus):
- self._c += len(candidate)
- ref_len_list = [len(ref) for ref in references]
- ref_len_diff = [abs(len(candidate) - x) for x in ref_len_list]
- self._r += ref_len_list[ref_len_diff.index(min(ref_len_diff))]
- translation_counter = self._count_ngram(candidate, self.n_gram)
- reference_counter = Counter()
-
- for ref in references:
- reference_counter |= self._count_ngram(ref, self.n_gram)
-
- ngram_counter_clip = translation_counter & reference_counter
-
- for counter_clip in ngram_counter_clip:
- self._numerator[len(counter_clip) - 1] += ngram_counter_clip[counter_clip]
-
- for counter in translation_counter:
- self._denominator[len(counter) - 1] += translation_counter[counter]
-
- self._trans_len = np.array(self._c)
- self._ref_len = np.array(self._r)
- self._is_update = True
-
- def eval(self):
- # 如果_is_update是False,则说明使用方法错误。
- if self._is_update is False:
- raise RuntimeError('Call the update method before calling eval.')
-
- # 分母不能为0
- if min(self._numerator) == 0.0:
- return np.array(0.0)
-
- # 计算准确度
- if self.smooth:
- precision_scores = np.add(self._numerator, np.ones(self.n_gram)) / np.add(self._denominator,
- np.ones(self.n_gram))
- else:
- precision_scores = self._numerator / self._denominator
-
- # 使用公式进行计算BLEU
- log_precision_scores = np.array([1.0 / self.n_gram] * self.n_gram) * np.log(precision_scores)
- # 几何平均形式求平均值然后加权
- geometric_mean = np.exp(np.sum(log_precision_scores))
- brevity_penalty = np.array(1.0) if self._c > self._r else np.exp(1 - (self._ref_len / self._trans_len))
- bleu = brevity_penalty * geometric_mean
-
- return bleu
使用方法如下:
- import numpy as np
- import mindspore.common.dtype as mstype
- import mindspore.nn as nn
- from mindspore import Tensor
-
- candidate_corpus = [['i', 'have', 'a', 'pen', 'on', 'my', 'desk']]
- reference_corpus = [[['i', 'have', 'a', 'pen', 'in', 'my', 'desk'],
- ['there', 'is', 'a', 'pen', 'on', 'the', 'desk']]]
- metric = BleuScore()
- metric.clear()
- metric.update(candidate_corpus, reference_corpus)
- bleu_score = metric.eval()
- print(bleu_score)
-
- 0.5946035575013605
需要说明的问题:
BLEU采用的是clipping策略,即:在参考译文中被匹配过的单元(n-gram)应该被剪切掉,不应该再被匹配。BLEU在语料库层级上具有很好的匹配效果,但是随着n的增加,在句子层级上的匹配表现越来越差,因此BLEU在个别语句上可能表现不佳。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。