🎃

cabocha-pythonで係り受け解析

2023/04/28に公開

係り受け解析器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