文法誤り訂正の評価ツール ERRANT の使い方 

## ERRANT is 何？
[ERRor ANnotation Toolkit: ERRANT](https://github.com/chrisjbryant/errant)は文法誤り訂正 (Grammatical Error Correction: GEC) システムの出力文に対して、自動で編集箇所とエラータイプを付与し、評価スコアを計算することができるツールです。

2019年にGECの国際コンペである[BEA-2019 Shared Task](https://www.cl.cam.ac.uk/research/nl/bea2019st/)が開催されました。このコンペでは共通のデータセット・評価方法のもとで、参加チームが作成したGECシステムのスコアを競い合いました。このコンペの評価方法として用いられたのがERRANTです。

ERRANTのエラータイプの推定はルールベースで行なっており、Good, Acceptable, Badの3段階で人手評価を行ったところ、95%以上がGood, Acceptableであったと報告されています ([Bryant et al. 2017](https://aclanthology.org/P17-1074/))。

## 文法誤り訂正の評価方法

GECでは、編集箇所単位での適合率(Precision)、再現率(Recall)、および$F_{0.5}$が一般的な評価メトリクスとなっています。$F_{0.5}$は再現率を重視したF値のことです。

これらの値を計算するためには、GECシステムの訂正と、真の結果を以下のように分類した結果のカウントが必要になります。

|  |真の結果が正 | 真の結果が負 |
| -------- | -------- | -------- |
| GECシステムが訂正を行った     | TP| FP |
| GECシステムが訂正を行わなかった | FN | TN |

- TP: GECシステムが訂正を行い、それが真に正しい訂正だった場合の数
- FP: GECシステムが訂正を行い、それが真に正しい訂正ではなかった場合の数
- FN: 訂正すべき箇所に対して、GECシステムが訂正を行わなかった場合の数
- TN: 訂正が不要な箇所に対して、GECシステムが訂正を行わなかった場合の数



これらを使って以下の式で適合率、再現率、$F_{0.5}$を計算します。

```math
\text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}} \\

\text{Recall} = \frac{\text{TP}}{\text{TP} + \text{FN}} \\

F_{0.5} = (1+0.5^2) \cdot \frac{\text{Precision} \cdot \text{Recall}}{(0.5^2 \cdot \text{Precision}) + \text{Recall}}
```

## サンプルデータ
今回はERRANTを試す用に、以下のような訂正を行うことを想定します。

<img width="600" alt="スクリーンショット 2021-12-11 20.31.59.png (766.2 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/2c4bc2bc-0acd-8bb7-f4d7-2c74c2ed307b.png">
※ [COLING2020のGECチュートリアルのスライド](https://github.com/grammatical/coling2020-tutorial/blob/master/slides/GEC_tutorial_2020.pdf)の5ページ目を拝借しました

**原文ファイル**(orig.txt)、**正解文ファイル**(cor.txt)を用意します。

```orig.txt
I think, that everybody deserve privacy, including famous people.
They can barelly breathing with all those photographers around them.
I don't know why people love spying famous people.
And magazines are full of those things.
```

```cor.txt
I think that everybody deserves privacy, including famous people.
They can barely breath with all those photographers around them.
I don't know why people love spying on famous people.
And magazines are full of those things.
```

また、GECシステムの**出力文ファイル**(hyp.txt)も用意します。今回はGECシステムはないので一部を手動で編集しました。

```hyp.txt
I think that everybody deserve privacy, including famous people.
They can barely breathing with all those photographers around them.
I don't know why people love spying on famous people.
And the magazines are full of those things.
```

- 手動で編集した箇所
    - 1文目の不要な「,」を削除 (TP)
    - 2文目の「barelly」を「barely」に修正 (TP)
    - 3文目の「spaying」を「spaying on」に修正 (TP)
    - 4文目に不要な「the」を挿入 (FP)
- その他の間違いは修正しない (FN × 2)

## ERRANTの使い方
まずERRANTをインストールします。

```sh
$ pip3 install -U pip setuptools wheel
$ pip3 install errant
$ python3 -m spacy download en
```

インストールすると、次の3つのコマンドを使用できるようになります。

1. errant_parallel
1. errant_m2
1. errant_compare

以下の手順でコマンドを実行します

- ① errant_parallel コマンドで原文から正解文への編集箇所とエラータイプをM2フォーマットで出力する
- ② errant_parallel コマンドで原文から出力文への編集箇所とエラータイプをM2フォーマットで出力する
- ③ errant_compare コマンドで評価スコアを計算する

なお、errant_m2 コマンドは今回は使用しません。ERRANT以外の方法でM2ファイルが作成されている場合、それをERRANTの形式に統一するときにこのコマンドを使用します。

## ① errant_parallel コマンドで原文から正解文への編集箇所とエラータイプをM2フォーマットで出力する

最初に、次のコマンドを実行します。

```sh
$ errant_parallel -orig orig.txt -cor cor.txt -tok -out ref.m2
```

- `-org`には原文ファイルを指定します
- `-cor`には正解文ファイルを指定します
- `-out`には出力するファイルパスを指定します
- `-tok`を指定するとspaCyを使って文をトークナイズしてくれます
    - 「think,」が「think ,」に分割されます

出力されるファイル(ref.m2)の中身は次のようになります。

```ref.m2
S I think , that everybody deserve privacy , including famous people .
A 2 3|||U:PUNCT||||||REQUIRED|||-NONE-|||0
A 5 6|||R:VERB:SVA|||deserves|||REQUIRED|||-NONE-|||0

S They can barelly breathing with all those photographers around them .
A 2 3|||R:SPELL|||barely|||REQUIRED|||-NONE-|||0
A 3 4|||R:VERB|||breath|||REQUIRED|||-NONE-|||0

S I do n't know why people love spying famous people .
A 8 8|||M:PREP|||on|||REQUIRED|||-NONE-|||0

S And magazines are full of those things .
A -1 -1|||noop|||-NONE-|||REQUIRED|||-NONE-|||0
```

これは原文から正解文までにどのような編集を行ったかを**M2フォーマット**という形式で表しています。M2フォーマットのファイルを**M2ファイル**と呼びます。このコマンドではこのように2文間の編集アノテーションを自動で推定し、M2フォーマットで出力します。

### M2フォーマット
M2フォーマットは[CoNLL-2013 Shared Task](https://www.comp.nus.edu.sg/~nlp/conll13st.html)以降、GECにおいて一般的なアノテーショファイルのフォーマットになっています。

```
S This are a sentence .
A 1 2|||R:VERB:SVA|||is|||-REQUIRED-|||NONE|||0
A 3 3|||M:ADJ|||good|||-REQUIRED-|||NONE|||0
A 1 2|||R:VERB:SVA|||is|||-REQUIRED-|||NONE|||1
A -1 -1|||noop|||-NONE-|||REQUIRED|||-NONE-|||2
```

- Sで始まる行は元の文を表す
- Aで始まる行は編集のアノテーションを表す
- アノテーション行は以下のようなフォマットに従う
    - `A <開始トークン位置> <終了トークン位置>|||<エラータイプ>|||<正解トークン>|||<意味はないが形式上必要>|||<意味はないが形式上必要>|||<アノテーターID>`
- `noop`は変更がないことを表す特別なラベル
- もし何も編集がない場合、以下のようなnoopの1行だけ出力される
    - ```
        A -1 -1|||noop|||-NONE-|||REQUIRED|||-NONE-|||0
        ```

### エラータイプ
例えば「M:ADJ」は「形容詞の不足」を表します。「operation:code」のように表されます。

operationにはM、R、Uの3種類があります。

- M: Missing (不足)
- R: Replacement (置換)
- U: Unnecessary (不要)

エラータイプのcodeは、論文に主な25種類が紹介されています　([Bryant et al. 2017](https://aclanthology.org/P17-1074/)) 。

<img width="600" alt="スクリーンショット 2021-12-12 2.15.56.png (496.4 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/696108df-2c82-857f-96bf-e0b110a19666.png">


ERRANTはエラータイプをルールベースで分類しているので、以下の特徴があります。

- 特定のデータセットに依存しない
- 訓練データやアノテーションラベルデータなどが不要
- 機械学習と違い、分類のロジックが明確


## ② errant_parallel コマンドで原文から出力文への編集箇所とエラータイプをM2フォーマットで出力する
次に、errant_parallelコマンドの`-cor`を出力文ファイルに入れ替えて実行します。

```sh
$ errant_parallel -orig orig.txt -cor hyp.txt -tok -out hyp.m2
```

出力されるM2ファイル(hyp.m2)の中身は次のようになります。

```hyp.m2
S I think , that everybody deserve privacy , including famous people .
A 2 3|||U:PUNCT||||||REQUIRED|||-NONE-|||0

S They can barelly breathing with all those photographers around them .
A 2 3|||R:SPELL|||barely|||REQUIRED|||-NONE-|||0

S I do n't know why people love spying famous people .
A 8 8|||M:PREP|||on|||REQUIRED|||-NONE-|||0

S And magazines are full of those things .
A 1 1|||M:DET|||the|||REQUIRED|||-NONE-|||0
```

これは原文から出力文までにどのような編集を行ったのかを表しています。

## ③ errant_compare コマンドで評価スコアを計算する
最後に、次のコマンドを実行することで、評価スコアが表示されます。

```sh
$ errant_compare -hyp hyp.m2 -ref ref.m2

=========== Span-Based Correction ============
TP      FP      FN      Prec    Rec     F0.5
3       1       2       0.75    0.6     0.7143
==============================================
```

- `-hyp`には、2で作成した原文-出力文のM2ファイルを指定します
- `-ref`には、1で作成した原文-正解文のM2ファイルを指定します

ERRANTは2つのM2ファイルからTP、FP、FNをカウントし、適合率、再現率、$F_{0.5}$を計算します。

`-v`オプションを加えることで詳細を確認できます。

```sh
$ errant_compare -hyp hyp.m2 -ref ref.m2 -v

----------------------------------------
SENTENCE 0 - HYP 0 - REF 0
HYPOTHESIS EDITS : [(2, 3, '')]
REFERENCE EDITS  : [(2, 3, ''), (5, 6, 'deserves')]
Local TP/FP/FN   : 1 0 1
Local P/R/F0.5  : 1.0 0.5 0.8333
Global TP/FP/FN  : 1 0 1
Global P/R/F0.5  : 1.0 0.5 0.8333
----------------------------------------
^^ HYP 0, REF 0 chosen for sentence 0
----------------------------------------
SENTENCE 1 - HYP 0 - REF 0
HYPOTHESIS EDITS : [(2, 3, 'barely')]
REFERENCE EDITS  : [(2, 3, 'barely'), (3, 4, 'breath')]
Local TP/FP/FN   : 1 0 1
Local P/R/F0.5  : 1.0 0.5 0.8333
Global TP/FP/FN  : 2 0 2
Global P/R/F0.5  : 1.0 0.5 0.8333
----------------------------------------
^^ HYP 0, REF 0 chosen for sentence 1
----------------------------------------
SENTENCE 2 - HYP 0 - REF 0
HYPOTHESIS EDITS : [(8, 8, 'on')]
REFERENCE EDITS  : [(8, 8, 'on')]
Local TP/FP/FN   : 1 0 0
Local P/R/F0.5  : 1.0 1.0 1.0
Global TP/FP/FN  : 3 0 2
Global P/R/F0.5  : 1.0 0.6 0.8824
----------------------------------------
^^ HYP 0, REF 0 chosen for sentence 2
----------------------------------------
SENTENCE 3 - HYP 0 - REF 0
HYPOTHESIS EDITS : [(1, 1, 'the')]
REFERENCE EDITS  : []
Local TP/FP/FN   : 0 1 0
Local P/R/F0.5  : 0.0 1.0 0.0
Global TP/FP/FN  : 3 1 2
Global P/R/F0.5  : 0.75 0.6 0.7143
----------------------------------------
^^ HYP 0, REF 0 chosen for sentence 3

=========== Span-Based Correction ============
TP      FP      FN      Prec    Rec     F0.5
3       1       2       0.75    0.6     0.7143
==============================================
```

`-cat 1`オプションを加えることでoperation別にスコアを確認できます。

```sh
$ errant_compare -hyp hyp.m2 -ref ref.m2 -cat 1

===================== Span-Based Correction ======================
Category       TP       FP       FN       P        R        F0.5
M              1        1        0        0.5      1.0      0.5556
R              1        0        2        1.0      0.3333   0.7143
U              1        0        0        1.0      1.0      1.0

=========== Span-Based Correction ============
TP      FP      FN      Prec    Rec     F0.5
3       1       2       0.75    0.6     0.7143
==============================================
```

`-cat 2`オプションを加えることでエラータイプのcode別にスコアを確認できます。

```sh
$  errant_compare -hyp hyp.m2 -ref ref.m2 -cat 2

===================== Span-Based Correction ======================
Category       TP       FP       FN       P        R        F0.5
DET            0        1        0        0.0      1.0      0.0
PREP           1        0        0        1.0      1.0      1.0
PUNCT          1        0        0        1.0      1.0      1.0
SPELL          1        0        0        1.0      1.0      1.0
VERB           0        0        1        1.0      0.0      0.0
VERB:SVA       0        0        1        1.0      0.0      0.0

=========== Span-Based Correction ============
TP      FP      FN      Prec    Rec     F0.5
3       1       2       0.75    0.6     0.7143
==============================================
```

`-cat 3`オプションを加えることでoperation:code別にスコアを確認できます。

```sh
$  errant_compare -hyp hyp.m2 -ref ref.m2 -cat 3

===================== Span-Based Correction ======================
Category       TP       FP       FN       P        R        F0.5
M:DET          0        1        0        0.0      1.0      0.0
M:PREP         1        0        0        1.0      1.0      1.0
R:SPELL        1        0        0        1.0      1.0      1.0
R:VERB         0        0        1        1.0      0.0      0.0
R:VERB:SVA     0        0        1        1.0      0.0      0.0
U:PUNCT        1        0        0        1.0      1.0      1.0

=========== Span-Based Correction ============
TP      FP      FN      Prec    Rec     F0.5
3       1       2       0.75    0.6     0.7143
==============================================
```

その他のオプションもあるので、helpをご覧ください。

```sh
$ errant_compare -h
```

## 日本語で使ったらどうなる？
ERRANTは英語向けのツールですが、日本語文を入力したらどうなるのか試します。次のようなサンプルデータを使用します。

<img width="600" alt="スクリーンショット 2021-12-12 13.01.01.png (557.1 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145075/d311891a-164e-c7e9-c6e0-c19f7471868f.png">
※ [こちらの論文](https://www.anlp.jp/proceedings/annual_meeting/2020/pdf_dir/F2-3.pdf)から拝借しました


```orig.txt
だから、たばこの吸う人がたくさんいる。
人間の健康ようにたばこを吸わなくてください。
個人の権利はもし他人の権利を悪く影響したら、禁止られるべきです。
でも、人はその一人ばかりこの地球ですむのはない、
```

```cor.txt
だから、たばこを吸う人がたくさんいる。
人間の健康のためにたばこを吸わないでください。
個人の権利はもし他人の権利に悪く影響したら、禁止されるべきです。
でも、人は一人だけでこの地球にすんでいるのではありません。
```

```hyp.txt
だから、たばこを吸う人がたくさんいる。
人間の健康のためにたばこを吸わないでください。
個人の権利はもし他人の権利に悪く影響したら、禁止されるべきです。
でも、人はその一人だけこの地球に住むことはない、
```

英語の場合はerrant_parallelのオプション`-tok`でトークナイズできましたが、日本語には対応していないため事前に分かち書きしておきます。今回はmecab unidicで分かち書きします。

```sh
$ mecab -d path/to/unidic -Owakati orig.txt > orig.tok.txt
$ mecab -d path/to/unidic -Owakati cor.txt > cor.tok.txt
$ mecab -d path/to/unidic -Owakati hyp.txt > hyp.tok.txt
```

```orig.tok.txt
だ から 、 たばこ の 吸う 人 が たくさん いる 。 
人間 の 健康 よう に たばこ を 吸わ なく て ください 。 
個人 の 権利 は もし 他人 の 権利 を 悪く 影響 し たら 、 禁止 られる べき です 。 
で も 、 人 は その 一人 ばかり この 地球 で すむ の は ない 、 
```

```cor.tok.txt
だ から 、 たばこ を 吸う 人 が たくさん いる 。 
人間 の 健康 の ため に たばこ を 吸わ ない で ください 。 
個人 の 権利 は もし 他人 の 権利 に 悪く 影響 し たら 、 禁止 さ れる べき です 。 
で も 、 人 は 一人 だけ で この 地球 に すん で いる の で は あり ませ ん 。 
```

```hyp.tok.txt
だ から 、 たばこ を 吸う 人 が たくさん いる 。 
人間 の 健康 の ため に たばこ を 吸わ ない で ください 。 
個人 の 権利 は もし 他人 の 権利 に 悪く 影響 し たら 、 禁止 さ れる べき です 。 
で も 、 人 は その 一人 だけ この 地球 に 住む こと は ない 、 
```


① errant_parallel コマンドで原文から正解文への編集箇所とエラータイプをM2フォーマットで出力する

```sh
$ errant_parallel -orig orig.tok.txt -cor cor.tok.txt -out ref.m2
```

```ref.m2
S だ から 、 たばこ の 吸う 人 が たくさん いる 。
A 4 5|||R:NOUN|||を|||REQUIRED|||-NONE-|||0

S 人間 の 健康 よう に たばこ を 吸わ なく て ください 。
A 3 3|||M:NOUN|||の|||REQUIRED|||-NONE-|||0
A 3 4|||R:PREP|||ため|||REQUIRED|||-NONE-|||0
A 8 9|||R:SPELL|||ない|||REQUIRED|||-NONE-|||0
A 9 10|||R:DET|||で|||REQUIRED|||-NONE-|||0

S 個人 の 権利 は もし 他人 の 権利 を 悪く 影響 し たら 、 禁止 られる べき です 。
A 8 9|||R:NOUN|||に|||REQUIRED|||-NONE-|||0
A 15 15|||M:NOUN|||さ|||REQUIRED|||-NONE-|||0
A 15 16|||R:SPELL|||れる|||REQUIRED|||-NONE-|||0

S で も 、 人 は その 一人 ばかり この 地球 で すむ の は ない 、
A 5 6|||U:NOUN||||||REQUIRED|||-NONE-|||0
A 7 7|||M:ADV|||だけ|||REQUIRED|||-NONE-|||0
A 7 8|||R:NOUN|||で|||REQUIRED|||-NONE-|||0
A 10 10|||M:NOUN|||に すん|||REQUIRED|||-NONE-|||0
A 11 12|||R:NOUN|||いる|||REQUIRED|||-NONE-|||0
A 13 13|||M:NOUN|||で|||REQUIRED|||-NONE-|||0
A 14 16|||R:NOUN|||あり ませ ん|||REQUIRED|||-NONE-|||0
A 16 16|||M:PUNCT|||。|||REQUIRED|||-NONE-|||0
```

② errant_parallel コマンドで原文から出力文への編集箇所とエラータイプをM2フォーマットで出力する

```sh
$ errant_parallel -orig orig.tok.txt -cor hyp.tok.txt -out hyp.m2
```

```hyp.m2
S だ から 、 たばこ の 吸う 人 が たくさん いる 。
A 4 5|||R:NOUN|||を|||REQUIRED|||-NONE-|||0

S 人間 の 健康 よう に たばこ を 吸わ なく て ください 。
A 3 3|||M:NOUN|||の|||REQUIRED|||-NONE-|||0
A 3 4|||R:PREP|||ため|||REQUIRED|||-NONE-|||0
A 8 9|||R:SPELL|||ない|||REQUIRED|||-NONE-|||0
A 9 10|||R:DET|||で|||REQUIRED|||-NONE-|||0

S 個人 の 権利 は もし 他人 の 権利 を 悪く 影響 し たら 、 禁止 られる べき です 。
A 8 9|||R:NOUN|||に|||REQUIRED|||-NONE-|||0
A 15 15|||M:NOUN|||さ|||REQUIRED|||-NONE-|||0
A 15 16|||R:SPELL|||れる|||REQUIRED|||-NONE-|||0

S で も 、 人 は その 一人 ばかり この 地球 で すむ の は ない 、
A 7 8|||R:NOUN|||だけ|||REQUIRED|||-NONE-|||0
A 10 11|||R:NOUN|||に|||REQUIRED|||-NONE-|||0
A 11 12|||R:SPELL|||住む|||REQUIRED|||-NONE-|||0
A 12 13|||R:DET|||こと|||REQUIRED|||-NONE-|||0
```

③ errant_compare コマンドで評価スコアを計算する

```sh
$ errant_compare -hyp hyp.m2 -ref ref.m2 -v

----------------------------------------
SENTENCE 0 - HYP 0 - REF 0
HYPOTHESIS EDITS : [(4, 5, 'を')]
REFERENCE EDITS  : [(4, 5, 'を')]
Local TP/FP/FN   : 1 0 0
Local P/R/F0.5  : 1.0 1.0 1.0
Global TP/FP/FN  : 1 0 0
Global P/R/F0.5  : 1.0 1.0 1.0
----------------------------------------
^^ HYP 0, REF 0 chosen for sentence 0
----------------------------------------
SENTENCE 1 - HYP 0 - REF 0
HYPOTHESIS EDITS : [(3, 3, 'の'), (3, 4, 'ため'), (8, 9, 'ない'), (9, 10, 'で')]
REFERENCE EDITS  : [(3, 3, 'の'), (3, 4, 'ため'), (8, 9, 'ない'), (9, 10, 'で')]
Local TP/FP/FN   : 4 0 0
Local P/R/F0.5  : 1.0 1.0 1.0
Global TP/FP/FN  : 5 0 0
Global P/R/F0.5  : 1.0 1.0 1.0
----------------------------------------
^^ HYP 0, REF 0 chosen for sentence 1
----------------------------------------
SENTENCE 2 - HYP 0 - REF 0
HYPOTHESIS EDITS : [(8, 9, 'に'), (15, 15, 'さ'), (15, 16, 'れる')]
REFERENCE EDITS  : [(8, 9, 'に'), (15, 15, 'さ'), (15, 16, 'れる')]
Local TP/FP/FN   : 3 0 0
Local P/R/F0.5  : 1.0 1.0 1.0
Global TP/FP/FN  : 8 0 0
Global P/R/F0.5  : 1.0 1.0 1.0
----------------------------------------
^^ HYP 0, REF 0 chosen for sentence 2
----------------------------------------
SENTENCE 3 - HYP 0 - REF 0
HYPOTHESIS EDITS : [(7, 8, 'だけ'), (10, 11, 'に'), (11, 12, '住む'), (12, 13, 'こと')]
REFERENCE EDITS  : [(5, 6, ''), (7, 7, 'だけ'), (7, 8, 'で'), (10, 10, 'に すん'), (11, 12, 'いる'), (13, 13, 'で'), (14, 16, 'あり ませ ん'), (16, 16, '。')]
Local TP/FP/FN   : 0 4 8
Local P/R/F0.5  : 0.0 0.0 0.0
Global TP/FP/FN  : 8 4 8
Global P/R/F0.5  : 0.6667 0.5 0.625
----------------------------------------
^^ HYP 0, REF 0 chosen for sentence 3

=========== Span-Based Correction ============
TP      FP      FN      Prec    Rec     F0.5
8       4       8       0.6667  0.5     0.625
==============================================
```

1~3文目は正しい訂正を行なっているため適合率、再現率ともに100%です。ですが4文目でFP、FNが多くカウントされているため、全体的なスコアが低くなっています。文節単位の訂正の場合、対象のトークン数が多くなるためスコアに与える影響が大きいということかと思います。

英語と違い、日本語ではエラータイプのルールベースが適用されないため、付与されるエラータイプは間違ったものになります。一方で編集箇所の検出やoperationの判別は日本語でも機能します。日本語でもエラータイプ別に精度を確認できるようになると嬉しいですね。


## まとめ
長くなりましたが、文法誤り訂正の評価ツール ERRANT の使い方を紹介しました。テキストファイルがあれば、コマンドだけでM2ファイルに変換してくれて、評価スコアが計算でき、さらにエラータイプ別の精度まで確認できるのはとても便利だと思います！

ERRANTの詳細は公式GitHubをご覧ください。
https://github.com/chrisjbryant/errant