Edited at

CaboCha & Python3で文節ごとの係り受けデータ取得


最終的にやりたいこと

定期的に送られてくる報告書の類(書かれていることは毎回似通っている)を、一定のフォーマット・一定の文体で要約したい、というのが最終的なゴールです。既存の要約アルゴリズムも使うかもしれませんが、元の文は数パターン(ただし色々修飾がついている)のため、情報の優先順位をルールベースで実装しながらきっちり目的の文型に収めたいと思っています。

ここではルールを定義する前段として、CaboChaで分析した係り受けをPythonから操作する基本的な処理を作ってみたいと思います。

参考) CaboCha & Python3の環境構築(Windows版)


まずPython3からCaboChaを使ってみる

環境構築編でも使ったCaboChaのサンプルコードを参考に、今回は、tree構造をデータとしてもparseしてみます。

※setup.pyと一緒に配布されているtest.pyを参考にPython2系のprint文をprint()に変更・簡易化したもの。

出力の*で始まる行に、文節ごとのID、かかり先ID(-1はかかり先なし)、主辞/機能語(※1)、係り関係のスコア(※2)が、形態素(単語)で始まる行にMeCab同様の形態素分析の結果が出力されます。

※1: 文節の中のn番目の形態素が主辞で、m番目の形態素が機能語かをn/mの形式で表記したもの。

※2: スコアが大きいほど係りやすい。(がスコアの値と解析精度の関係については研究中とのこと)


サンプルコード

import CaboCha

c = CaboCha.Parser()

sentence = "太郎はこの本を二郎を見た女性に渡した。"

# 視覚的にわかりやすいフォーマット
print(c.parseToString(sentence))

# プログラム的に処理しやすいフォーマット
tree = c.parse(sentence)
print(tree.toString(CaboCha.FORMAT_LATTICE))



実行結果

> python test.py

太郎は-----------D
この-D |
本を---D |
二郎を-D |
見た-D |
女性に-D
渡した。
EOS

* 0 6D 0/1 -2.457381
太郎 名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
* 1 2D 0/0 1.509507
この 連体詞,*,*,*,*,*,この,コノ,コノ
* 2 4D 0/1 0.091699
本 名詞,一般,*,*,*,*,本,ホン,ホン
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
* 3 4D 1/2 2.359707
二 名詞,数,*,*,*,*,二,ニ,ニ
郎 名詞,一般,*,*,*,*,郎,ロウ,ロー
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
* 4 5D 0/1 1.416783
見 動詞,自立,*,*,一段,連用形,見る,ミ,ミ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
* 5 6D 0/1 -2.457381
女性 名詞,一般,*,*,*,*,女性,ジョセイ,ジョセイ
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
* 6 -1D 0/1 0.000000
渡し 動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。 記号,句点,*,*,*,*,。,。,。
EOS



CaboChaのデータ構造

tree = c.parse(sentence)でparseしたtreeのデータ構造は、ソースコードに付属しているexample/excample.cより、

tree

 └ token

   └ chunk     ←ない場合(NULL)もある

   │  └ link    ←かかり先ID

   │  └ head_pos  ←主辞

   │  └ func_pos  ←機能語

   │  └ score   ←スコア

   └ surface    ←形態素部分(元の文字列)

   └ feature    ←品詞や読みなど形態素の情報部分

   └ ne      ←??

という構造にあることがわかります。


example/excample.cより抜粋

  size = cabocha_tree_token_size(tree);

cid = 0;
for (i = 0; i < size; ++i) {
const cabocha_token_t *token = cabocha_tree_token(tree, i);
if (token->chunk != NULL)
printf ("* %d %dD %d/%d %f\n",
cid++,
token->chunk->link,
token->chunk->head_pos,
token->chunk->func_pos,
token->chunk->score);
printf ("%s\t%s\t%s\n",
token->surface,
token->feature,
token->ne ? token->ne : "O");
}


Pythonから係り受けデータに触れてみる

上記、Cのサンプルを、そのままPythonで書いてみると、こんな感じでしょうか。

実行してみると、確かにそれぞれのデータに触れていることがわかります。


そのままポーティング

import CaboCha

c = CaboCha.Parser()
sentence = "太郎はこの本を二郎を見た女性に渡した。"

tree = c.parse(sentence)
chunkId = 0
for i in range(0, tree.size()):
token = tree.token(i)
if token.chunk != None:
print(chunkId, token.chunk.link, token.chunk.head_pos,
token.chunk.func_pos, token.chunk.score)
chunkId += 1

print(token.surface, token.feature, token.ne)



実行結果

0 6 0 1 -2.4573814868927

太郎 名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー None
は 助詞,係助詞,*,*,*,*,は,ハ,ワ None
1 2 0 0 1.5095065832138062
この 連体詞,*,*,*,*,*,この,コノ,コノ None
2 4 0 1 0.0916985273361206
本 名詞,一般,*,*,*,*,本,ホン,ホン None
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ None
3 4 1 2 2.3597068786621094
二 名詞,数,*,*,*,*,二,ニ,ニ None
郎 名詞,一般,*,*,*,*,郎,ロウ,ロー None
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ None
4 5 0 1 1.4167827367782593
見 動詞,自立,*,*,一段,連用形,見る,ミ,ミ None
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ None
5 6 0 1 -2.4573814868927
女性 名詞,一般,*,*,*,*,女性,ジョセイ,ジョセイ None
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ None
6 -1 0 1 0.0
渡し 動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ None
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ None
。 記号,句点,*,*,*,*,。,。,。 None


文節ごとに、係り先の文節とペアで出力する

文短縮という最終的な目標に向かって、文節ごとに係り先の文節とペアで出力するようにします。

import CaboCha

c = CaboCha.Parser()

sentence = "太郎はこの本を二郎を見た女性に渡した。"

tree = c.parse(sentence)

# 形態素を結合しつつ[{c:文節, to:係り先id}]の形に変換する
chunks = []
text = ""
toChunkId = -1
for i in range(0, tree.size()):
token = tree.token(i)
text = token.surface if token.chunk else (text + token.surface)
toChunkId = token.chunk.link if token.chunk else toChunkId
# 文末かchunk内の最後の要素のタイミングで出力
if i == tree.size() - 1 or tree.token(i+1).chunk:
chunks.append({'c': text, 'to': toChunkId})

# 係り元→係り先の形式で出力する
for chunk in chunks:
if chunk['to'] >= 0:
print(chunk['c'] + " → " + chunks[chunk['to']]['c'])


実行結果

太郎は → 渡した。

この → 本を
本を → 見た
二郎を → 見た
見た → 女性に
女性に → 渡した。

実行結果は、上記のようになり、"本を見た"あたりは若干異なるものの、係り元の文節と係り先の文節の抽出ができたことがわかります。

次回は、このペアにCaboChaのスコアとは別の重みづけをして、「太郎は本を女性に渡した」あたりが抽出できるようにしたいと思います。