日本語文法誤り訂正の入力に最適な単位は？

[フューチャー 2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/future2)の 15日目の記事です。
今年6月に転職してきて半年経ったのでその辺の話でも書こうかと思ったけど、結局ずっとやってきた文法誤り訂正の話を書きます。（フューチャーの仕事にはあまり関係なく、客員をやっている理研AIPの内容です。）

# はじめに
英語の文法誤り訂正 (Grammatical Error Correction; GEC)の研究は盛んに行なわれており、その訂正性能も年々上がっています。一方で残念ながら英語以外の言語での研究はほとんど行なわれていないのが実情です。そこで今回は我々の母語である日本語を対象とした文法誤り訂正の話をしていきたいと思います。

文法誤り訂正に限らずの話ですが、英語のような言語では空白で単語が区切られていますが、日本語の場合は空白で単語は区切られていません。そのため、日本語を対象とした場合、どのような単位でシステムに入力するべきかというのはなかなか難しい問題です [^1]。本記事では、実際に文法誤り訂正システムを動かして、分割の単位が訂正性能にどのよう影響を与えるのかを見ていきたいと思います。

# 文の分割単位の種類
今回のブログでは、
1. 単語分割
2. 文字分割
3. Sentencepieceによる分割
を用いて比較実験します。

自分が2011年にやった日本語を対象とした文法誤り訂正の研究[^2][^3]では、単語単位分割だけではなく文字に分割して訂正する手法を提案しました。文法誤り訂正では機械翻訳と同様、入力と出力ともに文字列であり、上記の研究では、入力と出力の分割単位を「単語-単語」、「文字-文字」、「文字-単語」にして実験しました。論文中の例ですが、「文字-文字」、「文字-単語」の入力・出力の分割例は以下の感じです。
<img width="453" alt="スクリーンショット 2019-12-15 11.43.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440796/8e038110-ed8e-a6b7-09fd-9a2c83b4c99b.png">
実験結果としては、評価データの母語によって異なる結果になっていて、英語母語話者の誤りを訂正する場合は「文字-文字」が良くて、中国語母語話者の誤りを訂正する場合は「単語-単語」が良いという結果でした。これは、英語母語話者の場合、ひらがなで書くことが多く長音も抜けたりしやすく従来の形態素解析器では分割に失敗してしまうため、文字分割の方がロバストに動くからです。一方で中国語母語話者は漢字を使って書けるため、ひらがなと漢字の境目で単語を分割できるため、従来の形態素解析器でも比較的きれいに単語分割が可能なため「単語-単語」でもうまく訂正ができます。

