cabochaソースを読む(3)各文節の素性
素性選択レイヤを見る。ソースはselector.cpp。l.117 parse()から行く。
l.121 の forループで(sentence中の)全chunkをなめる。
l.128 の forループで各chunkの中の全tokenをなめる。
各tokenに対して、
l.130 pat_kutouten_.match(token->normalized_surface)
これは何だろうか。parse()のすぐ上にSelector::open()があり、ここで
CHECK_DIE(pat_kutouten_.compile(KUTOUTEN_PAT, &iconv));
としている。open()というくらいだから最初に1度呼ぶんだろう。この KUTOUTEN_PAT は、ソースの selector_pat.h の中で
// const char KUTOUTEN_PAT = "(。|、|,|.)";
のように定義されている。
match()の中身は追っていないが、まあパターンマッチしてくれるのだろう。
parse()に戻って、ll.130-143は、句読点・[開閉]かっこのパターンが見つかると片っ端から出力しているのがわかる。
素性選択レイヤの出力は↓こんな感じだが
* 3 -1D 0/1 0.000000 F_H0:本,F_H1:名詞,F_H2:一般,F_F0:を,F_F1:助詞,F_F2:格助詞,F_F3:一般,A:を,B:名詞-一般,G_CASE:を
G_PUNC:** や F_OB:** もこのような素性の例である。
また、この"G_"とか"F_"とかは何か意味があるのかと気になるのだが、これもまた後ほど述べる。
l.148 findHead() の定義は下の l.238 にある。この関数は、文節の「主辞」と「機能辞」の位置を返す。中を見よう。
l. 253 head_matcher = &pat_ipa_head_;
のような行があるが、pat_ipa_head_ も pat_kutouten_/KUTOUTEN_PAT と同様 selector_pat.h に定義があり、
// const char IPA_HEAD_PAT = "!(助詞|助動詞|動詞,非自立|動詞,接尾|形容詞,非自
立|形容詞,接尾|空白|記号)";
要はこれらの品詞以外、つまりいわゆる自立語にマッチするようだ。ちなみに機能語は次のとおり:
// const char IPA_FUNC_PAT = "(助詞|助動詞|動詞,非自立|動詞,接尾|形容詞,非自立|形容詞,接尾)";
ll.266-271 を見るに、これらのパターンにマッチする自立語・機能語のうち、文節中の最後に表われるものをそれぞれこの文節の主辞・機能辞と判断し、その定義を返す。たとえば「約3キロ」なら最後の「キロ」が主辞になる、ということか。「転覆しそうだったのだけれど」なら最後の「けれど」が機能辞になる、と。係り受けを決定するのには「係り元の機能辞 -> 係り先の主辞」が重要なので、こうするのが効果的、ということなのだろう。
parse()に戻って、l.150あたりから。hXXは主辞(head)、fXXは機能辞(func)に関わる。
155 const char *hctype = get_token(htoken, pos_size);
156 const char *hcform = get_token(htoken, pos_size + 1);
pos_size は l.119で定義されてて、辞書がIPAなら4。IPA辞書では入力が
読ん 動詞,自立,*,*,五段・マ行,連用タ接続,読む,ヨン,ヨン
こうなので、[4]=段・マ行 [5]=連用タ接続 のようになるはず。つまりctypeが活用種類、cformが活用形だろう。
ll.195-205:
if (pat_dyn_a_.prefix_match(ftoken->feature)) {
ostrs << " A:" << fsurface;
} else if (fcform) {
ostrs << " A:" << fcform;
} else {
concat_feature(ftoken, pos_size, &output);
ostrs << " A:" << output;
}
concat_feature(htoken, pos_size, &output);
ostrs << " B:" << output;
また変なのが出てきた。pat_dyn_a_/DYN_A_PAT も selector_pat.h にあって、
// const char DYN_A_PAT = "(助詞|副詞|連体詞|接続詞)";
concat_feature()はutils.cppにあり、あるtokenのfeatureを全部ハイフンでつなげる。
つまり"A:"素性とは、機能辞が「これら4つのどれかの品詞なら文字列そのもの、活用語ならその活用形、それら以外なら品詞情報」となる。また"B:"素性とは「主辞の品詞情報」となる。
このA, Bとは何ぞや?は、係り受けparserを読まないとわからないのだが、先に簡単に説明しておくと、A素性とは「ある文節に既に係っている係り元の情報」、B素性は「ある文節から既に係っている係り先の情報」のことである。最初の回に挙げた論文にこの辺のことが「動的素性」として説明されているので参照のこと。
これは多分、「ひとつの述語にヲ格の補語は2つはつかない」のような制約をうまく表現できるのだろう。
selectorの段階ではまだどの文節がどの文節に係るかわかっていないが、まずは文節Xに対して「もしXが別の文節Dに係ったときは、DにつけるであろうA素性」と「もしXが別の文節Sから係られたときは、SにつけるであろうB素性」をリストアップしておく、という作業をselectorで行っているわけである。
ついでなので、最初の方で G_PUNCとかF_OBとか出てきたが、このG_やF_にも触れておく。G_は"gap feature"を表す。これは、「文節Sが文節Dにかかるとき、SとDの間に位置する文節の持つ情報」のことである。たとえば「私は彼の無垢な、あふれんばかりの才能に(ひそかに)嫉妬していた」という文で、「私は」が「嫉妬していた」に係るか?を考えるとき、途中に読点があるのでG_PUNCが、また開・閉かっこがあるので G_OB, G_CB がつくことになる。
F_は static feature といって、これは文節自体の持つ特徴である。
このように、cabochaではfeatureはstatic/gap/dyn_a/dyn_bの4種類ある。
これらをどのように使用しているか、は次回係り受けレイヤで見ていく。
* * * *
ところでふと気づいたが、cabocha, KNP以外の係り受け解析器もあるみたい:
暇があったらみてみようか…まあそうそう他人のソースばかり読んでもいられないが。