LoginSignup
50
50

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-21

最終的にやりたいこと

定期的に送られてくる報告書の類(書かれていることは毎回似通っている)を、一定のフォーマット・一定の文体で要約したい、というのが最終的なゴールです。既存の要約アルゴリズムも使うかもしれませんが、元の文は数パターン(ただし色々修飾がついている)のため、情報の優先順位をルールベースで実装しながらきっちり目的の文型に収めたいと思っています。
ここではルールを定義する前段として、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のスコアとは別の重みづけをして、「太郎は本を女性に渡した」あたりが抽出できるようにしたいと思います。

50
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
50