Я обнаружил нулевое значение при подсчете баллов BLEU для китайских предложений.

Предложение-кандидат c и две ссылки: r1 и r2

c=[u'\u9274\u4e8e', u'\u7f8e\u56fd', u'\u96c6', u'\u7ecf\u6d4e', u'\u4e0e', u'\u8d38\u6613', u'\u6700\u5927', u'\u56fd\u4e8e', u'\u4e00\u8eab', u'\uff0c', u'\u4e0a\u8ff0', u'\u56e0\u7d20', u'\u76f4\u63a5', u'\u5f71\u54cd', u'\u7740', u'\u4e16\u754c', u'\u8d38\u6613', u'\u3002']

r1 = [u'\u8fd9\u4e9b', u'\u76f4\u63a5', u'\u5f71\u54cd', u'\u5168\u7403', u'\u8d38\u6613', u'\u548c', u'\u7f8e\u56fd', u'\u662f', u'\u4e16\u754c', u'\u4e0a', u'\u6700\u5927', u'\u7684', u'\u5355\u4e00', u'\u7684', u'\u7ecf\u6d4e', u'\u548c', u'\u8d38\u6613\u5546', u'\u3002']

r2=[u'\u8fd9\u4e9b', u'\u76f4\u63a5', u'\u5f71\u54cd', u'\u5168\u7403', u'\u8d38\u6613', u'\uff0c', u'\u56e0\u4e3a', u'\u7f8e\u56fd', u'\u662f', u'\u4e16\u754c', u'\u4e0a', u'\u6700\u5927', u'\u7684', u'\u5355\u4e00', u'\u7684', u'\u7ecf\u6d4e\u4f53', u'\u548c', u'\u8d38\u6613\u5546', u'\u3002']

Код: :

weights = [0.1, 0.8, 0.05, 0.05]
print nltk.align.bleu_score.bleu(c, [r1, r2], weights)

А вот получил результат 0. Когда я вхожу в процесс bleu, я обнаружил, что

try:
    s = math.fsum(w * math.log(p_n) for w, p_n in zip(weights, p_ns))
except ValueError:
    # some p_ns is 0
    return 0

Вышеупомянутая программа переходит к , кроме ValueError. Однако я не знаю, почему это возвращает ошибку. Если я попробую другие предложения, я могу получить ненулевое значение.

Ответы (1)

Похоже, вы поймали ошибку в реализациях NLTK! Это try-except неверно на https://github.com/alvations/nltk/blob/develop/nltk/translate/bleu_score.py#L76


В длинных:

Во-первых, давайте рассмотрим, что означает p_n в счете BLEU:

enter image description here

