ナード戦隊データマン

機械学習, 自然言語処理, データサイエンスについてのブログ

SGNMT: 機械翻訳のためのデコーディングプラットフォーム

機械翻訳において性能を向上させるための一つの方法として、デコーディング系の手法があります。SGNMT1はStahlbergらによって開発されており、様々なデコーディング手法をサポートしているプラットフォームです。

概要

デコーディング手法には以下のようなものがあります。

  • 複数のモデルのアンサンブル。
  • SMTのLatticeを使うことによる文法補正。
  • 言語モデル等を使ったリスコア。

SGNMTはこれらをサポートしている旨が書かれています2

SGNMT is an open-source framework for neural machine translation (NMT) and other sequence prediction tasks. The tool provides a flexible platform which allows pairing NMT with various other models such as language models, length models, or bag2seq models. It supports rescoring both n-best lists and lattices. A wide variety of search strategies is available for complex decoding problems.

訳: SGNMTはNMTや他の系列予測タスクのためのオープンソースフレームワークです。ツールはNMTと様々な他のモデル(言語モデル, 長さモデル, bag2seq)のペアリングを可能とする柔軟なプラットフォームを提供します。n-bestリストとlatticeからリスコアすることもサポートしています。複雑なデコード問題に対する様々なサーチ戦略が可能です。

--

現在のところ、tensor2tensor3やfairseq4をサポートしていますが、まだドキュメントが整備されていない部分もあり、未熟な段階です。しかし、チュートリアルデータを使って試せるので試してみます。

インストール

まずはインストールから始めます。

install_openfst.sh

finite-state transducersを使ってlatticeを表現する機能がサポートされているので、openfst5をインストールします。

#!/bin/bash

mkdir -p ../tools
pushd ../tools
if [ ! -f openfst-1.7.3.tar.gz ]; then
    wget http://www.openfst.org/twiki/pub/FST/FstDownload/openfst-1.7.3.tar.gz
    tar -xzvf openfst-1.7.3.tar.gz
fi

if [ ! -x "$(command -v fstprint)" ]; then
    cd openfst-1.7.3
    ./configure --enable-far=true
    make -j4
    make install
fi
popd

install_moses.sh

moses6も使うので準備します。

#!/bin/bash

mkdir -p ../tools
pushd ../tools
if [ ! -f mosesdecoder/README ]; then
    git clone https://github.com/moses-smt/mosesdecoder
fi
popd

install_sgnmt.sh

sgnmtと依存ツールを入れます。

#!/bin/bash

pushd ..
git clone https://github.com/ucam-smt/sgnmt.git
pip3 install https://github.com/kpu/kenlm/archive/master.zip
pip3 install -r requirements.txt
python3 sgnmt/decode.py --run_diagnostics
popd

チュートリアルデータのダウンロード

チュートリアルデータは未公開ですが、ぐぐったら出てきたので、それをダウンロードします。

wget http://www.e8u.de/files/sgnmt/tutorial-wmt19-ende.tar.gz 
unzip tutorial-wmt19-ende.tar.gz

実行

それでは、チュートリアルデータを使って実行していきます。

tensor2tensorだけでデコード

tensor2tensorを使って単純にデコードする場合は、以下を実行します。

makedir -p output
makedir -p output/out_base
python ../sgnmt/decode.py --config_file ini/base.ini,ini/bpe.ini --src_test data/test18.ende.preprocess.en --outputs text,nbest,sfst,timecsv --output_path output/out_base/base-output.%s

sgnmt/decode.pyを実行し、設定ファイルとして、ini/base.iniとini/bpe.iniを渡します。テストデータとして、前処理済みのデータを指定し、text, nbest, sfst, timecsvの4つの形式で出力します。出力ファイルはoutput/out_base/base-output.%sに出力します。

設定ファイルの中身

設定ファイルの中身は以下のようになっています。

[base.ini]

predictors: t2t

pred_src_vocab_size: 35627
pred_trg_vocab_size: 35627
t2t_model: transformer
t2t_checkpoint_dir: models/ende/base/
t2t_problem: translate_ende_wmt32k
t2t_hparams_set: transformer_base_v2

[bpe.ini]

wmap: data/wmap.bpe.ende
bpe_codes: data/bpe.train.ende
preprocessing: bpe
postprocessing: bpe

bpe.iniでは、bpeのサブワードのマップとそのIDを示したファイルが指定されています。もしini/base.iniだけ指定する場合は、サブワードのIDを入力し、サブワードのIDを出力する形になりますが、bpe.iniによってIDを変換する前処理・後処理を行えるようになるようです。

出力

  • text: 翻訳結果
  • nbest: 翻訳結果のnbest出力
  • sfst: fstの出力

