💡 概要
- 評価・推論するための英語文法誤り訂正について最低限の知識が得られる
- Fairseqを用いてTransformerの文法誤り訂正モデルを動かす
- 文法誤り訂正モデルの推論結果を評価する
はじめに
2021年12月現在,著者の金子は東工大岡崎研で研究員をしている.学生の頃に文法誤り訂正 (Grammatical Error Correction; GEC) に関する研究をやっていた関係で,研究室の学生と一緒にGECの研究をやっている(現在2人の学生がGECを研究中!).その際,これを見ておけばGECモデルを最低限動かせるようになるというようなものがあれば良いなと思ったので,研究室に配属されてGECの研究を始めようとしている学生がお手軽に国際会議に投稿されているような設定でGECモデルを動かせるようになってくれれば嬉しいという記事を書く.
コードは全てのこのリポジトリにあるのでそれを使う.
GECデータセットの準備

学習データと開発データについて
一般的にBEA shared taskで用いられたデータセットが学習データと開発データとして広く使われている.このデータセットではFCE, Lang-8, NUCLEとW&I + LOCNESSの4つのデータセットの学習データをくっつけることで,BEA学習データを構築する.その他の学習データとして,Wikipediaの編集履歴から作成したWikEd(現在は配布されていない気がする),訂正が低品質であるが大規模な学習者データであるEFCamDatや論文ドメインのAESWなどが存在する.そして,W&I+LOCNESSの開発データがチューニングのために使われることが多い.このブログではBEA学習データとW&I+LOCNESS開発データを用いる.
まずBEA shared taskのサイトの「Other Corpora and Download Links」からデータセットをゲットする(Lang-8とNUCLEはリクエストが必要).M2形式(誤文に対する編集が1行ずつに書かれている)でデータが提供されているためfairseqで学習するためにはパラレルデータ形式(ソース文とターゲット文別々のテキストデータに1行ずつ並んだ形式)に変換する必要がある.そして,学習データでは基本的に訂正されていない文対は前処理で学習データから除外することが多い.
評価データと評価指標について
主に評価データとしては,学習者のエッセイをベースにしたW&I+LOCNESS,CoNLL-2014とJFLEG [記事]が使われている.W&I+LOCNESSは学習者の習熟度に関する情報が付与されている.W&I+LOCNESSやCoNLL2014と異なりJFLEGは最低限の文法的な訂正だけでなく,流暢になるように訂正が行われている.その他の評価データとしてウェブドメインのGMEGやCWEBなどがある.このブログではを用いるW&I+LOCNESS評価データ,CoNLL-2014とJFLEGを用いる.
GECでは評価データによって使われる評価指標が異なる.CoNLL-2014,W&I+LOCNESSとJFLEGではそれぞれ以下の評価指標が使われている.
- ${\rm M}^2$, CoNLL-2014:ターゲット文と出力文に対するスパンの一致に対して適合率, 再現率と${\rm F}_{0.5}$値(適合率をより重視する)で評価を行う.
- ERRANT, W&I+LOCNESS:${\rm M}^2$と同じように適合率, 再現率と${\rm F}_{0.5}$で評価を行う.置換,削除や挿入のような編集タイプや前置詞誤りや動詞誤りのような誤りタイプごとのスコアなども評価することができる.スパンの計算方法の違いから${\rm M}^2$はERRANTと比較してシステムを過大評価する傾向にある.
- GLEU, JFLEG:ソース文,ターゲット文と出力文に対してn-gramの一致率を用いて評価する.BLEU(機械翻訳の評価手法)を参考にしている.
その他に参照文を必要としない提案手法としてSOMEなどもある.
データセット構築のコード
W&I+LOCNESSとFCEはwgetによりダウンロードすることが可能である.Lang-8とNUCLEはリクエストが必要であるため,各自申請してdata/m2
に配置する.これらのデータはM2形式で配布されており,Fairseqで取り扱えるようにパラレル形式に変換する必要がある.
1 2 3 4 5 6 7 8 9
| # W&I+LOCNESSとFCEデータをダウンロードし`data/m2`ディレクトリに配置する.Lang-8とNUCLEは適宜リクエストして配置する. M2_DIR=data/m2 PARA_DIR=data/parallel mkdir -p $M2_DIR mkdir -p $PARA_DIR wget https://www.cl.cam.ac.uk/research/nl/bea2019st/data/fce_v2.1.bea19.tar.gz -O - | tar xvf - -C $M2_DIR wget https://www.cl.cam.ac.uk/research/nl/bea2019st/data/wi+locness_v2.1.bea19.tar.gz -O - | tar xvf - -C $M2_DIR # データに対して前処理(1:M2形式からパラレルデータ形式に変換する,2:データを結合する,3:訂正されていない文対を除外する)を行う. ./script/preprocess.sh $M2_DIR $PARA_DIR
|
上記のコマンドによりW&I+LOCNESSの評価データはダウンロードされているが,CoNLL-2014とJFLEGは以下のコマンドでdata
にダウンロードする必要がある.
1 2 3 4 5
| # CoNLL-2014のダウンロードとパラレルデータ形式に変換 wget https://www.comp.nus.edu.sg/~nlp/conll14st/conll14st-test-data.tar.gz -O - | tar xvf - -C data python src/convert_m2_to_parallel.py data/conll14st-test-data/noalt/official-2014.combined.m2 data/conll14st-test-data/noalt/conll2014.src data/conll14st-test-data/noalt/conll2014.trg # JFLEGのダウンロード git clone https://github.com/keisks/jfleg.git data/jfleg
|
FairseqでGECモデルを学習する
train.sh
を使い作成したデータをバイナリーデータにしGECモデルを学習する.ここではGECモデルとしてTransformer-bigを使用する.
train.sh1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| eed=1111 num_operations=8000 subword_model=model/bpe.model cpu_num=`grep -c ^processor /proc/cpuinfo`
FAIRSEQ_DIR=fairseq/fairseq_cli DATA_DIR=data/parallel PROCESSED_DIR=process/$seed MODEL_DIR=model/$seed
mkdir -p $PROCESSED_DIR
# Fairseqに読み込ませるためのバイナリーデータを作成する.
if [ -e $PROCESSED_DIR/bin ]; then echo 既にバイナリーデータは存在している. else echo バイナリデータを作成する.
mkdir -p $PROCESSED_DIR/bin subword-nmt learn-bpe -s $num_operations < $DATA_DIR/train_corrected.trg \ > $PROCESSED_DIR/trg_$num_operations.bpe subword-nmt apply-bpe -c $PROCESSED_DIR/trg_$num_operations.bpe \ < $DATA_DIR/train_corrected.src \ > $PROCESSED_DIR/train.src subword-nmt apply-bpe -c $PROCESSED_DIR/trg_$num_operations.bpe \ < $DATA_DIR/train_corrected.trg \ > $PROCESSED_DIR/train.trg subword-nmt apply-bpe -c $PROCESSED_DIR/trg_$num_operations.bpe \ < $DATA_DIR/dev.src \ > $PROCESSED_DIR/dev.src subword-nmt apply-bpe -c $PROCESSED_DIR/trg_$num_operations.bpe \ < $DATA_DIR/dev.trg \ > $PROCESSED_DIR/dev.trg
python -u $FAIRSEQ_DIR/preprocess.py \ --source-lang src \ --target-lang trg \ --trainpref $PROCESSED_DIR/train \ --validpref $PROCESSED_DIR/dev \ --testpref $PROCESSED_DIR/dev \ --destdir $PROCESSED_DIR/bin \ --workers $cpu_num \ --joined-dictionary \ --tokenizer space fi
# GECモデルの学習
mkdir -p $MODEL_DIR
python -u $FAIRSEQ_DIR/train.py $PROCESSED_DIR/bin \ --save-dir $MODEL_DIR \ --source-lang src \ --target-lang trg \ --log-format simple \ --fp16 \ --max-epoch 30 \ --arch transformer_vaswani_wmt_en_de_big \ --max-tokens 4096 \ --optimizer adam \ --adam-betas '(0.9, 0.98)' \ --lr 0.0005 \ --lr-scheduler inverse_sqrt \ --warmup-updates 4000 \ --warmup-init-lr 1e-07 \ --stop-min-lr 1e-09 \ --dropout 0.3 \ --clip-norm 1.0 \ --weight-decay 0.0 \ --criterion label_smoothed_cross_entropy \ --label-smoothing 0.1 \ --num-workers $cpu_num \ --no-epoch-checkpoints \ --share-all-embeddings \ --seed $seed
|
FairseqでGECモデルを推論し評価する
推論結果を評価するために評価指標を3つeval
ディレクトリに配置する.W&I+LOCNESSはCodaLabで評価するためERRANTは直接使わない.
1 2 3 4 5 6 7
| mkdir eval # M2のダウンロード git clone https://github.com/kanekomasahiro/m2_python3.git eval/m2_python3 # GLEUのダウンロード git clone https://github.com/kanekomasahiro/gec-ranking_python3.git eval/gec-ranking_python3 # 使わないが一応ERRANTのダウンロード git clone https://github.com/chrisjbryant/errant.git eval/errant
|
interactive.sh
を使い学習したモデルで評価データに対して推論を行う.wi,conllまたはjflegのどれを推論するか引数で指定する.そして,CoNLL-2014(評価に時間がかかることがある)とJFLEGに対しては推論結果の評価も行われる.評価結果や出力結果はoutput/$seed
に保存される.
interactive.sh1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| seed=1111 num_operations=8000 beam=5 test_data=$1 # wi conll jfleg
FAIRSEQ_DIR=fairseq/fairseq_cli DATA_DIR=data PROCESSED_DIR=process MODEL_DIR=model/$seed OUTPUT_DIR=output EVAL_DIR=eval
export PYTHONPATH=$FAIRSEQ_DIR
mkdir -p $OUTPUT_DIR/$seed
if [ -e $PROCESSED_DIR/$seed/${test_data}_bin ]; then echo $test_data のバイナリーデータは既に存在する. else echo $test_data のバイナリーデータを作成する. cpu_num=`grep -c ^processor /proc/cpuinfo` if [ $test_data = 'wi' ]; then subword-nmt apply-bpe -c $PROCESSED_DIR/$seed/trg_$num_operations.bpe \ < $DATA_DIR/wi.test.src \ > $PROCESSED_DIR/$seed/$test_data.src elif [ $test_data = 'conll' ]; then subword-nmt apply-bpe -c $PROCESSED_DIR/$seed/trg_$num_operations.bpe \ < $DATA_DIR/conll14st-test-data/noalt/conll2014.src \ > $PROCESSED_DIR/$seed/$test_data.src elif [ $test_data = 'jfleg' ]; then subword-nmt apply-bpe -c $PROCESSED_DIR/$seed/trg_$num_operations.bpe \ < $DATA_DIR/jfleg/test/test.src \ > $PROCESSED_DIR/$seed/$test_data.src fi
cp $PROCESSED_DIR/$seed/$test_data.src $PROCESSED_DIR/$seed/$test_data.trg python -u $FAIRSEQ_DIR/preprocess.py \ --source-lang src \ --target-lang trg \ --trainpref $PROCESSED_DIR/$seed/train \ --validpref $PROCESSED_DIR/$seed/$test_data \ --testpref $PROCESSED_DIR/$seed/$test_data \ --destdir $PROCESSED_DIR/$seed/${test_data}_bin \ --srcdict $PROCESSED_DIR/$seed/bin/dict.src.txt \ --tgtdict $PROCESSED_DIR/$seed/bin/dict.trg.txt \ --workers $cpu_num \ --tokenizer space fi
# GECモデルを用いて評価データの推論 python -u $FAIRSEQ_DIR/interactive.py $PROCESSED_DIR/$seed/bin \ --source-lang src \ --target-lang trg \ --path $MODEL_DIR/checkpoint_best.pt \ --beam $beam \ --nbest $beam \ --no-progress-bar \ --buffer-size 1024 \ --batch-size 32 \ --log-format simple \ --remove-bpe \ < $PROCESSED_DIR/$seed/$test_data.src > $OUTPUT_DIR/$seed/$test_data.nbest.tok
# n-bestから1-bestを抽出する cat $OUTPUT_DIR/$seed/$test_data.nbest.tok | grep "^H" | python -c "import sys; x = sys.stdin.readlines(); x = ' '.join([ x[i] for i in range(len(x)) if (i % ${beam} == 0) ]); print(x)" | cut -f3 > $OUTPUT_DIR/$seed/$test_data.best.tok sed -i '$d' $OUTPUT_DIR/$seed/$test_data.best.tok
# 推論結果を評価する if [ $test_data = 'conll' ]; then CONLL_DIR=data/conll14st-test-data/noalt/ $EVAL_DIR/m2_python3/m2scorer $OUTPUT_DIR/$seed/$test_data.best.tok $CONLL_DIR/official-2014.combined.m2 > $OUTPUT_DIR/$seed/$test_data.eval elif [ $test_data = 'jfleg' ]; then JFLEG_DIR=data/jfleg/test $EVAL_DIR/gec-ranking_python3/scripts/compute_gleu -s $JFLEG_DIR/test.src -r $JFLEG_DIR/test.ref0 $JFLEG_DIR/test.ref1 $JFLEG_DIR/test.ref2 $JFLEG_DIR/test.ref3 -o $OUTPUT_DIR/$seed/$test_data.best.tok -n 4 > $OUTPUT_DIR/$seed/$test_data.eval fi
|
W&I+LOCNESSは評価データのターゲット側が公開されていないため,CodaLabにGECモデルの推論結果を投稿する必要がある.アカウントを作成し,zip
コマンドにより推論結果を圧縮しParticipateのSubmitを押してアップロードすることでERRANTスコアを取得できる.
seedによって1.5ぐらい前後するがスコアとしてはW&I+LOCNESS: 50, CoNLL-2014: 49, JFLEG: 53がでる.
おわり
これはGEC (Grammatical Error Correction) Advent Calendar 2021のための記事である.需要があれば疑似データ生成モデルの動かし方についても説明するかもしれない.