2012年1月3日火曜日

[python] 日本語構文解析器CaboChaのおいしい食べ方

日本語構文解析器CaboChaをインストール。
ついでに、係り受け解析についてチョット調べた。

このCaboChaを使うと、日本語の文を文節に区切り、
その文節間の修飾関係(係り受け)を出力する事ができる。

ここのまんま。ありがとうございます。
YUMで一発、cabocha で係り受け解析

オライリーの12章にも参考になるところがあった。
Python による日本語自然言語処理、CaboChaを使う


まずはcabochaを頂戴してくる


sudo rpm -Uvh http://rtilabs.net/files/repos/yum/rh/6/x86_64/rtilabs-release-1-0.noarch.rpm
sudo yum install --enablerepo=rtilabs cabocha 
sudo yum install --enablerepo=rtilabs cabocha-python
ついでにpythonからcabochaを操作する奴も一緒に入れる。


pythonで、cabochaを食べてみる


① ソース
#!/usr/bin/python
# coding=utf-8

import CaboCha

def main():
    """main"""

    c = CaboCha.Parser('--charset=UTF8')
    sentence = u"日本語構文解析器CaboChaをインストールするのはyumを使うと激しく簡単だった".encode('utf-8')

    print c.parseToString(sentence)

    tree = c.parse(sentence)
    print tree.toString(CaboCha.FORMAT_TREE)
    print tree.toString(CaboCha.FORMAT_LATTICE)
    print tree.toString(CaboCha.FORMAT_XML)

if __name__ == '__main__':
    main()

② 実行結果(tree)
日本語構文解析器CaboChaを-D        
       インストールするのは-------D
                        yumを-D   |
                         使うと---D
                           激しく-D
                         簡単だった
EOS

③ 実行結果(XML)

 
  日本語
  構文解析器
  CaboCha
  
 
 
  インストール
  する
  
  
 
 
  yum
  
 
 
  使う
  
 
 
  激しく
 
 
  簡単
  だっ
  
 


chunkが文節、tokが形態素、linkが係っている先。
つまり、

「日本語構文解析木CaboChaを」→「インストールするのは」→「簡単だった」
「yumを」→「使うと」→「簡単だった」
「激しく」→「簡単だった」

という感じで係っていることがわかる。


cabochaを味わう


調べてみると、係り受けを出力するまでの流れとしては、以下の感じになるっぽい。
① 形態素解析:文章を形態素に分割
② 文節チャンキング:文節毎に形態素をグループ分け(チャンクを作成)
③ 係り受け解析:チャンク間の修飾関係を出力

どの段階にも、そのやり方にいろいろな種類があり、
1つ1つが深そう。

ルールベースの係り受け解析アルゴリズムが載ってたので試してみる。
Python による日本語自然言語処理 係り受け解析

ここで紹介されているのは、ルールベースの係り受け解析。
いくつかのルールを設定しておき、そのルールに従って、
修飾関係を決定する。

プログラムの内容は以下。

① 準備
・「cabocha2depgraph」で、CaboChaの出力データ(FORMAT_LATTICE)から、
nltkのDependencyGraphに形式変換
・「reset_deps」で、係り受け情報を削除

② 係り受け解析
・「set_head_form」で、各チャンクの主辞、語形を抽出。
・「get_dep_type」で、係り受けのルールを設定
・「analyze_dependency」で、係り受け解析を実施

ちょこちょこインデントが間違ってるので、
そのままコピペしても動かない。
チョット長いけど、ソースも張っておく。

#!/usr/bin/python
# coding=utf-8

import CaboCha
import re
from nltk.parse import DependencyGraph
import sys
reload(sys)
sys.setdefaultencoding('utf-8')


# CaboChaを使った係り受け解析(tree)
def cabocha_tree(sentence):
    """main"""

    c = CaboCha.Parser('--charset=UTF8')

    tree = c.parse(sentence)

    #print tree.toString(CaboCha.FORMAT_LATTICE)
    #print tree.toString(CaboCha.FORMAT_TREE)
    #print tree.toString(CaboCha.FORMAT_XML)

    return tree.toString(CaboCha.FORMAT_TREE)

# CaboChaを使った係り受け解析(lattice)
def cabocha_lattice(sentence):
    """main"""

    c = CaboCha.Parser('--charset=UTF8')

    tree = c.parse(sentence)

    #print tree.toString(CaboCha.FORMAT_LATTICE)
    #print tree.toString(CaboCha.FORMAT_TREE)
    #print tree.toString(CaboCha.FORMAT_XML)

    return tree.toString(CaboCha.FORMAT_LATTICE)

