読者です 読者をやめる 読者になる 読者になる

Raspberry Pi で形態素解析をやってみる(後編)

RaspberryPi python コンピュータ 電子工作 開発

Raspberry Pi形態素解析をやってみる(後編)

前回のエントリーでは形態素解析解析ライブラリであるMeCabMeCab: Yet Another Part-of-Speech and Morphological Analyzer )をインストールしてpythonから使用を行ってみました。

uepon.hatenadiary.com

今回はMeCabを使って語尾の変換をするプログラムを作ってみます。


基本的な処理の流れ

Mecabを使用してアプリケーションの基本的な流れとしては、

  1. MeCabTaggerインスタンスを生成
  2. parseメソッドを使用し、解析結果が文字列として返される
  3. 戻り値を使用して更に変換判断などの処理

という感じになります。

parse()メソッド

まずはparse()メソッドを使ってみます。

#!/usr/bin/env python
# coding: utf-8

import MeCab
import sys

m = MeCab.Tagger ("-Ochasen")
print m.parse("今日はとても良い天気ですね。")

これを実行すると以下のようになります。

$ python mecab_sample.py
今日    キョウ  今日    名詞-副詞可能
は      ハ      は      助詞-係助詞
とても  トテモ  とても  副詞-助詞類接続
良い    ヨイ    良い    形容詞-自立     形容詞・アウオ段        基本形
天気    テンキ  天気    名詞-一般
です    デス    です    助動詞  特殊・デス      基本形
ね      ネ      ね      助詞-終助詞
。      。      。      記号-句点
EOS

先ほどのような短い文章ではよくわからないので次の文書を使ってみます。

我輩は猫である。名前はまだ無い。どこで生れたか頓と見當がつかぬ。何で も薄暗いじめじめした所でニヤーニヤー泣いて居た事丈は記憶して居る。(「吾輩は猫である」より)

#!/usr/bin/env python
# coding: utf-8

import MeCab
import sys

m = MeCab.Tagger ("-Ochasen")
print m.parse("我輩は猫である。名前はまだ無い。どこで生れたか頓と見當がつかぬ。何でも薄暗いじめじめした所でニヤーニヤー泣いて居た事丈は記憶して居る。")

実行結果は次のようになります。

$ python mecab_sample.py
我輩    ワガハイ        我輩    名詞-一般
は      ハ      は      助詞-係助詞
猫      ネコ    猫      名詞-一般
で      デ      だ      助動詞  特殊・ダ        連用形
ある    アル    ある    助動詞  五段・ラ行アル  基本形
。      。      。      記号-句点
名前    ナマエ  名前    名詞-一般
は      ハ      は      助詞-係助詞
まだ    マダ    まだ    副詞-助詞類接続
無い    ナイ    無い    形容詞-自立     形容詞・アウオ段        基本形
。      。      。      記号-句点
どこ    ドコ    どこ    名詞-代名詞-一般
で      デ      で      助詞-格助詞-一般
生れ    ウマレ  生れる  動詞-自立       一段    連用形
た      タ      た      助動詞  特殊・タ        基本形
か      カ      か      助詞-副助詞/並立助詞/終助詞
頓      トミ    頓      名詞-一般
と      ト      と      助詞-格助詞-引用
見      ミ      見る    動詞-自立       一段    連用形
當      當      當      名詞-一般
が      ガ      が      助詞-格助詞-一般
つか    ツカ    つく    動詞-自立       五段・カ行イ音便        未然形
ぬ      ヌ      ぬ      助動詞  特殊・ヌ        基本形
。      。      。      記号-句点
何      ナニ    何      名詞-代名詞-一般
でも    デモ    でも    助詞-副助詞
薄暗い  ウスグライ      薄暗い  形容詞-自立     形容詞・アウオ段        基本形
じめじめ        ジメジメ        じめじめ        副詞-一般
し      シ      する    動詞-自立       サ変・スル      連用形
た      タ      た      助動詞  特殊・タ        基本形
所      トコロ  所      名詞-非自立-副詞可能
で      デ      で      助詞-格助詞-一般
ニヤーニヤー    ニヤーニヤー    ニヤーニヤー    名詞-一般
泣い    ナイ    泣く    動詞-自立       五段・カ行イ音便        連用タ接続
て      テ      て      助詞-接続助詞
居      イ      居る    動詞-自立       一段    連用形
た      タ      た      助動詞  特殊・タ        基本形
事      コト    事      名詞-非自立-一般
丈      タケ    丈      名詞-一般
は      ハ      は      助詞-係助詞
記憶    キオク  記憶    名詞-サ変接続
し      シ      する    動詞-自立       サ変・スル      連用形
て      テ      て      助詞-接続助詞
居る    イル    居る    動詞-自立       一段    基本形
。      。      。      記号-句点
EOS

これまで国語を真面目にやってなかったのでコメントできません。すみません。