Обратите внимание, что:

  • формула Папинени основана на оценке BLEU на уровне корпуса, а собственная реализация использует оценку BLEU на уровне предложения (новейшая версия NLTK содержит реализацию, которая соответствует статье Папинени для расчета BLEU на уровне корпуса) *. 100003 *
  • в BLEU с несколькими ссылками, Count_match (ngram) основывается на ссылке с более высоким счетчиком (см. https://github.com/alvations/nltk/blob/develop/nltk/translate/bleu_score.py#L270).

Таким образом, оценка BLEU по умолчанию использует n = 4, что включает униграммы до 4 граммов. Для каждой диаграммы давайте вычислим p_n:

>>> from collections import Counter
>>> from nltk import ngrams
>>> hyp = u"鉴于 美国 集 经济 与 贸易 最大 国于 一身 , 上述 因素 直接 影响 着 世界 贸易 。".split()
>>> ref1 = u"这些 直接 影响 全球 贸易 和 美国 是 世界 上 最大 的 单一 的 经济 和 贸易商 。".split()
>>> ref2 = u"这些 直接 影响 全球 贸易 和 美国 是 世界 上 最大 的 单一 的 经济 和 贸易商 。".split()
# Calculate p_1, p_2, p_3 and p_4
>>> from nltk.translate.bleu_score import _modified_precision
>>> p_1 = _modified_precision([ref1, ref2], hyp, 1)
>>> p_2 = _modified_precision([ref1, ref2], hyp, 2)
>>> p_3 = _modified_precision([ref1, ref2], hyp, 3)
>>> p_4 = _modified_precision([ref1, ref2], hyp, 4)
>>> p_1, p_2, p_3, p_4
(Fraction(4, 9), Fraction(1, 17), Fraction(0, 1), Fraction(0, 1))

Обратите внимание на последнюю версию _modified_precision в рейтинге BLEU, поскольку в этом https://github.com/nltk/nltk/pull/1229 использовались выходные данные Fraction вместо float. Итак, теперь мы ясно видим числитель и знаменатель.

Итак, давайте теперь проверим выходные данные _modified_precision для униграммы. В гипотезе жирные слова встречаются в ссылках:

  • 鉴于 美国经济贸易 国 于 一身 , 上述 因素 直接 影响世界 贸易

Есть 9 токенов, перекрывающихся с 1 из 9 - дубликат, который встречается дважды.

>>> from collections import Counter
>>> ref1_unigram_counts = Counter(ngrams(ref1, 1))
>>> ref2_unigram_counts = Counter(ngrams(ref2, 1))
>>> hyp_unigram_counts = Counter(ngrams(hyp,1))
>>> for overlaps in set(hyp_unigram_counts.keys()).intersection(ref1_unigram_counts.keys()):
...     print " ".join(overlaps)
... 
美国
直接
经济
影响
。
最大
世界
贸易
>>> overlap_counts = Counter({ng:hyp_unigram_counts[ng] for ng in set(hyp_unigram_counts.keys()).intersection(ref1_unigram_counts.keys())})
>>> overlap_counts
Counter({(u'\u8d38\u6613',): 2, (u'\u7f8e\u56fd',): 1, (u'\u76f4\u63a5',): 1, (u'\u7ecf\u6d4e',): 1, (u'\u5f71\u54cd',): 1, (u'\u3002',): 1, (u'\u6700\u5927',): 1, (u'\u4e16\u754c',): 1})

Теперь давайте проверим, сколько раз эти перекрывающиеся слова встречаются в ссылках. Взяв значение «комбинированных» счетчиков из разных источников в качестве числителя для формулы p_1. И если одно и то же слово встречается в обеих ссылках, возьмите максимальное количество.

>>> overlap_counts_in_ref1 = Counter({ng:ref1_unigram_counts[ng] for ng in set(hyp_unigram_counts.keys()).intersection(ref1_unigram_counts.keys())})
>>> overlap_counts_in_ref2 = Counter({ng:ref2_unigram_counts[ng] for ng in set(hyp_unigram_counts.keys()).intersection(ref1_unigram_counts.keys())})
>>> overlap_counts_in_ref1
Counter({(u'\u7f8e\u56fd',): 1, (u'\u76f4\u63a5',): 1, (u'\u7ecf\u6d4e',): 1, (u'\u5f71\u54cd',): 1, (u'\u3002',): 1, (u'\u6700\u5927',): 1, (u'\u4e16\u754c',): 1, (u'\u8d38\u6613',): 1})
>>> overlap_counts_in_ref2
Counter({(u'\u7f8e\u56fd',): 1, (u'\u76f4\u63a5',): 1, (u'\u7ecf\u6d4e',): 1, (u'\u5f71\u54cd',): 1, (u'\u3002',): 1, (u'\u6700\u5927',): 1, (u'\u4e16\u754c',): 1, (u'\u8d38\u6613',): 1})
>>> overlap_counts_in_ref1_ref2 = Counter()
>>> numerator = overlap_counts_in_ref1_ref2
>>> 
>>> for c in [overlap_counts_in_ref1, overlap_counts_in_ref2]:
...     for k in c:
...             numerator[k] = max(numerator.get(k,0), c[k])
... 
>>> numerator
Counter({(u'\u7f8e\u56fd',): 1, (u'\u76f4\u63a5',): 1, (u'\u7ecf\u6d4e',): 1, (u'\u5f71\u54cd',): 1, (u'\u3002',): 1, (u'\u6700\u5927',): 1, (u'\u4e16\u754c',): 1, (u'\u8d38\u6613',): 1})
>>> sum(numerator.values())
8

Теперь что касается знаменателя, это просто нет. униграмм, фигурирующих в гипотезе:

>>> hyp_unigram_counts
Counter({(u'\u8d38\u6613',): 2, (u'\u4e0e',): 1, (u'\u7f8e\u56fd',): 1, (u'\u56fd\u4e8e',): 1, (u'\u7740',): 1, (u'\u7ecf\u6d4e',): 1, (u'\u5f71\u54cd',): 1, (u'\u56e0\u7d20',): 1, (u'\u4e16\u754c',): 1, (u'\u3002',): 1, (u'\u4e00\u8eab',): 1, (u'\u6700\u5927',): 1, (u'\u9274\u4e8e',): 1, (u'\u4e0a\u8ff0',): 1, (u'\u96c6',): 1, (u'\u76f4\u63a5',): 1, (u'\uff0c',): 1})
>>> sum(hyp_unigram_counts.values())
18

В результате получается дробь 8/18 -> 4/9 и наша функция _modified_precision проверяет.

Теперь перейдем к полной формуле BLEU:

enter image description here

Из формулы рассмотрим пока только экспоненту суммирования, т.е. exp (...). Его также можно упростить как сумму логарифма различных p_n, как мы вычислили ранее, то есть sum (log (p_n)). И вот как это реализовано в NLTK, см. https://github.com/alvations/nltk/blob/develop/nltk/translate/bleu_score.py#L79

Игнорируя пока BP, давайте рассмотрим суммирование p_n и принятие во внимание их соответствующих весов:

>>> from fractions import Fraction
>>> from math import log
>>> log(Fraction(4, 9))
-0.8109302162163288
>>> log(Fraction(1, 17))
-2.833213344056216
>>> log(Fraction(0, 1))
Traceback (most recent call last):
  File "", line 1, in 
ValueError: math domain error

Ага! Вот где возникает ошибка, и сумма журналов вернула бы ValueError при передаче их через math.fsum ().

>>> try:
...     sum(log(pi) for pi in (Fraction(4, 9), Fraction(1, 17), Fraction(0, 1), Fraction(0, 1)))
... except ValueError:
...     0
... 
0

Чтобы исправить реализацию, try-except должен был быть:

s = []
# Calculates the overall modified precision for all ngrams.
# by summing the the product of the weights and the respective log *p_n*
for w, p_n in zip(weights, p_ns)):
    try:
        s.append(w * math.log(p_n))
    except ValueError:
        # some p_ns is 0
        s.append(0)
 return sum(s)

Ссылки:

Формулы взяты из http://lotus.kuee.kyoto-u.ac.jp/WAT/papers/submissions/W15/W15-5009.pdf, который описывает некоторые проблемы чувствительности с BLEU.

2022 WebDevInsider