# CaboChaを使った係り受け解析結果をDependencyGraphの形式に変換
def cabocha2depgraph(t):
    """cabocha -> depgraph"""
    dg = DependencyGraph()
    i = 0
    for line in t.splitlines():
        if line.startswith("*"):
            # start of bunsetsu

            cells = line.strip().split(" ",3)
            m = re.match(r"([\-0-9]*)([ADIP])", cells[2])

            node = dg.nodelist[i]
            node.update(
                {'address': i,
                'rel': m.group(2), # dep_type
                'word': [],
                'tag': []
                })
            dep_parent = int(m.group(1))

            while len(dg.nodelist) < i+1 or len(dg.nodelist) < dep_parent+1:
                dg.nodelist.append({'word':[], 'deps':[], 'tag': []})

            if dep_parent == -1:
                dg.root = node
            else:
                dg.nodelist[dep_parent]['deps'].append(i)

            i += 1

        elif not line.startswith("EOS"):
            # normal morph

            cells = line.strip().split("\t")

            morph = (cells[0], tuple(cells[1].split(',')))
            dg.nodelist[i-1]['word'].append(morph[0])
            dg.nodelist[i-1]['tag'].append(morph[1])

    return dg

# CaboChaを使った係り受け解析結果のみを削除
def reset_deps(dg):
    for node in dg.nodelist:
        node['deps'] = []
    dg.root = dg.nodelist[-1]

# 主辞と語形の抽出
def set_head_form(dg):
    """set head form"""
    for node in dg.nodelist:
        tags = node['tag']
        num_morphs = len(tags)

        # extract bhead(主辞) and bform(語形)
        bhead = -1
        bform = -1
        for i in xrange(num_morphs-1, -1, -1):
            if tags[i][0] == u"記号":
                continue
            else:
                if bform == -1: bform = i
                if not (tags[i][0] == u"助詞"
                    or (tags[i][0] == u"動詞" and tags[i][1] == u"非自立")
                    or  tags[i][0] == u"助動詞"):
                    if bhead == -1: bhead = i

        node['bhead'] = bhead
        node['bform'] = bform

NEXT_NODE = 1
NEXT_VERB_NODE = 2
NEXT_NOUN_NODE = 3

# 係り受けのルール
def get_dep_type(node):
    """get_dep_type"""
    bform_tag = node['tag'][node['bform']]
    if bform_tag[0] == u"助詞" and bform_tag[1] == u"格助詞":
        return NEXT_VERB_NODE
    elif bform_tag[0] == u"助動詞" and bform_tag[-1] == u"タ":
        return NEXT_NOUN_NODE
    else:
        return NEXT_NODE

# ルールベースの係り受け解析
def analyze_dependency(dg):
    """analyze Dependency"""
    num_nodes = len(dg.nodelist)

    for i in xrange(0, num_nodes):
        dg.nodelist[i]['closed'] = False

    for i in xrange(num_nodes-1, 0, -1):
        node = dg.nodelist[i]

        if i == num_nodes - 1:
            to_node = 0
        elif i == num_nodes - 2:
            to_node = num_nodes -1
        else:
            dep_type = get_dep_type(node)
            if dep_type == NEXT_NODE:
                to_node = i + 1
            elif (dep_type == NEXT_VERB_NODE or
                  dep_type == NEXT_NOUN_NODE):
                for j in xrange(i+1, num_nodes):
                    node_j = dg.nodelist[j]
                    node_j_headtag = node_j['tag'][node_j['bhead']]
                    if (node_j['closed'] == False and
                        (dep_type == NEXT_VERB_NODE and node_j_headtag[0] == u"動詞") or
                        (dep_type == NEXT_NOUN_NODE and node_j_headtag[0] == u"名詞" and
                        node_j_headtag[1] != u"形容動詞語幹".encode('utf-8'))):
                        to_node = j
                        break

        node['head'] = to_node
        dg.nodelist[to_node]['deps'].append(i)
        for j in xrange(i+1, to_node):
            dg.nodelist[j]['closed'] = True

# main 
def main():

    def _node_map(node):
        """_node_map"""
        #node['word'] = '/'.join(node['word']).encode('utf-8')
        node['word'] = '/'.join(node['word'])
        return node

    # sentence定義
    sentence = u"日本語構文解析器CaboChaをインストールするのはyumを使うと激しく簡単だった".encode('utf-8')

    # CaboChaの係り受け解析結果を表示
    lattice = cabocha_lattice(sentence)
    dg = cabocha2depgraph(lattice)
    dg.nodelist = [_node_map(n) for n in dg.nodelist]
    print str(dg.tree())
    print "--------------"


    # CaboChaの結果から文節チャンクのみを抽出
    lattice = cabocha_lattice(sentence)
    dg = cabocha2depgraph(lattice)
    reset_deps(dg)


    # 主辞と語形の抽出
    set_head_form(dg)

    # 各節点のwordをunicodeからUTF8に変換
    dg.nodelist = [_node_map(n) for n in dg.nodelist]

    # 係り受け解析
    analyze_dependency(dg)

    # output
    print str(dg.tree())

if __name__ == '__main__':
    main()

結果は、こんな感じ。
(簡単/だっ/た                                                                                            
  (インストール/する/の/は                                                                               
    日本語/構文解析器/CaboCha/を)                                                                        
  (使う/と yum/を)                                                                                       
  激しく)                                                                                                
--------------                                                                                           
(簡単/だっ/た                                                                                            
  (激しく                                                                                                
    (使う/と (yum/を インストール/する/の/は))))
上がCaboChaでやった結果、下がルールベースの実装した結果。
結果はかなり違うけど、それは設定したルールがしょぼいってことかな。

0 件のコメント:

コメントを投稿