今回は時間的に間に合わなかったので、入力と出力で分割単位を合わせたものだけで比較します。分割単位は上記研究でも行なわれていた形態素解析器を用いた単語分割、文字分割に加えて[Sentencepiece](https://github.com/google/sentencepiece)による分割を比べます。SentencepieceはGoogleの工藤拓さんによって開発されたニューラル言語処理向けのトークナイザです。サブワードは単語を部分文字列に分割するものでしたが、Sentencepieceは文を直接分割します。人の直感とは合わない結果が出てくることもありますが、ニューラルネットワークの中に入れるのであればこちらの方が理にはかなっていそうです[^4]。今回はSentencepieceでの語彙サイズを4000、8000、16000に変えて比較もしてみます。

# 前準備
## 使用するデータ
モデルの学習には[NAIST Lang-8 Corpora](https://sites.google.com/site/naistlang8corpora/)を用います。こちらのコーパスは商用利用には使用できませんが、研究には使用できます。コーパスから誤り文-訂正文のパラレルデータを作るためのscriptは[こちら](https://github.com/tomo-wb/Lang8-NAIST-extractor)で公開しています。評価用のデータには[作文対訳DB](https://db3.ninjal.ac.jp/contr-db/)というのを使用していますが、現在は手に入れるのは難しいかもしれません。

## 文の分割
単語分割にはMeCabを使用し、辞書はIPADic使用します。
書くほどでもありませんが、pythonでこんな感じで書いて処理してます。

```MeCab_Tok.py
import MeCab
m = MeCab.Tagger("-Owakati")

def tok(text):
    return m.parse(text).rstrip()
```

文字分割も以下のように、stringをリストに突っ込んでスペースでjoinして作ってます。

```Char_Tok.py

def tok(text):
    return " ".join(list(text))
```

Sentencepieceのモデル学習と分割も以下のようなコードで。

```Sentencepiece_Train.py
import sentencepiece as spm

sp_args = ["--input=" + data_set, "--model_prefix=" + model_nm, "--vocab_size=" + vcb_size]
sp_arg = " ".join(sp_args)
sp = spm.SentencePieceProcessor()
spm.SentencePieceTrainer.Train(sp_arg)
```

```Sentencepiece_Tok.py
import sentencepiece as spm
sp = spm.SentencePieceProcessor()
sp.Load(sp_model)

def tok(text):
    return sp.EncodeAsPieces(text)
```


## 文法誤り訂正器
今回文法誤り訂正のために使ったのは[Fairseq](https://github.com/pytorch/fairseq)というFacebook AI Researchが作っている機械翻訳などで使われているSequence-to-Sequence (seq2seq)のツールです。Fairseqにはいろんなseq2seqのアルゴリズムが実装されており、RNN、CNN、Transformerなども使えます。また、最新の機械翻訳アルゴリズムもどんどん追加されています。

今回は文法誤り訂正の研究でもよく使われており、高い性能を出しているTransformerを使って実験をします。以下、簡単に学習から訂正するまでのコマンド例を書いておきます。
fairseqで学習するためにはまず学習データや開発データに対して前処理をかける必要があります。`/home/hogehoge/work/data`にtrain.src、train.trg、dev.src、dev.trgといったデータがある時は以下のようになります。

```preprocess.sh
FAIRSEQ_DIR=/home/hogehoge/fairseq
DIR=/home/hogehoge/work/data

python $FAIRSEQ_DIR/preprocess.py --source-lang src --target-lang trg --trainpref $DIR/train --validpref $DIR/dev --testpref  $DIR/dev --destdir $DIR/bin 
```

その後、上の前処理で作ったデータを使ってモデルの学習を行います。

```train.sh
FAIRSEQ_DIR=/home/hogehoge/fairseq
OUT_DIR=/home/hogehoge/work/model
SEED=1111
DATA_BIN_DIR=/home/hogehoge/work/data/bin

python $FAIRSEQ_DIR/train.py \
    --source-lang src \
    --target-lang trg \
    --save-dir ${OUT_DIR} \
    --max-tokens 4000 \
    --seed ${SEED} \
    --arch transformer_vaswani_wmt_en_de_big \
    --optimizer adam \
    --lr 0.0005 \
    --dropout 0.3 \
    --min-lr '1e-09' \
    --lr-scheduler inverse_sqrt \
    --warmup-updates 4000 \
    --warmup-init-lr '1e-07' \
    --adam-betas '(0.9, 0.98)' \
    --max-epoch 100 \
    --clip-norm 1.0 \
    --criterion label_smoothed_cross_entropy \
    --label-smoothing 0.1 \
    --log-format simple \
    --log-interval 100 \
    --no-epoch-checkpoints \
    ${DATA_BIN_DIR} 
```

最後に上で学習したモデルを使って訂正します。

```correct.sh
FAIRSEQ_DIR=/home/hogehoge/fairseq
Model=/home/hogehoge/work/model/checkpoint_last.pt
Input=/home/hogehoge/data/test.src

python $FAIRSEQ_DIR/interactive.py --path $Model --beam 12 $data_bin < $Input > output.txt
```

## 評価方法
今回のブログでは、GLEU[^5]という評価尺度を使用して評価します。文法誤り訂正の研究でよく使われている評価尺度はM2 Scoreというものなのですが、公開されているscriptを使うためにはデータを特別な形に整形する必要があり、少し手間がかかります。そのため今回は1文1行の形式になっていれば使えるGLEUのツールを使って評価します。~~GLEUのツールは[こちら](https://github.com/cnap/gec-ranking)。python2で書かれているため不便です。~~
python3で動くように修正してくれているのがありました[(コード)](https://github.com/borgr/gec-ranking)。

機械翻訳の研究を知っている人であれば、GLEUと聞いてピンとくるかもしれませんが、この評価尺度は機械翻訳で使われているBLEUという評価尺度と似ています。翻訳の場合は、正解文とシステムの出力だけを比べて評価しますが、GLEUでは正解文とシステムの出力に加えて、原文も考慮して評価します。詳しく知りたい人は論文を見てください。

また、英語の場合では単語単位で訂正できているかの評価をしますが、今回は文字単位でどの程度訂正できているかを評価しました。これは日本語の場合、最初の方に書いたように形態素解析器での単語分割に失敗する可能性があり、単語単位では評価も正しくできない可能性があるからです。

# 実験結果
以下に実験結果を示します。各値はseedを変えて4回回した場合の平均値です。ちなみに元々の誤り文に対してGLEUをかけて性能を測った場合の数値は0.587でした。

| 1 | 2 |
|:-:|:-:|
| MeCab | 0.579 |
| Character  | 0.608 |
| Sentencepiece (4000)  | 0.617 |
| Sentencepiece (8000)  | 0.616 |
| Sentencepiece (16000)  | 0.617 |

MeCabを使って単語分割した場合は、訂正をかける前よりGLEUの値が悪くなる結果となりました。誤りによって単語分割に失敗していることが影響しているからでしょうか。Characterに分割することによって、訂正をかける前よりもGLEUが0.03上昇しており訂正ができていることがわかります。またSentencepieceを使うことでCharacter分割よりも0.01上昇しました。Sentencepieceだと低頻度語は文字と同じように分割され、高頻度のものは固まりとなるため誤りには頑健になり、よく出てくるような表現もちゃんと捉えられるようになったと考えます。語彙数を変えた時には、性能に差が出なかったのも個人的には面白い結果でした。これはもう少し考察が必要です。
ひとまず今回実験した中では、seq2seqを用いた日本語文法誤り訂正にはSentencepieceを使うのが良さそうという結論です。もうちょっと色々試しみたい。

# おわりに
内容的にはかなりニッチでしたがいかがでしたでしょうか？フューチャー Strategic AI Group (SAIG)では自然言語処理の案件を扱っております。中途でも新卒でも自然言語処理やってたよっていう人でフューチャーが気になるという人がいれば是非一度ご連絡くださいー:grinning: 会社の紹介や会社案内、軽くご飯（飲み）に行ったりとかできると思います（多分）。


[^1]: 英語の場合も最近は低頻度語に対応するために、サブワード(例えばByte Pair Encoding (BPE)など)を使ってたりします。
[^2]: Tomoya Mizumoto, Mamoru Komachi, Masaaki Nagata and Yuji Matsumoto. Mining Revision Log of Language Learning SNS for Automated Japanese Error Correction of Second Language Learners. In Proceedings of the International Joint Conference on Natural Language Processing (IJCNLP2011), pp.147-155, Chiang Mai, Thailand, November 2011. 
[^3]: 水本智也, 小町守, 永田昌明, 松本裕治. 日本語学習者の作文自動誤り訂正のための語学学習SNSの添削ログからの知識獲得. 人工知能学会論文誌, Vol.28, No.5, pp.420-432, July 2013. 
[^4]: 人間が見て判断したい場合、解釈性とかを考えたい場合は少し不向きかもしれません。
[^5]: Courtney Napoles, Keisuke Sakaguchi, Matt Post, Joel Tetreault. Ground Truth for Grammatical Error Correction Metrics. In Proceedings of the 53rd Annual Meeting of the Association for Computational Linguistics and the 7th International Joint Conference on Natural Language Processing (ACL-IJCNLP2015), pp.588-593, Beijing, China, July 2015.