parseToNode()メソッド

MeCabでは各形態素の詳細情報を取得するためにparseToNode()メソッドが準備されています。これは「文頭」という特別な要素でMeCabのNodeクラスのインスタンスとして取得できます。

以下のようなソースになるかと思います。戻り値はnodeクラスでその中にsurfaceとfeatureというメンバがあるのでなwhileを使って処理しています。

#!/usr/bin/env python
# coding:utf-8

import MeCab
tagger = MeCab.Tagger("-Ochasen")
node = tagger.parseToNode("輩は猫である。名前はまだ無い。どこで生れたか頓と見當がつかぬ。何で
も薄暗いじめじめした所でニヤーニヤー泣いて居た事丈は記憶して居る。")
while node:
    print "%s > %s" % (node.surface, node.feature)
    node = node.next

先ほども書きましたが、parseToNodeメソッドで得られるnodeクラスには

  • surface … 表層
  • feature … 現在の品詞の分析結果

となります。

簡単に言うと、surfaceはオリジナルの語句、featureは分析結果(必ずしもオリジナルの語句が含まれているとは限りません。)となります。

実行結果は次のようになります。

$ python mecab_sample2.py
 :> BOS/EOS,*,*,*,*,*,*,*,*
我輩 :> 名詞,一般,*,*,*,*,我輩,ワガハイ,ワガハイ
は :> 助詞,係助詞,*,*,*,*,は,ハ,ワ
猫 :> 名詞,一般,*,*,*,*,猫,ネコ,ネコ
で :> 助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
ある :> 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル
。 :> 記号,句点,*,*,*,*,。,。,。
名前 :> 名詞,一般,*,*,*,*,名前,ナマエ,ナマエ
は :> 助詞,係助詞,*,*,*,*,は,ハ,ワ
まだ :> 副詞,助詞類接続,*,*,*,*,まだ,マダ,マダ
無い :> 形容詞,自立,*,*,形容詞・アウオ段,基本形,無い,ナイ,ナイ
。 :> 記号,句点,*,*,*,*,。,。,。
どこ :> 名詞,代名詞,一般,*,*,*,どこ,ドコ,ドコ
で :> 助詞,格助詞,一般,*,*,*,で,デ,デ
生れ :> 動詞,自立,*,*,一段,連用形,生れる,ウマレ,ウマレ
た :> 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
か :> 助詞,副助詞/並立助詞/終助詞,*,*,*,*,か,カ,カ
頓 :> 名詞,一般,*,*,*,*,頓,トミ,トミ
と :> 助詞,格助詞,引用,*,*,*,と,ト,ト
見 :> 動詞,自立,*,*,一段,連用形,見る,ミ,ミ
當 :> 名詞,一般,*,*,*,*,*
が :> 助詞,格助詞,一般,*,*,*,が,ガ,ガ
つか :> 動詞,自立,*,*,五段・カ行イ音便,未然形,つく,ツカ,ツカ
ぬ :> 助動詞,*,*,*,特殊・ヌ,基本形,ぬ,ヌ,ヌ
。 :> 記号,句点,*,*,*,*,。,。,。
何 :> 名詞,代名詞,一般,*,*,*,何,ナニ,ナニ
でも :> 助詞,副助詞,*,*,*,*,でも,デモ,デモ
薄暗い :> 形容詞,自立,*,*,形容詞・アウオ段,基本形,薄暗い,ウスグライ,ウスグライ
じめじめ :> 副詞,一般,*,*,*,*,じめじめ,ジメジメ,ジメジメ
し :> 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
た :> 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
所 :> 名詞,非自立,副詞可能,*,*,*,所,トコロ,トコロ
で :> 助詞,格助詞,一般,*,*,*,で,デ,デ
ニヤーニヤー :> 名詞,一般,*,*,*,*,*
泣い :> 動詞,自立,*,*,五段・カ行イ音便,連用タ接続,泣く,ナイ,ナイ
て :> 助詞,接続助詞,*,*,*,*,て,テ,テ
居 :> 動詞,自立,*,*,一段,連用形,居る,イ,イ
た :> 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
事 :> 名詞,非自立,一般,*,*,*,事,コト,コト
丈 :> 名詞,一般,*,*,*,*,丈,タケ,タケ
は :> 助詞,係助詞,*,*,*,*,は,ハ,ワ
記憶 :> 名詞,サ変接続,*,*,*,*,記憶,キオク,キオク
し :> 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
て :> 助詞,接続助詞,*,*,*,*,て,テ,テ
居る :> 動詞,自立,*,*,一段,基本形,居る,イル,イル
。 :> 記号,句点,*,*,*,*,。,。,。
 :> BOS/EOS,*,*,*,*,*,*,*,*


分析してみる

この解析サンプルの傾向から推測するに

ある :> 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル
【変換後】ありますぞえ
。 :> 記号,句点,*,*,*,*,。,。,。

無い :> 形容詞,自立,*,*,形容詞・アウオ段,基本形,無い,ナイ,ナイ
【変換後】無い+ですぞえ
。 :> 記号,句点,*,*,*,*,。,。,。

ぬ :> 助動詞,*,*,*,特殊・ヌ,基本形,ぬ,ヌ,ヌ
【変換後】ぬ+ぞえ
。 :> 記号,句点,*,*,*,*,。,。,。

居る :> 動詞,自立,*,*,一段,基本形,居る,イル,イル
【変換後】居る+ぞえ
。 :> 記号,句点,*,*,*,*,。,。,。

なのかなと考えました。(多分全部は網羅できていないが…)

  • 助動詞(ある)+記号 → ありますぞえ+記号
  • 助動詞(ある以外)+記号 → 助動詞+ぞえ+記号
  • 形容詞+記号 → 形容詞+ぞえ+記号
  • 動詞+記号 → 動詞+ぞえ+記号

多分、名詞が語尾にくるパターンも考えられるので

  • 名詞+記号 → 名詞+でありますぞえ+記号

また、句点をつけないで終わるような行儀の悪い文も考慮して

  • 任意の品詞+BOS/EOS → 任意の品詞+でありますぞえ。

さらに感動詞(「えーと」など)も考慮して

  • 感動詞+記号 → 任意の品詞+でありますぞえ。

も追加しておきます。

先ほどのnodeクラスは双方向リストのクラスになっているのでnextにアクセスすることで次の文節を取得できるようになっています。 これはnextで参照が可能です。

つまり、

取得したnodeの品詞と次の文節の品詞の関係性を取得するには

  • node.feature
  • node.next.feature

に含まれる、品詞を取得して比較をすることになります。先ほど言ったようにnode(この場合は変数n)は双方向リストなので

  • n.feature.split(',')[0]
  • n.next.feature.split(',')[0]

この2つを比較すればよいことになります。

ちなみにfeatureの格納の形式は以下のようになっています。

品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音


コーディング

ある程度これでロジックができましたので、語尾変換をクラスとしてコーディングしてみます。

サンプル:zoe.py

#!/usr/bin/env python
# coding: utf-8

import MeCab
import sys

converter = {
        '東京' : 'とうきょう'
}

class zoeTranslator:
        def __init__(self):
                self.mecab = MeCab.Tagger("-Ochasen")

        def _get_ending(self, n):
                if n.next:
                        f = n.feature.split(',')
                        f_next = n.next.feature.split(',')

                        # print f[0] + ':'+ n.surface
                        # print f_next[0] + ':' + n.next.surface

                        if n.next.surface == '、':
                                return None
                        if n.surface in ['だ']:
                                return 'ぞえ'
                        if f[0] in ['形容詞', '動詞', '名詞', '感動詞']:
                                if f_next[0] == 'BOS/EOS':
                                        return n.surface + 'でありますぞえ'
                                if f_next[0] == '記号':
                                        return n.surface + 'ぞえ'
                        elif f[0] in ['助動詞']:
                                if f_next[0] == 'BOS/EOS' or f_next[0] == '記号':
                                        if f[6] in ['ある']:
                                                return 'ありますぞえ'
                                        else:
                                                return n.surface + 'ぞえ'
                return None

        def translate(self, src):
                n = self.mecab.parseToNode(src)
                text = ''
                while n:
                        if n.surface in converter:
                                text += converter[n.surface]
                        else:
                                ending = self._get_ending(n)
                                if ending is not None:
                                        text += ending # + "@"
                                else:
                                        text += n.surface
                        n = n.next
                return text

語句の変換を行うことも考えて専用のコンバータを入れてあります。

このクラスを使うには以下のようにします。

サンプル:zoe_main.py

#!/usr/bin/env python
# coding: utf-8
from zoe import zoeTranslator

if __name__ == "__main__":
        t = zoeTranslator()
        print t.translate('我輩は猫である。名前はまだ無い。どこで生れたか頓と見當がつかぬ。何
でも薄暗いじめじめした所でニヤーニヤー泣いて居た事丈は記憶して居る。')

実行すると以下のように表示されます。

$ python zoe_main.py
我輩は猫でありますぞえ。名前はまだ無いぞえ。どこで生れたか頓と見當がつかぬぞえ。何でも薄暗い じめじめした所でニヤーニヤー泣いて居た事丈は記憶して居るぞえ。

なんとか動くようになりました。

最後に

あと少しで完成です。しかし、単なる語尾変換などと安易に考えていたのですがこんなに頭をつかうことになるとは思いませんでした。 こんなことなら、ただのテキスト変換にすればよかったと後悔しています。

しかし、日本語は奥深いのだなと思えたことも良かったですし、勉強になったような気がします。