predicate.jp - Yusuke Oda

BLEU (Bilingual Evaluation Understudy)

以下の実装例は元々のBLEUの定義に忠実なものだが,一般的に使用されるツールでは様々な変形が施されており,同じスコアが出るとは限らない. また言語依存の字句解析も必要である.

実際に機械翻訳の実験を行う際は,自分で実装したコードを使用するのではなく,ツールに頼る方が確実だろう.

import math
import collections

# 文からBLEU統計量を計算
def get_bleu_stats(ref: List[str], hyp: List[str], N: int = 4) -> Dict[str, int]:
  stats = collections.defaultdict(int, {'rl': len(ref), 'hl': len(hyp)})
  N = len(hyp) if len(hyp) < N else N
  for n in range(N):
    matched = 0
    possible = collections.defaultdict(int)
    for k in range(len(ref) - n):
      possible[tuple(ref[k : k + n + 1])] += 1
    for k in range(len(hyp) - n):
      ngram = tuple(hyp[k : k + n + 1])
      if possible[ngram] > 0:
        possible[ngram] -= 1
        matched += 1
    stats['d' + str(n + 1)] = len(hyp) - n
    stats['n' + str(n + 1)] = matched
  return stats

# BLEU統計量からBLEUを計算
def calculate_bleu(stats: Dict[str, int], N: int = 4) -> float:
  np = 0.0
  for n in range(N):
    nn = stats['n' + str(n + 1)]
    if nn == 0:
      return 0.0
    dd = stats['d' + str(n + 1)]
    np += math.log(nn) - math.log(dd)
  bp = 1.0 - stats['rl'] / stats['hl']
  if bp > 0.0: bp = 0.0
  return math.exp(np / N + bp)
  
# 単一リファレンスの場合
ref = ['This', 'is', 'a', 'pen', '.']
hyp = ['There', 'is', 'a', 'pen', '.']
stats = get_bleu_stats(ref, hyp)
bleu = calculate_bleu(stats) # => 0.668740

# 複数リファレンスの場合
stats = collections.defaultdict(int)
for ref, hyp in zip(refs, hyps):
  for k, v in get_bleu_stats(ref, hyp).items():
  stats[k] += v
bleu = calculate_bleu(stats)