CRFsuite で推定に使われるパラメータ情報を出力する
CRFsuite を使っていると、「このケースでこの結果だったら、このケースでも同じような結果になりそうなのにどうして異なる結果になるんだ????」と思うことがあるでしょうが、デバッグするのがけっこう大変です。
普通に頑張る場合、crfsuite では dump コマンドでモデルの内容を確認することができるので、その内容と照らし合わせることになります。
例えば、次のようなデータ(foo bar の系列ラベルと bar foo の系列ラベル)で学習した場合、
B-A __BOS__ w[0]=foo w[0]|w[1]=foo|bar
I-A __EOS__ w[0]=bar
B-B __BOS__ w[0]=bar w[0]|w[1]=bar|foo
I-B __EOS__ w[0]=foo
% crfsuite learn -m foo.model train.txt
次のような出力結果になります。
% crfsuite dump foo.model
FILEHEADER = {
magic: lCRF
size: 4972
type: FOMC
version: 100
num_features: 0
num_labels: 4
num_attrs: 6
off_features: 0x30
off_labels: 0x12C
off_attrs: 0x9C4
off_labelrefs: 0x12CC
off_attrrefs: 0x1308
}
LABELS = {
0: B-A
1: I-A
2: B-B
3: I-B
}
ATTRIBUTES = {
0: __BOS__
1: w[0]=foo
2: w[0]|w[1]=foo|bar
3: __EOS__
4: w[0]=bar
5: w[0]|w[1]=bar|foo
}
TRANSITIONS = {
(1) B-A --> I-A: 0.385733
(1) B-B --> I-B: 0.385733
}
STATE_FEATURES = {
(0) __BOS__ --> B-A: 0.191293
(0) __BOS__ --> B-B: 0.191293
(0) w[0]=foo --> B-A: 0.193166
(0) w[0]=foo --> I-B: 0.225723
(0) w[0]|w[1]=foo|bar --> B-A: 0.306020
(0) __EOS__ --> I-A: 0.205886
(0) __EOS__ --> I-B: 0.205886
(0) w[0]=bar --> I-A: 0.225723
(0) w[0]=bar --> B-B: 0.193166
(0) w[0]|w[1]=bar|foo --> B-B: 0.306020
}
例えば、次のデータからラベルを推定する場合、B-A I-A という推定結果になるですが、
B-A __BOS__ w[0]=foo w[0]|w[1]=foo|bar
I-A __EOS__ w[0]=bar
そのスコアを計算するには「__BOS__ –> B-A のスコア」 + 「w[0]=foo –> B-A のスコア」 + 「w[0]|w[1]=foo|bar –> B-A のスコア」 +「B-A –> I-A のスコア」+「__EOS__ –> I-A のスコア」+「w[0]=bar –> I-A のスコア」を計算すればよいです。
B-B I-B の推定結果のスコアも同様に計算できるので、どのスコアが推定結果に大きな影響を及ぼしているかなんとなく見当が付きます。
データが少なくて素性も少ない場合はこれで頑張れるんですが、それなりに大きなモデルになってくるとこの作業をするのは極めて困難です。
というわけで、使われている素性のスコア情報を出力する verbose オプションを入れてみました。
https://github.com/abicky/crfsuite/commit/98620a12110fb932952a7a9b94600ffe896bfa8e
※当方 C 言語は全然読み書きできないので極めておかしな書き方やメモリリークがあるかもしれませんが、教えてもらえると嬉しいです!
インストール
Mac だと次のコマンドでインストールできると思います。
% git clone https://github.com/abicky/crfsuite.git
% cd ./crfsuite
% git checkout feature/verbose-option
% ./autogen.sh
% # --prefix は好みで
% ./configure --prefix ~/local
% make install
これで先ほどのデータに対して -v オプション付きで推定を行うと使われるパラメータについて詳細な情報が出力されます。
% ~/local/bin/crfsuite tag -m foo.model -v test.txt
TRANSITIONS = {
B-A --> I-A: 0.385733
B-B --> I-B: 0.385733
}
STATES = {
tag[0] --> B-A: 0.690479
__BOS__ --> B-A: 0.191293
w[0]=foo --> B-A: 0.193166
w[0]|w[1]=foo|bar --> B-A: 0.306020
tag[0] --> I-A: 0.000000
tag[0] --> B-B: 0.191293
__BOS__ --> B-B: 0.191293
tag[0] --> I-B: 0.225723
w[0]=foo --> I-B: 0.225723
tag[1] --> B-A: 0.000000
tag[1] --> I-A: 0.431609
__EOS__ --> I-A: 0.205886
w[0]=bar --> I-A: 0.225723
tag[1] --> B-B: 0.193166
w[0]=bar --> B-B: 0.193166
tag[1] --> I-B: 0.205886
__EOS__ --> I-B: 0.205886
}
B-A
I-A
タグの遷移のスコア、最初のタグ(tag[0])が B-A になる場合のスコアとその内訳などが出力されているのがわかると思います。
これで学習データや素性がそこそこ多くなってもなんとか確認できるかなぁという気がします。
ちなみに、Ruby で使う場合は次のようにオプションを指定することで同じ出力がされます。
require 'crfsuite'
tagger = Crfsuite::Tagger.new
verbose = true
tagger.open('/path/to/model', verbose)
気が向いたら N-best +各パスのスコア情報を出せるようにしたいと思います。