ナード戦隊データマン

機械学習と自然言語処理についてのブログ

fairseqを試しに使ってみる

fairseq1とは、研究者や開発者が翻訳、要約、言語モデリング、その他のテキスト生成タスク用のカスタムモデルをトレーニングできるシーケンスモデリングツールキットです。

これまでtensor2tensor2を使ってきましたが、WMT3では多くの論文がfairseqによって実装されているため、fairseqの基本的な使い方を試します。

インストール

pip3 install fairseq

データのダウンロード

今回は使い方の確認だけなので、tatoebaコーパスを使います。

#!/bin/bash
 
mkdir data
cd data
if [ ! -f "jpn-eng.zip" ]; then
    wget https://www.manythings.org/anki/jpn-eng.zip
    unzip jpn-eng.zip
fi

前処理

mecab.pl

まず、mecabperlラッパーを作成します。

#!/usr/bin/env perl
system("mecab -Owakati");

split_data.py

次に、データスプリッタを作成します。

from sklearn.utils import shuffle
 
 
def split(srcfile="orig/ja-en/train.tags.ja-en.ja",
          tgtfile="orig/ja-en/train.tags.ja-en.en",
          valid_size=0.05,
          test_size=0.15):
 
    with open(srcfile) as fsrc, open(tgtfile) as ftgt:
        data = shuffle(list(zip(fsrc, ftgt)))
    tmp_size_abs = int(len(data) * (valid_size + test_size))
    tmp = data[:tmp_size_abs]
    train = data[tmp_size_abs:]
    valid_size_abs = int(len(tmp) * (valid_size / (test_size + valid_size)))
    valid = tmp[:valid_size_abs]
    test = tmp[valid_size_abs:]
    tmpdir = "tatoeba.tokenized.ja-en/tmp/"
 
    for x in [("train.ja", "train.en", train), ("valid.ja", "valid.en", valid),
              ("test.ja", "test.en", test)]:
 
        with open(tmpdir + x[0], "w") as fsrc, open(tmpdir + x[1],
                                                    "w") as ftgt:
            for row in x[2]:
                fsrc.write(row[0].strip() + "\n")
                ftgt.write(row[1].strip() + "\n")
 
 
if __name__ == "__main__":
    split()

prepare.sh

最後に、BPEなどで前処理を行うスクリプトを作成します。

#!/usr/bin/env bash
#
# Adapted from https://github.com/facebookresearch/MIXER/blob/master/prepareData.sh
 
if [ ! -f mosesdecoder ]; then
    echo 'Cloning Moses github repository (for tokenization scripts)...'
    git clone https://github.com/moses-smt/mosesdecoder.git
fi
 
if [! -f subword-nmt ]; then
    echo 'Cloning Subword NMT repository (for BPE pre-processing)...'
    git clone https://github.com/rsennrich/subword-nmt.git
fi
 
SCRIPTS=mosesdecoder/scripts
#TOKENIZER=$SCRIPTS/tokenizer/tokenizer.perl
TOKENIZER=mecab.pl
LC=$SCRIPTS/tokenizer/lowercase.perl
CLEAN=$SCRIPTS/training/clean-corpus-n.perl
BPEROOT=subword-nmt
BPE_TOKENS=10000
 
src=ja
tgt=en
lang=ja-en
prep=tatoeba.tokenized.ja-en
tmp=$prep/tmp
orig=orig
 
mkdir -p $orig $tmp $prep
 
mv jpn.txt orig
mkdir $orig/$lang
cat orig/jpn.txt | cut -f 2 > $orig/$lang/train.tags.$lang.$src
cat orig/jpn.txt | cut -f 1 > $orig/$lang/train.tags.$lang.$tgt
 
echo "pre-processing train data..."
for l in $src $tgt; do
    f=train.tags.$lang.$l
    tok=train.tags.$lang.tok.$l
 
    cat $orig/$lang/$f | perl $TOKENIZER -threads 8 -l $l > $tmp/$tok
    echo ""
done
perl $CLEAN -ratio 1.5 $tmp/train.tags.$lang.tok $src $tgt $tmp/train.tags.$lang.clean 1 175
for l in $src $tgt; do
    perl $LC < $tmp/train.tags.$lang.clean.$l > $tmp/train.tags.$lang.$l
done
 
echo "creating train, valid, test..."
python3 split_data.py
 
TRAIN=$tmp/train.ja-en
BPE_CODE=$prep/code
rm -f $TRAIN
for l in $src $tgt; do
    cat $tmp/train.$l >> $TRAIN
done
 
echo "learn_bpe.py on ${TRAIN}..."
python3 $BPEROOT/learn_bpe.py -s $BPE_TOKENS < $TRAIN > $BPE_CODE
 
for L in $src $tgt; do
    for f in train.$L valid.$L test.$L; do
        echo "apply_bpe.py to ${f}..."
        python3 $BPEROOT/apply_bpe.py -c $BPE_CODE < $tmp/$f > $prep/$f
    done
done

そして、前処理を実行してください。

chmod +x prepare.sh
./prepare.sh

fairseqの実行

前処理

tensor2tensorのt2t-datagenのようなコマンドを実行します。

#!/bin/bash
 
TEXT=data/tatoeba.tokenized.ja-en
fairseq-preprocess --source-lang ja --target-lang en \
           --trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \
           --destdir data-bin/tatoeba.tokenized.ja-en \
           --workers 6

訓練

各種パラメータを設定して訓練します。

#!/bin/bash

