気が向いたら書くやつ

なんかプログラミングのことについて

NLTKのsentence_bleuで送出される警告について

機械翻訳などの論文を読むと評価尺度としてBLEUというものが用いられています。

NLTKMosesといった有名な言語処理ライブラリにBLEUを計算する関数が実装されており、私のようによく知らずとも簡単に翻訳文の評価を行うことができます。

その場合、NLTKでは以下のように警告されることがあります。

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

これが発生する理由について、この記事では述べたいと思います。

sentence_bleuとcorpus_bleuの違い

pythonのライブラリであるNLTKにはbleuを計算する関数としてsentence_bleuとcorpus_bleuという2つの関数が実装されています。

ソースを読んでみるとsentence_bleuは1文をcorpus_bleuに渡しているだけなので基本的に処理自体は変わりません。

しかし、corpus_bleuで警告がでなくとも、一文ずつsentence_bleuで計算すると警告が発生してしまうことが多くあります。

この理由については、(気が向けば)別の記事で述べたいと思います。

BLEUの計算方法

BLEUの求め方は至って簡単です。
参照文(正しい翻訳)Rと仮定文(機械翻訳)Hがあるとして、まずngram精度P_nを求めます。

\displaystyle
P_n=\frac{Rの\mathrm{ngram}に含まれるHの\mathrm{ngram}の数}{Hの\mathrm{ngram}の数}

これはつまり、R=[a,b,c]とH=[a,d,a]があるとすれば

n=1\displaystyle\frac{1}{3}

n=2\displaystyle\frac{0}{2}

となるわけです。
このngram精度を使ってBLEU値は以下のように求めることができます。

\displaystyle
BLEU = BP * \exp(\frac{1}{N}\sum_{i=1}^{N}\log{P_n})\\
BP = \begin{cases} \exp(1-len_{R}/len_{H}) & len_{H} \leq len_{R}\\1 & else \end{cases}

ここでNは考慮するngramの最大値です。NLTKでもMosesでもデフォルト値は4になっており、基本は4のようです。
また、短い文のngram精度は高くなるので、BPでペナルティを与えます。

計算できない

そうです、上の例におけるn=2P_n=0になるので\log{0}となり、計算することができないのです。

この時にNLTKから2gramの一致数が0であるためBLEUスコアが正しくない可能性があると、以下のように警告されます。

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

算出される値は何か

この警告がなされた場合でもNLTKは無理やりBLEU値を計算して出力します。

P_{n}=0の場合、NLTKでは\log{P_n}=0として計算します。

また、ngramに分割できないn=4の場合も0として計算します(したがって、文の長さが4未満なら必ず警告が出ます)。

上の例を用いて、NLTKのsentence_bleuで計算すると

from nltk.translate import bleu_score

ref = [["a", "b", "c"]]
hypo = ["a", "d", "a"]

print(bleu_score.sentence_bleu(ref, hypo))

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

この値は

\displaystyle
P_{1}=\frac{1}{3}, P_{2}=0, P_{3}=0, P_{4}=0

なので

\displaystyle
\exp(\frac{1}{4}(\log{P_{1}} + \log{P_{2}} + \log{P_{3}} + \log{P_{4}}))

この式は計算できないため

\displaystyle
\exp(\frac{1}{4}(\log{\frac{1}{3}} + 0 + 0 + 0))

と同じになります。

from nltk.translate import bleu_score
import math

ref = [["a", "b", "c"]]
hypo = ["a", "d", "a"]

print("nltk:", bleu_score.sentence_bleu(ref, hypo))
print("math:", math.exp(math.log(1 / 3) / 4))

"""
出力
UserWarning:
Corpus/Sentence contains 0 counts of 2-gram overlaps.
BLEU scores might be undesirable; use SmoothingFunction().
  warnings.warn(_msg)
nltk: 0.7598356856515925
math: 0.7598356856515925
"""

Mosesではどうなっているか

Mosesにはmulti-bleu.perlというプログラムが含まれており、これがbleuを計算するプログラムになっています。

上述のようなP_{n}=0が含まれる場合、BLEU値は問答無用で0になります(警告文は出ます)。

警告を消す方法

警告を消す方法として最も簡単なものとして、sentence_bleu関数のemulate_multibleuのフラグをTrueにする方法が挙げられます。

これは、おそらくMosesのmulti-bleuと同じ動作にするフラグです(未調査)。

そのため、警告は出ませんがNLTKでもBLEU値が0になります。

from nltk.translate import bleu_score
import math

ref = [["a", "b", "c"]]
hypo = ["a", "d", "a"]

print("nltk:", bleu_score.sentence_bleu(ref, hypo, emulate_multibleu=True))
print("math:", math.exp( math.log(1/3) / 4))

"""
出力
nltk: 0.0
math: 0.7598356856515925
"""

どちらが正しいのか

わかりません。
誰か詳しい方教えてくださいお願いします。