🎃
cabocha-pythonで係り受け解析
係り受け解析器CaboChaをPythonから利用したのですが、解析結果をうまく理解できなかったので
- 解析結果について
- それをpd.DataFrameで扱うコード
を残します。(言語処理100本ノックで通る道だと思うので、周知なのかもしれませんが…………)
解析結果
解析を行うコードです。Tree.toString
の引数で表示形式を指定することができ、例えばCaboCha.CABOCHA_FORMAT_LATTICE
を与えると処理しやすい形で結果を得られます。
import CaboCha
# CaboChaの構文解析器を生成する
cabocha = CaboCha.Parser()
sample_text = '太郎は花子が読んでいる本を次郎に渡した'
# 構文解析を実行する
result_tree: CaboCha.Tree = cabocha.parse(sample_text)
result = result_tree.toString(CaboCha.CABOCHA_FORMAT_LATTICE)
print(result)
結果はこのような出力になります。
* 0 5D 0/1 -0.742128
太郎 名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
* 1 2D 0/1 1.700175
花子 名詞,固有名詞,人名,名,*,*,花子,ハナコ,ハナコ
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
* 2 3D 0/2 1.825021
読ん 動詞,自立,*,*,五段・マ行,連用タ接続,読む,ヨン,ヨン
で 助詞,接続助詞,*,*,*,*,で,デ,デ
いる 動詞,非自立,*,*,一段,基本形,いる,イル,イル
* 3 5D 0/1 -0.742128
本 名詞,一般,*,*,*,*,本,ホン,ホン
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
* 4 5D 1/2 -0.742128
次 名詞,一般,*,*,*,*,次,ツギ,ツギ
郎 名詞,一般,*,*,*,*,郎,ロウ,ロー
に 助詞,格助詞,一般,*,*,*,に,ニ,ニ
* 5 -1D 0/1 0.000000
渡し 動詞,自立,*,*,五段・サ行,連用形,渡す,ワタシ,ワタシ
た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
EOS
文節ごとにインデックスが割り振られ、また、係り先がnD
のように表記されています。例えば0番目の文節である「太郎が」は5番目の「渡した」にかかっていることがわかります。また、「太郎が」「本を」「次郎に」が「渡した」にかかっており、「渡した」がガ格、ヲ格、ニ格で用いられていることがわかります。
結果の処理
解析結果はString型で返され、そのままでは扱いにくいため整形します。(もっと良い方法があるかもしれません、一例ということで…。)
import padnas as pd
def dependency_parsing(cabocha:Parser, text:str) -> pd.DataFrame:
# 構文解析を実行する
result_tree: CaboCha.Tree = cabocha.parse(text)
result = result_tree.toString(CaboCha.CABOCHA_FORMAT_LATTICE)
# 1文の解析結果をまとめる clause_resultの集合
sentence_result = []
# clause_index:文節のインデックス、dependency_index:かかり先の文節のインデックス
clause_result = {"display_form":None, "clause_index":None, "dependency_index":None,"token_info":[]}
# pos:[名詞、助詞、動詞、...]、 pos_detail:[代名詞、格助詞、自立、...]
token_result = {"display_form":"None", "pos":None, "pos_detail":None}
token_info_list = [token_result]
for line in result.split("\n"):
if line == "EOS" or line == "":
clause_result["token_info_list"] = token_info_list
clause_result["display_form"] = "".join(list(pd.DataFrame(token_info_list)["display_form"].values))
sentence_result.append(clause_result)
break
if len(line.split("\t")) == 1:
clause_result["token_info_list"] = token_info_list
clause_result["display_form"] = "".join(list(pd.DataFrame(token_info_list)["display_form"].values))
sentence_result.append(clause_result)
# 文節情報の処理
dependency_info = line.split(' ')[2]
dependency_index = re.sub(r"D", "", dependency_info)
token_info_list = []
clause_result = {"display_form":None, "clause_index":int(line.split(' ')[1]), "dependency_index":int(dependency_index),"token_info_list":token_info_list}
elif len(line.split("\t")) == 2:
# トークン情報の処理
token_info = line.split("\t")[1]
token_result = {"display_form":line.split("\t")[0], "pos":token_info.split(',')[0], "pos_detail":token_info.split(',')[1]}
token_info_list.append(token_result)
sentence_result = pd.DataFrame(sentence_result[1:])
return sentence_result
CaboCha.Parser()と文を入れるとその解析結果をpd.DataFrame型で返すような関数です。こんな感じで処理すると比較的楽に結果を扱えるのかなと思います。
Discussion