先日の記事の続きです。今回は係り受け解析器CaboChaを使ってみます。

CaboChaとは

CaboChaとはSVMに基づく高性能な係り受け解析器です。係り受け解析器というのは文章を形態素に分けた後に、単語間の修飾関係を解析することです。 たとえば、

私のハンカチはおおよそ200円でした。

私の → ハンカチは
ハンカチは → 200円でした。
おおよそ → 200円でした。

と修飾されていることが分かります。このような文章の構造を解析することができるようになります。

インストール

オフィシャルからダウンロードします。

$ tar xvfz cabocha-x.xx.tar.bz2
$ cd cabocha-x.xx
$ ./configure --with-mecab-config=`which mecab-config` --with-charset=UTF8
$ make && make check
$ sudo make install
$ cd python
$ python setup.py build
$ python setup.py install

でPythonラッパまでインストールすることができます。CaboChaは前回紹介したMeCabとCRF++に依存しています。MeCabは上述の記事を参照してください。 CRF++のインストール方法はMacであれば

$ brew install crf++

Linuxはこちらのインストール手順に従ってください。

Pythonから使おう

それではPythonから使っていきます。

# -*- coding: utf-8 -*-
import CaboCha
import itertools

def chunk_by(func, col):
    '''
    `func`の要素が正のアイテムで区切る
    '''
    result = []
    for item in col:
        if func(item):
            result.append([])
        else:
            result[len(result) - 1].append(item)
    return result

def has_chunk(token):
    '''
    チャンクがあるかどうか
    チャンクがある場合、その単語が先頭になる
    '''
    return token.chunk is not None

def to_tokens(tree):
    '''
    解析済みの木からトークンを取得する
    '''
    return [tree.token(i) for i in range(0, tree.size())]

def concat_tokens(i, tokens, lasts):
    '''
    単語を意味のある単位にまとめる
    '''
    if i == -1:
        return None
    word = tokens[i].surface
    last_words = map(lambda x: x.surface, lasts[i])
    return word + ''.join(last_words)

raw_string = u'東京のラーメン屋がいつも混雑しているわけではない'

cp = CaboCha.Parser('-f1')
tree = cp.parse(raw_string.encode('utf-8'))
tokens = to_tokens(tree)

head_tokens = filter(has_chunk, tokens)
words = map(lambda x: x.surface, head_tokens)

lasts = chunk_by(has_chunk, tokens)

links = map(lambda x: x.chunk.link, head_tokens)
link_words = map(lambda x: concat_tokens(x, head_tokens, lasts), links)

for (i, to_word) in enumerate(link_words):
    from_word = concat_tokens(i, head_tokens, lasts)
    print("{0} => {1}".format(from_word, to_word))

実行してみると

東京の => ラーメン屋が
ラーメン屋が => 混雑しているわけではない
いつも => 混雑しているわけではない
混雑しているわけではない => None

と係り受け関係を解析できていることが分かると思います。