fairseq-train data-bin/tatoeba.tokenized.ja-en \
          --arch transformer \
          --share-decoder-input-output-embed \
          --optimizer adam --adam-betas '(0.9,0.98)' --clip-norm 0.0 \
          --lr 5e-4 --lr-scheduler inverse_sqrt --warmup-updates 2000 \
          --dropout 0.3 --weight-decay 0.0001 \
          --criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
          --max-tokens 4096

評価

最後に、モデルをテストデータで評価します。

#!/bin/bash
fairseq-generate data-bin/tatoeba.tokenized.ja-en/ --path checkpoints/checkpoint_best.pt --beam 5 --remove-bpe > result.txt

結果

結果の一部は以下です。

()
S-4243  私の知る限り、彼はその詐欺の企みには関与していません。
T-4243  To the best of my knowledge, he wasn't involved in that fraud scheme.
H-4243    -0.8243939876556396 I don't know what to do.
P-4243  -0.3366 -1.4673 -0.2196 -1.5008 -1.5084 -0.7058 -0.0323
S-1448  咳、くしゃみ、あくびをする時は口を手で隠しなさい。
T-1448  Cover your mouth when you cough, sneeze, or yawn.
H-1448  -1.4342234134674072   A lot of people are looking for a good job.
P-1448  -2.0519 -2.7051 -0.0421 -0.4364 -1.3717 -2.5713 -0.9257 -1.2887 -1.2416 -3.0878 -0.0541
S-837   すごく待たせちゃってるのは分かってるんだけど、もうちょっとだけ待ってくれない?
T-837   I know you've been waiting a long time, but could you wait just a little bit longer?
H-837 -1.273877501487732  I wish I had a little more time to do today.
P-837 -0.6580 -2.3883 -0.5018 -1.2730 -0.4669 -2.3512 -1.5680 -1.7534 -1.3592 -0.5280 -2.3535 -0.0851
S-1724    「あの狭い部屋に30ドルはあんまりだ」と彼は思いました。
T-1724    "Thirty dollars is a lot for that small room," he thought.
H-1724    -1.5112301111221313 He said that he was in the accident.
P-1724    -1.4345 -3.1776 -0.7222 -1.7109 -0.8554 -1.9798 -0.6085 -3.0125 -0.0996
S-3019    その若い男性は、少女を不良の集団から助け出した。
T-3019    The young man saved the girl from a bunch of hoodlums.
H-3019    -1.4707800149917603 The old man was in the hospital.
P-3019    -0.1604 -2.4486 -0.3420 -1.4774 -3.0541 -0.7316 -3.5086 -0.0436
S-5019    おばあちゃんは身をかがめて<unk> の付いた針を拾った。
T-5019    My grandma stooped down and picked up a needle and thread.
H-5019    -1.7394040822982788 The girl was in a small accident.
P-5019    -0.3933 -2.6049 -1.4189 -2.7932 -1.6729 -2.5982 -2.3899 -0.0439
S-3992    これはなるべく早くお医者さんに診てもらった方がいいですよ。
T-3992    I suggest that you go and see a doctor about this as soon as you can.
H-3992    -1.126047968864441  I think that you should be a good idea.
P-3992    -0.5748 -1.3976 -1.1855 -1.3189 -0.9449 -1.2461 -1.1691 -1.0424 -2.2706 -0.1105
S-1646    冷戦はソビエトの崩壊と共に終わった。
T-1646    The Cold War ended when the Soviet Union collapsed.
H-1646    -1.4077836275100708 The population of the United States.
P-1646    -0.0732 -4.0193 -0.1943 -0.3842 -3.0299 -2.1153 -0.0383
S-5346    トムは、低<unk> <unk> ・高タンパク質ダイエットをしています。
T-5346    Tom is on a low-fat, high-protein diet.
H-5346    -1.0237045288085938 Tom has a lot of things to do.
P-5346    -0.0096 -0.9821 -0.8884 -2.1799 -0.1594 -3.0087 -0.2995 -1.6521 -0.0337
S-135 経済はまだ金融危機から完全に立ち直ってはいません。
T-135 The economy still hasn't completely recovered from the financial crisis.
H-135   -1.0100727081298828   There are a lot of people to do.
P-135   -1.9777 -0.8213 -0.8172 -0.5992 -0.0287 -1.0217 -1.7647 -2.0260 -0.0342
S-2046  信じられないだろうけど、その洞窟から怪獣が現れたんだ。
T-2046  Believe it or not, a monster emerged from the cave.
H-2046  -1.0378917455673218   I thought that it was a good idea.
P-2046  -0.4669 -1.4385 -0.5296 -2.4783 -0.3258 -1.1812 -1.1207 -1.7534 -0.0466
S-5734  子供みたいなことをするんなら、そのように扱われるってことだよ。
T-5734  If you act like a child, you will be treated as such.
H-5734  -1.156415581703186    I don't know how to do that.
P-5734    -1.1103 -1.6996 -0.7171 -1.9284 -0.1349 -2.1829 -1.4368 -0.0415
| Translated 6764 sentences (63943 tokens) in 20.5s (330.25 sentences/s, 3121.99 tokens/s)
| Generate test with beam=5: BLEU4 = 4.41, 21.4/6.2/2.5/1.1 (BP=1.000, ratio=1.134, syslen=50626, reflen=44626)

あまり翻訳の精度がよくありませんが、データが少ないのと、ハイパーパラメータがテキトーなのであまり良い学習はしていません。

個人的な目標

WMT19などの論文を読み、fairseqを使って結果を再現することが目標です。この目標を達成するためには、fairseqでより細かな設定を行う方法を理解する必要があります。例えば逆翻訳を行うためには、モノリンガルデータから効率的に翻訳する方法が必要ですし、論文のハイパーパラメータと同じ設定を使うには、用意されている設定セット以外のパラメータを設定する方法を知る必要があります。

参考