前言
最近还卡在复现工作的结果这一环节上。具体来说,我使用那篇工作提供的脚本,使用的是fairseq-generate来完成的结果的评估。然后我发现我得到的结果和论文中的结果完全不一致。
首先,在预处理阶段,如记一次多语言机器翻译模型的训练所示,我是用moses的tokenizer完成的tokenize,然后又使用moses的lowercase完成的小写化,最后用subword-nmt bpelearn和apply的子词。当然,一方面,小写化不利于模型性能的比较(来自师兄);另一方面,可以使用sentencepiece这一工具来直接学bpe,而不需要做额外的tokenize,但本文暂不考虑。
别人是怎么做的?
这部分内容主要参考:Computing and reporting BLEU scores
考虑以下情况:机器翻译的训练和测试数据,都经过tokenize和truecased的预处理,以及bpe的分词。而在后处理阶段,在保证正确答案(ref)和模型输出(hyp)经过相同的后处理操作的情况下,不同的操作带来的BLEU值是很不同的。如下图所示:
主要有以下发现:
- 【行1vs行2】在bpe级别计算的BLEU值偏高。解释:行1没有采取任何后处理,也就是说,hyp和ref都是tokenized、truecased的bpe子词。此时,由于粒度更细,原本word级别是错误的输出,分成更细粒度的子词,可能就产生“正确”的结果了。
- 【行3vs行4】如果不使用sacreBLEU提供的standard tokenization,结果BLEU值偏高。解释:行3没有做detokenize,同时,把sacreBLEU中的standard tokenization也ban掉了;而行4先做了detokenize(行4中的tokenization要理解为detokenize),然后使用了sacreBLEU中的standard tokenization(默认应该是13a tokenizier)。一般来说,行3被称为tokenized BLEU(wrong),行4被称为detokenized BLEU(right)。但是我不知道这两个为什么差别这么大。
- 【行5】如果不考虑大小写,也就是完全小写,BLEU值偏高。
- 【行6】这一类应该也算是tokenized BLEU,也就是说,它先做了detokenize,只不过在计算BLEU时,又要进行tokenize的时候,使用的是其它第三方的tokenizer而不是sacreBLEU中的tokenizer,这种做法对最终的结果也有影响。
总结:
- 用SacreBLEU!
- 在计算BLEU之前,要做好完全的post-preprocess(undo BPE\truecase\detokenize等等)!
我的错误
经过上一部分,可以看到,我的预处理步骤的问题并不大(虽然小写会导致结果偏高),我的问题主要出在,在运行fairseq-generate时,我没有提供bpe\bpe-codes\tokenizer\scoring\post-process参数,也就是说,我没有做任何的post-process。其中,各参数的功能如下:
- bpe:通过bpe.decode(x)语句来做undo bpe。以bpe=subword_nmt为例,做的就是:
(x + " ").replace(self.bpe_symbol, "").rstrip(),其中self.bpe_symbol=@@
- tokenizer:通过tokenizer.decode(x)语句来做detokenize。以tokenzier=moses为例,做的就是:
MosesDetokenizer.detokenize(inp.split())
- scoring:通过scorer = scoring.build_scorer(cfg.scoring, tgt_dict)语句来建立计算BLEU的对象。如果scoring=‘bleu’(默认),则具体计算的语句为
scorer.add(target_tokens, hypo_tokens)
,其中,target\hypo_tokens是经过一些预处理(可能经过了undo bpe和detokenize,也有可能什么也没做:只是用" "这个分隔符把sentence中所有的tokenized bpe子词连成了一串字符串),得到target\hypo_str之后,再做fairseq提供的简单的tokenize(fairseq/fairseq/tokenizer.py),然后计算BLEU值。而如果scoring=‘sacrebleu’,则具体计算的语句为scorer.add_string(target_str, detok_hypo_str)
,在这种情况下,如果我们做好了应该做的post-process,target_str就是纯纯的原文本,detok_hypo_str也如此。而在计算BLEU值时,我们又是用的sacrebleu的standard tokenization,可比性会高很多。 - post-process:这个参数和bpe参数重复
(可能)正确做法
做法一:直接用fairseq-generate计算BLEU值
CUDA_VISIBLE_DEVICES=5 fairseq-generate ../../data/iwslt14/data-bin --path checkpoints/iwslt14/baseline/evaluate_bleu/checkpoint_best.pt --task translation_multi_simple_epoch --source-lang ar --target-lang en --encoder-langtok "src" --decoder-langtok --bpe subword_nmt --tokenizer moses --scoring sacrebleu --bpe-codes /home/syxu/data/iwslt14/code --lang-pairs "ar-en,de-en,en-ar,en-de,en-es,en-fa,en-he,en-it,en-nl,en-pl,es-en,fa-en,he-en,it-en,nl-en,pl-en" --quiet
主要就是提供了bpe\bpe-codes\tokenizer\scoring,这四个参数。
做法二:先用fairseq-generate生成,再用sacrebleu评估
另外,也可以不看fairseq-generate提供的BLEU结果,而是利用它生成的hyp.txt和ref.txt文件,然后用sacrebleu工具来计算分数。
用fairseq-generate生成
cat ${genpath} | grep -P "^T" | cut -f 2- > ${refpath}
cat ${genpath} | grep -P "^D" | cut -f 3- > ${hyppath}
sacrebleu ${refpath} -i ${hyppath} -w 2 -b
注意:这里的ref.txt是fairseq-generate生成文件中的T开头的文本,而hyp.txt是fairseq-generate生成文件中的D开头的文本,如下图所示。
也碰到fairseq提供的一些examples中,它们的hyp.txt用的是生成文件中的H开头的文本,个人觉得不太对,因为H开头的文本只是经过了remove bpe,还没有完成detokenize。(如果它一开始就用sentencepiece来训练bpe的话,这里的H开头的文本应该就是纯文本?—>待确认)
尚存疑问
- 为什么tokenized\detokenized BLEU差别那么大?----> 师兄说,可能有一些语种,如拉丁字母构成的,这种情况下,tokenized BLEU意味着,使用拉丁字母特有的分词器来进行分词,可能会把文本全部分成字符。
- fairseq-generate的–sacrebleu有什么用?---->好像没屌用。
参考资料
https://bricksdont.github.io/posts/2020/12/computing-and-reporting-bleu-scores/