nbest出力は以下のようになっています。

0 ||| München 1856 : vier Karten , die Ihren Blick auf die Stadt verändern werden  ||| t2t= -5.486428 ||| -5.486428
0 ||| München 1856 : vier Karten , die Ihre Sicht auf die Stadt verändern werden  ||| t2t= -6.245754 ||| -6.245754
0 ||| München 1856 : vier Karten , die Ihren Blick auf die Stadt verändern .  ||| t2t= -6.356880 ||| -6.356880
1 ||| ein geistiges Asyl , wo heute junge Menschen sollen sich treffen .  ||| t2t= -8.179746 ||| -8.179746
1 ||| ein psychisches Asyl , wo heute junge Menschen sollen sich treffen .  ||| t2t= -8.702413 ||| -8.702413
1 ||| ein geistiges Asyl , wo heute junge Menschen sich treffen sollen .  ||| t2t= -8.870972 ||| -8.870972
1 ||| ein geistiges Asyl , wo heute junge Menschen zu treffen sind .  ||| t2t= -8.873652 ||| -8.873652

fstは(おそらく) nbest出力をfst形式のグラフにしています。

$ fstprint --isymbols=data/wmap.bpe.ende --acceptor output/out_base/base-output.sfst/1.fst
0       1       <s>     5.48642778
1       2       München</w>
2       3       18
3       4       56</w>
4       5       :</w>
5       6       vier</w>
6       7       Karten</w>
7       8       ,</w>
8       9       die</w>
9       10      Ihre</w>        0.759325981
9       11      Ihren</w>
10      12      Sicht</w>
11      13      Blick</w>
12      14      auf</w>
13      15      auf</w>
14      16      die</w>
15      17      die</w>
16      18      Stadt</w>
17      19      Stadt</w>
18      20      verändern</w>
19      21      verändern</w>
20      22      werden</w>
21      22      werden</w>
21      22      .</w>   0.870451987
22      23      </s>
23

アンサンブルモデル

アンサンブルデコーディングを実行するには、ini/ensemble.iniを指定します。

[ini/ensemble.ini]

predictors: t2t, t2t

t2t_checkpoint_dir: models/ende/base/
t2t_hparams_set: transformer_base_v2

t2t_checkpoint_dir2: models/ende/big/
t2t_hparams_set2: transformer_big

pred_src_vocab_size: 35627
pred_trg_vocab_size: 35627
t2t_model: transformer
t2t_problem: translate_ende_wmt32k

これは、baseモデルとbigモデルのアンサンブルデコーディングを表す設定ファイルです。

Latticeリスコアリング

ACL16の論文7では、文法エラーを補正するためにSMTのlatticeを用いてリスコアリングする旨が書かれているようです。リスコアリングのために、predictorsにt2t,fstを指定します。fst_pathにはsupplementary以下のファイルが指定されていますが、これは事前に用意されたfstファイルです。このファイルを自前で生成するためにはCambridge SMT Systemの説明を参考にしてください8

[ini/rescoring]

predictors: t2t, fst

fst_path: supplementary/smt.lats_test18/%d.fst

config_file: ini/bpe.ini
src_test: data/test18.ende.preprocess.en

pred_src_vocab_size: 35627
pred_trg_vocab_size: 35627
t2t_model: transformer
t2t_checkpoint_dir: models/ende/base/
t2t_problem: translate_ende_wmt32k
t2t_hparams_set: transformer_base_v2

outputs: text, nbest

それぞれのモデルの評価

モデルを評価するためのスクリプトはscripts/eval_test18.ende.shに入っています。

翻訳する

その前に、それぞれのモデルを使ってテストデータを翻訳する必要があります。

#!/bin/bash

python ../sgnmt/decode.py --config_file ini/base.ini,ini/bpe.ini --src_test data/test18.ende.preprocess.en --outputs text,nbest,sfst,timecsv --output_path output/out_base/base-output.%s

python ../sgnmt/decode.py --config_file ini/ensemble.ini,ini/bpe.ini --src_test data/test18.ende.preprocess.en --outputs text,nbest,sfst,timecsv --output_path output/out_ensemble/ensemble-output.%s

python ../sgnmt/decode.py --config_file ini/rescoring.ini --src_test data/test18.ende.preprocess.en --outputs text,nbest,sfst,timecsv --output_path output/out_rescoring/rescoring-output.%s

# python ../sgnmt/decode.py --config_file ini/ucam-wmt19-base.ini --src_test data/test18.ende.preprocess.en --outputs text,nbest,sfst,timecsv --output_path output/out_ucam-wmt19-base/ucam-output.%s

翻訳したものを評価する

評価スクリプトの一部を修正します。BPEのIDを入力した場合のスクリプトになっていますが、これをサブワードそのものを入力するものにします。

