係り受け解析器の学習機能

もう少し自然言語処理の意味理解よりのことをちゃんと勉強してみようと思う今日この頃。のやったことメモ_φ(・_・
今回は、係り受け解析器のうち、CaboChaとJ.DepP の学習機能を動かしてみたのでその辺の備忘メモ

準備

CaboChaは既にインストール済み。
モデルは今回、KNBコーパスから学習させるので品詞体系をJUMANにしておく。モデル(係り受け、チャンキング、固有表現)もとりあえずJUMANに変更。

$ cat /usr/local/etc/cabocharc 
 :
posset = JUMAN
 :
# Parser model file name
parser-model  = /usr/local/lib/cabocha/model/dep.juman.model
 :
# Chunker model file name
chunker-model = /usr/local/lib/cabocha/model/chunk.juman.model
 :
# NE model file name
ne-model = /usr/local/lib/cabocha/model/ne.juman.model
 :

MeCabのデフォルト辞書もJUMANに変更(辞書のインストールは既に済み)

$ cat /usr/local/etc/mecabrc
 :
dicdir = /usr/local/lib/mecab/dic/jumandic
 :

動作確認

$ echo "クロールで泳いでいる少女を見た" | cabocha -f 2 -n 1
dep.cpp(90) [decode_posset(p) == posset()] model posset and dependency parser's posset are different: JUMAN != IPA

なぜか品詞体系がIPAのまま?実行時オプションは利いたので、とりあえず今はスルー

$ echo "クロールで泳いでいる少女を見た" | cabocha -P JUMAN -f 2 -n 1
クロールで-D    
  泳いでいる-D  
        少女を-D
            見た
EOS
* 0 1D 0/1 1.421559
クロール	名詞,普通名詞,*,*,クロール,くろーる,代表表記:クロール/くろーる カテゴリ:抽象物 ドメイン:スポーツ	O
で	助詞,格助詞,*,*,で,で,*	O
* 1 2D 0/1 1.458612
泳いで	動詞,*,子音動詞ガ行,タ系連用テ形,泳ぐ,およいで,代表表記:泳ぐ/およぐ	O
いる	接尾辞,動詞性接尾辞,母音動詞,基本形,いる,いる,連語	O
* 2 3D 0/1 1.458612
少女	名詞,普通名詞,*,*,少女,しょうじょ,代表表記:少女/しょうじょ カテゴリ:人	O
を	助詞,格助詞,*,*,を,を,*	O
* 3 -1D 0/0 0.000000
見た	動詞,*,母音動詞,タ形,見る,みた,代表表記:見る/みる 補文ト 自他動詞:自:見える/みえる	O
EOS

続いてJ.DepP。 聞いたことなかったけど、CaboCha同様にコーパスから学習させることができるようなので入れてみた。学習、解析速度が速く、学習したモデルの精度もSVMに匹敵するらしい。
こちらは、デフォルトでMeCab、JUMAN辞書、モデルの学習にKNBコーパスを使うようになってる。

$ wget http://www.tkl.iis.u-tokyo.ac.jp/~ynaga/jdepp/jdepp-latest.tar.gz
$ tar xzf jdepp-latest.tar.gz 
$ cd jdepp-2015-10-05/
$ ./configure
$ make model
$ sudo make install
$ echo "クロールで泳いでいる少女を見た" | mecab | jdepp
(input: STDIN [-I 0])
# S-ID: 1; J.DepP
* 0 1D
クロール	名詞,普通名詞,*,*,クロール,くろーる,代表表記:クロール/くろーる カテゴリ:抽象物 ドメイン:スポーツ
で	助詞,格助詞,*,*,で,で,*
* 1 2D
泳いで	動詞,*,子音動詞ガ行,タ系連用テ形,泳ぐ,およいで,代表表記:泳ぐ/およぐ
いる	接尾辞,動詞性接尾辞,母音動詞,基本形,いる,いる,連語
* 2 3D
少女	名詞,普通名詞,*,*,少女,しょうじょ,代表表記:少女/しょうじょ カテゴリ:人
を	助詞,格助詞,*,*,を,を,*
* 3 -1D
見た	動詞,*,母音動詞,タ形,見る,みた,代表表記:見る/みる 補文ト 自他動詞:自:見える/みえる
EOS

J.DepP profiler:
io        : 0.0166 ms./trial (0.06638521/4)
dict      : 0.3317 ms.
preproc   : 0.0194 ms.
chunk     : 0.0536 ms.
depnd     : 0.0250 ms.

ついでに、KNPも入れた。解析速度とか今のところ大規模なテキスト処理は厳しいけど、70億分のWebテキストから自動構築した格フレームとかいろいろ興味深いところもあるので、また別途時間をとっていろいろ確認したい。

$ tar xjf juman-7.01.tar.bz2 
$ cd juman-7.01
$ ./configure --prefix=/usr/local
$ make
$ sudo make install
$ echo "クロールで泳いでいる少女を見た" | juman
クロール くろーる クロール 名詞 6 普通名詞 1 * 0 * 0 "代表表記:クロール/くろーる カテゴリ:抽象物 ドメイン:スポーツ"
で で で 助詞 9 格助詞 1 * 0 * 0 NIL
泳いで およいで 泳ぐ 動詞 2 * 0 子音動詞ガ行 4 タ系連用テ形 14 "代表表記:泳ぐ/およぐ"
いる いる いる 接尾辞 14 動詞性接尾辞 7 母音動詞 1 基本形 2 "代表表記:いる/いる"
少女 しょうじょ 少女 名詞 6 普通名詞 1 * 0 * 0 "代表表記:少女/しょうじょ カテゴリ:人"
を を を 助詞 9 格助詞 1 * 0 * 0 NIL
見た みた 見る 動詞 2 * 0 母音動詞 1 タ形 10 "代表表記:見る/みる 補文ト 自他動詞:自:見える/みえる"
EOS
$ tar xjf knp-4.15.tar.bz2
$ cd knp-4.15
$ ./configure --prefix=/usr/local --with-juman-prefix=/usr/local/
$ make
$ sudo make install
$ echo "クロールで泳いでいる少女を見た" | juman | knp
# S-ID:1 KNP:4.15-CF1.1 DATE:2015/11/26 SCORE:-19.12316
クロールで──┐         
        泳いでいる──┐     
                    少女を──┐ 
                              見た
EOS
CaboChaのモデルをKNBコーパスから作る

CaboChaの学習/評価手順の確認。今回は手軽につかえるKNBコーパスを使ってチャンキングと係り受けモデルを作成する。
4ジャンルに分かれているので、単純に1つを評価用、残りの3つを学習用として作業する。

入力フォーマットは、CaboCha形式に変換する必要があるが、後述のJ.DepP付属のスクリプトがほぼそのまま使えるのでそちらを調整(ヘッダを消しただけ)して使わせてもらう。

評価用のスクリプトeval.py)とかもJ.DepP付属のものを使用している。
最後に、各ジャンルの評価結果を↓でガッちゃんこしただけ


