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