[scripts/eval_test18_ende.sh]

#!/bin/bash

export LC_ALL=en_GB.utf8

MOSES_HOME=/root/work/aspec_experiments/sgnmt/tools/mosesdecoder
SGNMT=/root/work/aspec_experiments/sgnmt/sgnmt

echo $(dirname "$0")
#cat /dev/stdin | python $SGNMT/scripts/apply_wmap.py -m $(dirname "$0")/../data/wmap.bpe.ende -d i2s -t eow | $MOSES_HOME/scripts/recaser/detruecase.perl | $MOSES_HOME/scripts/tokenizer/detokenizer.perl | sed 's/" ?"/"/g' | sed 's/\([.,!?]\) *"/\1"/g' | sed 's/\([0-9]\) *: *\([0-9]\)/\1:\2/g' | sed 's/ ",/",/g' | sed "s/^'"'/"/' | sed "s/'$"'/"/' | sed "s/ 's /'s /g" | sed 's/,"/, "/g'  > tmp.bleu.hyp
cat /dev/stdin | $MOSES_HOME/scripts/recaser/detruecase.perl | $MOSES_HOME/scripts/tokenizer/detokenizer.perl | sed 's/" ?"/"/g' | sed 's/\([.,!?]\) *"/\1"/g' | sed 's/\([0-9]\) *: *\([0-9]\)/\1:\2/g' | sed 's/ ",/",/g' | sed "s/^'"'/"/' | sed "s/'$"'/"/' | sed "s/ 's /'s /g" | sed 's/,"/, "/g'  > tmp.bleu.hyp
cat tmp.bleu.hyp | perl $MOSES_HOME/scripts/ems/support/wrap-xml.perl de $(dirname "$0")/sgm/newstest2018-ende-src.en.sgm UCAM > tmp.bleu.sgm

$MOSES_HOME/scripts/generic/mteval-v13a.pl -c -s $(dirname "$0")/sgm/newstest2018-ende-src.en.sgm -r $(dirname "$0")/sgm/newstest2018-ende-ref.de.sgm  -t tmp.bleu.sgm -d 3 > tmp.bleu.eval

cat tmp.bleu.eval | tr "\n" ' ' | sed 's/Cumulative.*$//' | sed 's/^.*length ratio: \([0-9.-]\+\) (\([0-9.-]\+\)\/\([0-9.-]\+\)), penalty (log): \([0-9.-]\+\).*BLEU score = \([0-9.-]\+\) .*BLEU:/\1 \2 \3 \4 \5/' | awk '{print "BLEU = "($5*100)", "($6*100)"/"($7*100)"/"($8*100)"/"($9*100)" (BP="exp($4)", ratio="$1", hyp_len="$2", ref_len="$3")"}'

最後に、評価結果を出力するスクリプトを実行します。

#!/bin/bash

cat output/out_base/base-output.text | ./scripts/eval_test18_ende.sh 2>&1 | tee -a bleu_scores.txt
cat output/out_ensemble/ensemble-output.text | ./scripts/eval_test18_ende.sh | tee -a bleu_scores.txt
cat output/out_rescoring/rescoring-output.text | ./scripts/eval_test18_ende.sh | tee -a bleu_scores.txt
#cat output/out_ucam-wmt19-base/ucam-output.text | ./scripts/eval_test18_ende_ids.sh

結果

[base]
BLEU = 43.75, 72.07/49.79/37.11/28.39 (BP=0.992285, ratio=0.99231439417512, hyp_len=63782, ref_len=64276)

[ensemble]
BLEU = 47.8, 74.3/53.49/41.05/32.32 (BP=0.997617, ratio=0.997619640301201, hyp_len=64123, ref_len=64276)

[rescoring]
BLEU = 31.42, 64.81/37.5/24.42/16.42 (BP=1, ratio=1.01572904349991, hyp_len=65287, ref_len=64276)

rescoringが思ったよりも良い結果が出ませんでした。

考察

とりあえず、チュートリアルデータに対しては正常に実行できるようですが、ツールとしての使い勝手はイマイチで、ドキュメントも説明が足りない部分が多い気がしました。

デコード時に複数のモデルを組み合わせる手法は、デコードにかなり時間がかかってしまいます。ucam-wmt19モデルはスコアは高いようですが、実用性は低いかもしれません。

t2tのモデルをロードする際の挙動が不明で、自前のモデルをロードする際にエラーが出てしまうため、issueを残しました。

github.com

たしかに、デコード時の工夫をすることによって翻訳を改善するという手法は、BLEUスコアを上げるかもしれません。ただ、現段階でSGNMT以外に類似のツールはなく、研究や実装が未熟な段階かもしれません。

参考