以下、コーパスの作成からモデルの作成、テスト、評価までの作業メモ

# KNBコーパスの変換
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Keitai*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2cabocha.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc1.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Kyoto*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2cabocha.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc2.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Gourmet*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2cabocha.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc3.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Sports*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2cabocha.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc4.euc

$ for i in `seq 1 1 4`;
> do
>   iconv -f EUC-JP -t UTF-8 knbc$i.euc > knbc$i.utf8;
> done

# 学習用と評価用に分ける
$ cat knbc2.utf8 knbc3.utf8 knbc4.utf8> corpus1
$ cat knbc1.utf8 > gold1
$ cat knbc3.utf8 knbc4.utf8 knbc1.utf8> corpus2
$ cat knbc2.utf8 > gold2
$ cat knbc4.utf8 knbc1.utf8 knbc2.utf8> corpus3
$ cat knbc3.utf8 > gold3
$ cat knbc1.utf8 knbc2.utf8 knbc3.utf8> corpus4
$ cat knbc4.utf8 > gold4

# 文節と係り受けを学習させてそのまま評価
$ for i in `seq 1 1 4`;
> do
>   /usr/local/libexec/cabocha/cabocha-learn -e chunk -P JUMAN -t utf-8 corpus$i chunk$i.model;
>   /usr/local/libexec/cabocha/cabocha-learn -e dep -P JUMAN -t utf-8 corpus$i dep$i.model
>   cat gold$i | python ../tools/to_sent.py | mecab -d /usr/local/lib/mecab/dic/jumandic | cabocha -m dep$i.model -M chunk$i.model -P JUMAN -I1 -f1 > result$i
> done

$ for i in `seq 1 1 4`;
> do
>   python ../tools/eval.py result$i gold$i 2> eval$i;
> done

# 評価結果をガッちゃんこ
$ cat eval[1-4] | python ../tools/eval_all.py 
chunk:
  precision: 0.8221 (22713/27628)
  recall:    0.8189 (22713/27737)
  f1:        0.8205
  sent acc.: 0.4853 (2031/4185)
depnd:
  precision: 0.6288 (14742/23443)
  recall:    0.6259 (14742/23552)
  f1:        0.6274
  sent acc.: 0.3642 (1524/4185)

あんまりよくない気が、、、そもそもMeCabを学習させてないからこんなもんか??

J.DepPのモデルをKNBコーパスから作る

同じことをJ.DepPでもやってみる。

$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Keitai*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2kyoto.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc1.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Kyoto*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2kyoto.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc2.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Gourmet*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2kyoto.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc3.euc
$ find ../KNBC_v1.0_090925/corpus1 -type f -name "KN*Sports*" | LC_ALL=C sort | xargs cat | python ../tools/knbc2kyoto.py KNP | python ../tools/replace_pos.py mecab -d /usr/local/lib/mecab/dic/jumandic > knbc4.euc

