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 +各パスのスコア情報を出せるようにしたいと思います。