英語の文法誤り訂正に入門しつつFairseqでモデルを学習・推論して評価する

英語の文法誤り訂正に入門しつつFairseqでモデルを学習・推論して評価する

💡 概要

  • 評価・推論するための英語文法誤り訂正について最低限の知識が得られる
  • Fairseqを用いてTransformerの文法誤り訂正モデルを動かす
  • 文法誤り訂正モデルの推論結果を評価する

はじめに

2021年12月現在,著者の金子東工大岡崎研で研究員をしている.学生の頃に文法誤り訂正 (Grammatical Error Correction; GEC) に関する研究をやっていた関係で,研究室の学生と一緒にGECの研究をやっている(現在2人の学生がGECを研究中!).その際,これを見ておけばGECモデルを最低限動かせるようになるというようなものがあれば良いなと思ったので,研究室に配属されてGECの研究を始めようとしている学生がお手軽に国際会議に投稿されているような設定でGECモデルを動かせるようになってくれれば嬉しいという記事を書く.

コードは全てのこのリポジトリにあるのでそれを使う.

GECデータセットの準備

英語GECにおけるデータセット一覧

学習データと開発データについて

一般的にBEA shared taskで用いられたデータセットが学習データと開発データとして広く使われている.このデータセットではFCE, Lang-8, NUCLEW&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-2014JFLEG [記事]が使われている.W&I+LOCNESSは学習者の習熟度に関する情報が付与されている.W&I+LOCNESSやCoNLL2014と異なりJFLEGは最低限の文法的な訂正だけでなく,流暢になるように訂正が行われている.その他の評価データとしてウェブドメインのGMEGCWEBなどがある.このブログではを用いる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.sh
1
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.sh
1
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のための記事である.需要があれば疑似データ生成モデルの動かし方についても説明するかもしれない.