$ for i in `seq 1 1 4`;
> do
>   iconv -f EUC-JP -t UTF-8 knbc$i.euc > knbc$i.utf8;
> done

$ cat knbc2.utf8 knbc3.utf8 knbc4.utf8> corpus1
$ cat knbc1.utf8 > gold1
$ cat knbc3.utf8 knbc4.utf8 knbc1.utf8> corpus2
$ cat knbc2.utf8 > gold2
$ cat knbc4.utf8 knbc1.utf8 knbc2.utf8> corpus3
$ cat knbc3.utf8 > gold3
$ cat knbc1.utf8 knbc2.utf8 knbc3.utf8> corpus4
$ cat knbc4.utf8 > gold4

$ for i in `seq 1 1 4`;
> do
>   mkdir -p model/knbc$i && rm -rf model/knbc$i/*
>   jdepp -t 0 -I 1 -c corpus$i -m model/knbc$i -- -t 1 -d 2 -c 0.0008  -i 40 -p
>   jdepp -t 3 -I 1 -c corpus$i -m model/knbc$i -- -t 1 -d 2 -c 0.0008  -i 40 -p -- -s 0.02 -i 5 -t 1;
>   jdepp -t 0 -I 2 -c corpus$i -m model/knbc$i -- -t 1 -d 2 -c 0.00005 -i 40 -p
>   jdepp -t 3 -I 2 -c corpus$i -m model/knbc$i -- -t 1 -d 2 -c 0.00005 -i 40 -p -- -- -s 0.005 -i 5 -t 1;
>   cat gold$i | python ../tools/to_sent.py | mecab -d /usr/local/lib/mecab/dic/jumandic | jdepp -m model/knbc$i > result$i
> done

$ for i in `seq 1 1 4`;
> do
>   python ../tools/eval.py result$i gold$i 2> eval$i;
> done

$ cat eval[1-4] | python ../tools/eval_all.py 
chunk:
  precision: 0.9304 (25918/27856)
  recall:    0.9344 (25918/27737)
  f1:        0.9324
  sent acc.: 0.7510 (3143/4185)
depnd:
  precision: 0.7843 (18564/23671)
  recall:    0.7882 (18564/23552)
  f1:        0.7862
  sent acc.: 0.4937 (2066/4185)

これを見るとやはりCaboChaの結果が怪しい、、、なにかやらかしてる感じか:-)
念のため、J.DepPをCaboChaと同じSVMで学習させたらどうなるか見てみる。
#そして、ちゃんと計測してないけどJ.DepPの学習明らかに速いな。学習/解析速度ももう少しデータ用意して比べてみたい。
TinySVMをインストール。

$ brew install tinysvm

以下、学習部分のみ。
SVM(-l 1)を使うときのオプションがよくわかってないが、とりあえずモデルディレクトリにSVMLight形式の学習データができてたからTinySVMにそいつを学習させた。結構時間がかかる。。。

 :
[省略]
 :

$ for i in `seq 1 1 4`;
> do
>   mkdir -p model/knbc$i && rm -rf model/knbc$i/*
>   jdepp -t 0 -I 1 -c corpus$i -m model/knbc$i -- -t 0
>   svm_learn -t 1 -d 2 -c 1 model/knbc$i/chunk.train model/knbc$i/chunk
>   jdepp -t 3 -I 1 -c corpus$i -m model/knbc$i -- -t 0 -- -s 0.02 -i 5 -t 1;
>   jdepp -t 0 -I 2 -c corpus$i -m model/knbc$i -- -t 0
>   svm_learn -t 1 -d 2 -c 1 model/knbc$i/depnd.p0.train model/knbc$i/depnd.p0
>   jdepp -t 3 -I 2 -c corpus$i -m model/knbc$i -- -t 0 -- -- -s 0.005 -i 5 -t 1;
>   cat gold$i | python ../tools/to_sent.py | mecab -d /usr/local/lib/mecab/dic/jumandic | jdepp -m model/knbc$i > result$i
> done

$ for i in `seq 1 1 4`;
> do
>   python ../tools/eval.py result$i gold$i 2> eval$i;
> done

$ cat eval[1-4] | python ../tools/eval_all.py 
chunk:
  precision: 0.9308 (25893/27818)
  recall:    0.9335 (25893/27737)
  f1:        0.9322
  sent acc.: 0.7513 (3144/4185)
depnd:
  precision: 0.7809 (18455/23633)
  recall:    0.7836 (18455/23552)
  f1:        0.7822
  sent acc.: 0.4875 (2040/4185)

やはり、こっちはそれっぽい精度がでてる。。。にゃぜだ

ん〜、気になって眠れないけど眠いのでねる。

つづく(かも)