MeCabの解析結果をそのままMeCabの学習に使ってエラーが出る原因と対処(UniDicの場合)

MeCab の解析結果をそのまま MeCab の学習（mecab-cost-train）に使おうとして下のようになってしまうという "あるある" の原因と解決法。
![avn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2361891/4290ef99-dd11-5272-6285-c4d5c1e03ff6.png)

上の図だと、
* 解析結果を学習に使うということは CSV に登録されているはずなのに、adding virtual node が大量発生
* 解析結果が未知語（処理の出力）になった行で rewrite.def の記載のパターンとマッチせずにエラー出して終了（↓）
```
feature_index.cpp(278) [rewrite_.rewrite2(path->rnode->feature, &ufeature2, &lfeature2, &rfeature2)]  cannot rewrite pattern: 補助記号,一般,,,,
```

adding virtual node の説明はこれ（↓）

https://taku910.github.io/mecab/learn.html

> adding virtual node: 未知語処理を行なっても処理できなかった 形態素で, 学習の際便宜的に追加される形態素です. 

adding virtual node 起きても、別に学習は動くのだけど。virtual node 作るたびにメモリガンガン消費して、そこそこ大きいデータで学習しようとしたらすぐメモリ足りなくなるので、ちゃんと辞書に載ってる（未知語でない）なら、起きないのがベスト。

