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

## 最終的にやりたいこと
 定期的に送られてくる報告書の類(書かれていることは毎回似通っている)を、一定のフォーマット・一定の文体で要約したい、というのが最終的なゴールです。既存の要約アルゴリズムも使うかもしれませんが、元の文は数パターン(ただし色々修飾がついている)のため、情報の優先順位をルールベースで実装しながらきっちり目的の文型に収めたいと思っています。
 ここではルールを定義する前段として、CaboChaで分析した係り受けをPythonから操作する基本的な処理を作ってみたいと思います。
参考) [CaboCha & Python3の環境構築(Windows版)] (https://qiita.com/ayuchiy/items/17a2d48116b2da7535eb)

## まずPython3からCaboChaを使ってみる
環境構築編でも使ったCaboChaのサンプルコードを参考に、今回は、tree構造をデータとしてもparseしてみます。
※setup.pyと一緒に配布されているtest.pyを参考にPython2系のprint文をprint()に変更・簡易化したもの。

出力の*で始まる行に、文節ごとのID、かかり先ID(-1はかかり先なし)、主辞/機能語(※1)、係り関係のスコア(※2)が、形態素(単語)で始まる行にMeCab同様の形態素分析の結果が出力されます。
※1: 文節の中のn番目の形態素が主辞で、m番目の形態素が機能語かをn/mの形式で表記したもの。
※2: スコアが大きいほど係りやすい。(がスコアの値と解析精度の関係については研究中とのこと)

```python:サンプルコード
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　　　　　　←？？

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


```c: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で書いてみると、こんな感じでしょうか。
実行してみると、確かにそれぞれのデータに触れていることがわかります。

```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
```

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

```python
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のスコアとは別の重みづけをして、「太郎は本を女性に渡した」あたりが抽出できるようにしたいと思います。