原因は単に MeCab の出力結果と学習時の CSV がマッチしていないだけ、なのだけど、
[前回](https://qiita.com/TeruakiOka/items/92df9d4c144ab3fbfe61)のように、ミニデータ作ってちゃんと確認してみる。

# 使うデータ

[前回](https://qiita.com/TeruakiOka/items/92df9d4c144ab3fbfe61)作った[ミニデータ](https://github.com/TeruakiOka/mecab-mini-check)を再利用する。

# adding virtual node をなくす

ミニデータの final に入ってる辞書は「車で待つ」「くるまで待つ」くらいしか解析できないけれど、まずはそれを実行する。

解析するテキストを用意。
```raw_text.txt
車で待つ
くるまで待つ
```

前回の作った final はバイナリ化していなかったのでディレクトリに入って mecab-dict-index でバイナリ化。
```
$ cd final/

final$ mecab-dict-index
./pos-id.def is not found. minimum setting is used
reading ./unk.def ... 35
emitting double-array: 100% |###########################################|
./pos-id.def is not found. minimum setting is used
reading ./mini_lex.csv ... 6
emitting double-array: 100% |###########################################|
reading ./matrix.def ... 15x16
emitting matrix      : 100% |###########################################|

done!

final$ cd ../
```

この final 使って raw_text.txt を解析。

```
$ mecab -d final raw_text.txt > mecab_output_corpus.mecab
```

MeCab の -d オプションで final を辞書指定している。

```
$ cat mecab_output_corpus.mecab
車      名詞,普通名詞,一般,,,,クルマ,車,車,クルマ,車,クルマ,和,"ク濁","基本形","","","","",体,クルマ,クルマ,クルマ,クル マ,"0","C2","",2895297651024384,10533
で      助詞,接続助詞,,,,,テ,て,で,デ,で,デ,和,"","","","","","",接助,デ,デ,デ,デ,"","動詞%F1,形容詞%F2@-1","",6837330270888448,24874
待つ    動詞,一般,,,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,"","","","","","",用,マツ,マツ,マツ,マツ,"1","C1","",9848884062986923,35830
EOS
くるま  名詞,普通名詞,一般,,,,クルマ,車,くるま,クルマ,くるま,クルマ,和,"ク濁","基本形","","","","",体,クルマ,クルマ,クルマ,クルマ,"0","C2","",2895297718133248,10533
で      助詞,接続助詞,,,,,テ,て,で,デ,で,デ,和,"","","","","","",接助,デ,デ,デ,デ,"","動詞%F1,形容詞%F2@-1","",6837330270888448,24874
待つ    動詞,一般,,,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,"","","","","","",用,マツ,マツ,マツ,マツ,"1","C1","",9848884062986923,35830
EOS
```

解析はできたので、早速この結果を学習に使ってみる。

```
$ cd seed/
seed$ mecab-cost-train ../mecab_output_corpus.mecab model
reading corpus ...adding virtual node: 名詞,普通名詞,一般,,,,クルマ,車,車,クルマ,車,クルマ,和,"ク濁","基本形","","","","",体,クルマ,クルマ,クルマ,クルマ,"0","C2","",2895297651024384,10533
adding virtual node: 助詞,接続助詞,,,,,テ,て,で,デ,で,デ,和,"","","","","","",接助,デ,デ,デ,デ,"","動詞%F1,形容詞%F2@-1","",6837330270888448,24874
adding virtual node: 動詞,一般,,,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,"","","","","","",用,マツ,マツ, マツ,マツ,"1","C1","",9848884062986923,35830
adding virtual node: 名詞,普通名詞,一般,,,,クルマ,車,くるま,クルマ,くるま,クルマ,和,"ク濁","基本形","","","","",体,クル マ,クルマ,クルマ,クルマ,"0","C2","",2895297718133248,10533
adding virtual node: 助詞,接続助詞,,,,,テ,て,で,デ,で,デ,和,"","","","","","",接助,デ,デ,デ,デ,"","動詞%F1,形容詞%F2@-1","",6837330270888448,24874
adding virtual node: 動詞,一般,,,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,"","","","","","",用,マツ,マツ, マツ,マツ,"1","C1","",9848884062986923,35830

Number of sentences: 2
Number of features:  52
eta:                 0.00005
freq:                1
eval-size:           10
unk-eval-size:       4
threads:             1
charset:             UTF-8
C(sigma^2):          1.00000

iter=0 err=0.50000 F=0.50000 target=4.38203 diff=1.00000
iter=1 err=0.00000 F=1.00000 target=2.12781 diff=0.51442
iter=2 err=0.00000 F=1.00000 target=1.98969 diff=0.06491
iter=3 err=0.00000 F=1.00000 target=1.97815 diff=0.00580
iter=4 err=0.00000 F=1.00000 target=1.97697 diff=0.00060
iter=5 err=0.00000 F=1.00000 target=1.97687 diff=0.00005
iter=6 err=0.00000 F=1.00000 target=1.97687 diff=0.00000
iter=7 err=0.00000 F=1.00000 target=1.97687 diff=0.00000

Done! writing model file ...
```
自分の解析結果のくせに adding virtual node しますね。
まずこれは dicrc で指定してる MeCab の解析出力指定の一部が悪くて、

```
node-format-unidic22 = %m\t%f[0],%f[1],%f[2],%f[3],%f[4],%f[5],%f[6],%f[7],%f[8],%f[9],%f[10],%f[11],%f[12],"%f[13]","%f[14]","%f[15]","%f[16]","%f[17]","%f[18]",%f[19],%f[20],%f[21],%f[22],%f[23],"%f[24]","%f[25]","%f[26]",%f[27],%f[28]\n
```
この行の
```
"%f[13]","%f[14]","%f[15]","%f[16]","%f[17]","%f[18]",%f[19],%f[20],%f[21],%f[22],%f[23],"%f[24]","%f[25]","%f[26]",
```
このあたりのダブルクォーテーション（"）でくくってるカラムには、アクセント関係の値が入るのだけど。例えば「"動詞%F1,形容詞%F2@-1"」みたいに 1 カラムにカンマ（,）区切りで複数値が入ってくることがあって。何もしてないと、CSV 的にはまずい。だけど複数値が入る場合だけダブルクォーテーションでくくるとかそういう複雑、dicrc では設定できない。なので苦肉の策で値が 1 個でも空でもとりあえず全部ダブルクォーテーションでくくっている。

空の場合「,"",」みたいになってしまうけれど、CSV のルール的には空要素扱いで特に問題はない。

https://se.cite.ehime-u.ac.jp/~aman/memo/csv.html

ただ mini_lex.csv では、アクセント用のカラムに入る値が 1 個の時はダブルクォーテーションではくくっていないし、0 個の時はアスタリスク（*）が入る。

空の時にアスタリスク（*）が入るのは他のカラムでも同様。

```mini_lex.csv
くるま,0,0,0,名詞,普通名詞,一般,*,*,*,クルマ,車,くるま,クルマ,くるま,クルマ,和,ク濁,基本形,*,*,*,*,体,クルマ,クルマ,クルマ,クルマ,0,C2,*,2895297718133248,10533
車,0,0,0,名詞,普通名詞,一般,*,*,*,クルマ,車,車,クルマ,車,クルマ,和,ク濁,基本形,*,*,*,*,体,クルマ,クルマ,クルマ,クルマ,0,C2,*,2895297651024384,10533
くる,0,0,0,動詞,非自立可能,*,*,カ行変格,終止形-一般,クル,来る,くる,クル,くる,クル,和,*,*,*,*,*,*,用,クル,クル,クル,クル,1,C1,*,2891174448865963,10518
まで,0,0,0,助詞,副助詞,*,*,*,*,マデ,まで,まで,マデ,まで,マデ,和,*,*,*,*,*,*,副助,マデ,マデ,マデ,マデ,*,"名詞%F2@1,形容詞%F2@1,動詞%F2@1",*,9865651581755904,35891
で,0,0,0,助詞,接続助詞,*,*,*,*,テ,て,で,デ,で,デ,和,*,*,*,*,*,*,接助,デ,デ,デ,デ,*,"動詞%F1,形容詞%F2@-1",*,6837330270888448,24874
待つ,0,0,0,動詞,一般,*,*,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,*,*,*,*,*,*,用,マツ,マツ,マツ,マツ,1,C1,*,9848884062986923,35830
```

というわけで、

* 1 カラムに値が 0 or 1 つしか入っていないのにダブルクォーテーションでくくってあるカラムからくくってるダブルクォーテーションを除去
* 空のカラムはアスタリスクで埋める

を実施したのがこちら（↓）。

```
$ cat mecab_output_corpus_2.mecab
車      名詞,普通名詞,一般,*,*,*,クルマ,車,車,クルマ,車,クルマ,和,ク濁,基本形,*,*,*,*,体,クルマ,クルマ,クルマ,クルマ,0,C2,*,2895297651024384,10533
で      助詞,接続助詞,*,*,*,*,テ,て,で,デ,で,デ,和,*,*,*,*,*,*,接助,デ,デ,デ,デ,*,"動詞%F1,形容詞%F2@-1",*,6837330270888448,24874
待つ    動詞,一般,*,*,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,*,*,*,*,*,*,用,マツ,マツ,マツ,マツ,1,C1,*,9848884062986923,35830
EOS
くるま  名詞,普通名詞,一般,*,*,*,クルマ,車,くるま,クルマ,くるま,クルマ,和,ク濁,基本形,*,*,*,*,体,クルマ,クルマ,クルマ,クルマ,0,C2,*,2895297718133248,10533
で      助詞,接続助詞,*,*,*,*,テ,て,で,デ,で,デ,和,*,*,*,*,*,*,接助,デ,デ,デ,デ,*,"動詞%F1,形容詞%F2@-1",*,6837330270888448,24874
待つ    動詞,一般,*,*,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,*,*,*,*,*,*,用,マツ,マツ,マツ,マツ,1,C1,*,9848884062986923,35830
EOS
```

さっそく、mecab-cost-train
```
seed$ mecab-cost-train ../mecab_output_corpus_2.mecab model
reading corpus ...
Number of sentences: 2
Number of features:  52
eta:                 0.00005
freq:                1
eval-size:           10
unk-eval-size:       4
threads:             1
charset:             UTF-8
C(sigma^2):          1.00000

iter=0 err=0.50000 F=0.50000 target=0.69315 diff=1.00000
iter=1 err=0.00000 F=1.00000 target=0.50293 diff=0.27442
iter=2 err=0.00000 F=1.00000 target=0.29234 diff=0.41873
iter=3 err=0.00000 F=1.00000 target=0.29495 diff=0.00894
iter=4 err=0.00000 F=1.00000 target=0.17163 diff=0.41809
iter=5 err=0.00000 F=1.00000 target=0.17105 diff=0.00337
iter=6 err=0.00000 F=1.00000 target=0.17077 diff=0.00164
iter=7 err=0.00000 F=1.00000 target=0.17077 diff=0.00000
iter=8 err=0.00000 F=1.00000 target=0.17077 diff=0.00000

Done! writing model file ...
```

adding virtual node 回避。

ちなみにこういうふう（↓）に、空のカラムをアスタリスクで埋めなかったり、値が 1 個なのにダブルクォーテーションでくくってたり、ダブルクォーテーションでくくった空要素があると、CSV 側のアスタリスクで埋めてあるカラムと一致せず、もれなく adding virtual node 行き。

```mecab_output_corpus_3.mecab
車      名詞,普通名詞,一般,,,,クルマ,車,車,クルマ,車,クルマ,和,ク濁,基本形,*,*,*,*,体,クルマ,クルマ,クルマ,クルマ,0,C2,*,2895297651024384,10533
で      助詞,"接続助詞",*,*,*,*,テ,て,で,デ,で,デ,和,*,*,*,*,*,*,接助,デ,デ,デ,デ,*,"動詞%F1,形容詞%F2@-1",*,6837330270888448,24874
待つ    動詞,一般,"",*,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,*,*,*,*,*,*,用,マツ,マツ,マツ,マツ,1,C1,*,9848884062986923,35830
EOS
くるま  名詞,普通名詞,一般,*,*,*,クルマ,車,くるま,クルマ,くるま,クルマ,和,ク濁,基本形,*,*,*,*,体,クルマ,クルマ,クルマ,クルマ,0,C2,*,2895297718133248,10533
で      助詞,接続助詞,*,*,*,*,テ,て,で,デ,で,デ,和,*,*,*,*,*,*,接助,デ,デ,デ,デ,*,"動詞%F1,形容詞%F2@-1",*,6837330270888448,24874
待つ    動詞,一般,*,*,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,*,*,*,*,*,*,用,マツ,マツ,マツ,マツ,1,C1,*,9848884062986923,35830
EOS
```
```
seed$ mecab-cost-train ../mecab_output_corpus_3.mecab model
reading corpus ...adding virtual node: 名詞,普通名詞,一般,,,,クルマ,車,車,クルマ,車,クルマ,和,ク濁,基本形,*,*,*,*,体,ク ルマ,クルマ,クルマ,クルマ,0,C2,*,2895297651024384,10533
adding virtual node: 助詞,"接続助詞",*,*,*,*,テ,て,で,デ,で,デ,和,*,*,*,*,*,*,接助,デ,デ,デ,デ,*,"動詞%F1,形容詞%F2@-1",*,6837330270888448,24874
adding virtual node: 動詞,一般,"",*,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,*,*,*,*,*,*,用,マツ,マツ,マツ,マツ,1,C1,*,9848884062986923,35830

Number of sentences: 2
Number of features:  52
eta:                 0.00005
freq:                1
eval-size:           10
unk-eval-size:       4
threads:             1
charset:             UTF-8
C(sigma^2):          1.00000

iter=0 err=0.50000 F=0.50000 target=2.77259 diff=1.00000
iter=1 err=0.00000 F=1.00000 target=1.74502 diff=0.37062
iter=2 err=0.00000 F=1.00000 target=1.54654 diff=0.11374
iter=3 err=0.00000 F=1.00000 target=1.43054 diff=0.07501
iter=4 err=0.00000 F=1.00000 target=1.42481 diff=0.00400
iter=5 err=0.00000 F=1.00000 target=1.42397 diff=0.00059
iter=6 err=0.00000 F=1.00000 target=1.42393 diff=0.00003
iter=7 err=0.00000 F=1.00000 target=1.42393 diff=0.00000
iter=8 err=0.00000 F=1.00000 target=1.42393 diff=0.00000

Done! writing model file ...
```

同梱の SQL ファイルを読めばわかるけど、UniDic はver. 2.2.0 以降、DB から CSV を export する際に、空のカラムには必ずアスタリスクを入れているし、カラムに値が 2 つ以上入る場合のみ、ダブルクォーテーションでくくっている。

さらに補足だが、今回使っているミニデータでは feature.def でアクセント周りの素性は基の UniDic から削除して使っている。
なので実は、アクセント周りのカラムは空だろうが、ダブルクォーテーションでくくってあろうがなかろうが、問題なく動いたりする。

```
$ cat mecab_output_corpus_4.mecab
車      名詞,普通名詞,一般,*,*,*,クルマ,車,車,クルマ,車,クルマ,和,ク濁,基本形,*,*,*,*,体,クルマ,クルマ,クルマ,クルマ,0,C2,*,2895297651024384,10533
で      助詞,接続助詞,*,*,*,*,テ,て,で,デ,で,デ,和,*,*,*,*,*,*,接助,デ,デ,デ,デ,*,"動詞%F1,形容詞%F2@-1",*,6837330270888448,24874
待つ    動詞,一般,*,*,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,*,*,*,*,*,*,用,マツ,マツ,マツ,マツ,1,C1,*,9848884062986923,35830
EOS
くるま  名詞,普通名詞,一般,*,*,*,クルマ,車,くるま,クルマ,くるま,クルマ,和,ク濁,基本形,*,*,*,"",体,クルマ,クルマ,クルマ, クルマ,0,C2,*,2895297718133248,10533
で      助詞,接続助詞,*,*,*,*,テ,て,で,デ,で,デ,和,*,*,*,*,*,*,接助,デ,デ,デ,デ,,"動詞%F1,形容詞%F2@-1",*,6837330270888448,24874
待つ    動詞,一般,*,*,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,*,*,*,*,*,*,用,マツ,マツ,マツ,マツ,1,"C1",*,9848884062986923,35830
EOS
```
```
seed$ mecab-cost-train ../mecab_output_corpus_4.mecab model
reading corpus ...
Number of sentences: 2
Number of features:  52
eta:                 0.00005
freq:                1
eval-size:           10
unk-eval-size:       4
threads:             1
charset:             UTF-8
C(sigma^2):          1.00000

iter=0 err=0.50000 F=0.50000 target=0.69315 diff=1.00000
iter=1 err=0.00000 F=1.00000 target=0.50293 diff=0.27442
iter=2 err=0.00000 F=1.00000 target=0.29234 diff=0.41873
iter=3 err=0.00000 F=1.00000 target=0.29495 diff=0.00894
iter=4 err=0.00000 F=1.00000 target=0.17163 diff=0.41809
iter=5 err=0.00000 F=1.00000 target=0.17105 diff=0.00337
iter=6 err=0.00000 F=1.00000 target=0.17077 diff=0.00164
iter=7 err=0.00000 F=1.00000 target=0.17077 diff=0.00000
iter=8 err=0.00000 F=1.00000 target=0.17077 diff=0.00000

Done! writing model file ...
```

ポイントなのは feature.def で使用しているカラムの中身がコーパスと CSV で同じ形で書かれていることだったりする。

# 未知語処理された行でエラーになるのに対応

ミニデータの CSV にはカタカナ表記の「クルマ」は入っていないので、
```final raw_text_2.txt
クルマで待つ
```
を作って、
```
$ mecab -d final raw_text_2.txt > mecab_output_corpus_5.mecab
```

```mecab_output_corpus_5.mecab
ク      名詞,普通名詞,一般,,,
ル      名詞,普通名詞,一般,,,
マ      名詞,普通名詞,一般,,,
で      助詞,接続助詞,,,,,テ,て,で,デ,で,デ,和,"","","","","","",接助,デ,デ,デ,デ,"","動詞%F1,形容詞%F2@-1","",6837330270888448,24874
待つ    動詞,一般,,,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,"","","","","","",用,マツ,マツ,マツ,マツ,"1","C1","",9848884062986923,35830
EOS
```
これを訓練データにする。

```
seed$ mecab-cost-train ../mecab_output_corpus_5.mecab model
reading corpus ...adding virtual node: 名詞,普通名詞,一般,,,
adding virtual node: 名詞,普通名詞,一般,,,
adding virtual node: 名詞,普通名詞,一般,,,
adding virtual node: 助詞,接続助詞,,,,,テ,て,で,デ,で,デ,和,"","","","","","",接助,デ,デ,デ,デ,"","動詞%F1,形容詞%F2@-1","",6837330270888448,24874
adding virtual node: 動詞,一般,,,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,"","","","","","",用,マツ,マツ, マツ,マツ,"1","C1","",9848884062986923,35830
feature_index.cpp(278) [rewrite_.rewrite2(path->rnode->feature, &ufeature2, &lfeature2, &rfeature2)]  cannot rewrite pattern: 名詞,普通名詞,一般,,,
```

これ実は rewrite.def の書き換えパターンにマッチしない（列数が足りてない）って怒られているだけで。
列数は実際足りているのだけど、カンマ（,）の直後に改行が入ると、カンマ直後のカラムを「ない」と判断しているだけなので。こういうふうに改行直前に値を入れてあげるだけで解決する。

```mecab_output_corpus_6.mecab
ク      名詞,普通名詞,一般,,,*
ル      名詞,普通名詞,一般,,,*
マ      名詞,普通名詞,一般,,,*
で      助詞,接続助詞,,,,,テ,て,で,デ,で,デ,和,"","","","","","",接助,デ,デ,デ,デ,"","動詞%F1,形容詞%F2@-1","",6837330270888448,24874
待つ    動詞,一般,,,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,"","","","","","",用,マツ,マツ,マツ,マツ,"1","C1","",9848884062986923,35830
EOS
```

```
$ /work/oka/bin/mecab0996/libexec/mecab/mecab-cost-train ../mecab_output_corpus_5.mecab model
reading corpus ...adding virtual node: 名詞,普通名詞,一般,,,*
adding virtual node: 名詞,普通名詞,一般,,,*
adding virtual node: 名詞,普通名詞,一般,,,*
adding virtual node: 助詞,接続助詞,,,,,テ,て,で,デ,で,デ,和,"","","","","","",接助,デ,デ,デ,デ,"","動詞%F1,形容詞%F2@-1","",6837330270888448,24874
adding virtual node: 動詞,一般,,,五段-タ行,終止形-一般,マツ,待つ,待つ,マツ,待つ,マツ,和,"","","","","","",用,マツ,マツ, マツ,マツ,"1","C1","",9848884062986923,35830

Number of sentences: 1
Number of features:  32
eta:                 0.00005
freq:                1
eval-size:           10
unk-eval-size:       4
threads:             1
charset:             UTF-8
C(sigma^2):          1.00000

iter=0 err=1.00000 F=-nan target=7.45703 diff=1.00000
iter=1 err=0.00000 F=1.00000 target=4.06693 diff=0.45462
iter=2 err=0.00000 F=1.00000 target=3.24484 diff=0.20214
iter=3 err=0.00000 F=1.00000 target=3.22770 diff=0.00528
iter=4 err=0.00000 F=1.00000 target=3.22755 diff=0.00004
iter=5 err=0.00000 F=1.00000 target=3.22753 diff=0.00001
iter=6 err=0.00000 F=1.00000 target=3.22753 diff=0.00000

Done! writing model file ...
```

これで解決。