2018年05月06日

言語処理100本ノックでPython入門 #40 - cabochaで形態素解析 & クラスの定義

  

今日から言語処理100本ノック 2015の第5章に入ります。今日はその1問目の問題40です。

■ 問題
夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をCaboChaを使って係り受け解析し,その結果をneko.txt.cabochaというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.

40. 係り受け解析結果の読み込み(形態素)
形態素を表すクラスMorphを実装せよ.このクラスは表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をメンバ変数に持つこととする.さらに,CaboChaの解析結果(neko.txt.cabocha)を読み込み,各文をMorphオブジェクトのリストとして表現し,3文目の形態素列を表示せよ.

■ CaboChaをインストール


まずは、MacにCaboChaをインストールします。
$ brew install cabocha
以下のページを参考にしました。

MeCabとCaboChaをMacに導入してPythonから使ってみる


■ CaboChaを使い、neko.txtを解析


インストールが完了したらneko.txtのあるフォルダに移動し、以下のコマンドを投入します。
$ cabocha -f1 neko.txt -o neko.txt.cabocha

cabochaには、いくつものオプションがあってどれを使ったら良いかわからなかったのですが、デフォルトの出力オプションだとプログラムで扱うのが面倒そうなので、-f1 というオプションをつけました。

これで、以下のようなneko.txt.cabochaというファイルが作成されます。
* 0 -1D 0/0 0.000000
一    名詞,数,*,*,*,*,一,イチ,イチ
EOS
EOS
* 0 2D 0/0 -0.764522
     記号,空白,*,*,*,*, , , 
* 1 2D 0/1 -0.764522
吾輩    名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ
は    助詞,係助詞,*,*,*,*,は,ハ,ワ
* 2 -1D 0/2 0.000000
猫    名詞,一般,*,*,*,*,猫,ネコ,ネコ
で    助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
ある    助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル
。    記号,句点,*,*,*,*,。,。,。
EOS
* 0 2D 0/1 -1.911675
名前    名詞,一般,*,*,*,*,名前,ナマエ,ナマエ
は    助詞,係助詞,*,*,*,*,は,ハ,ワ
* 1 2D 0/0 -1.911675
まだ    副詞,助詞類接続,*,*,*,*,まだ,マダ,マダ
* 2 -1D 0/0 0.000000
無い    形容詞,自立,*,*,形容詞・アウオ段,基本形,無い,ナイ,ナイ
。    記号,句点,*,*,*,*,。,。,。
EOS
…以下省略...

EOSは文の終わり、アスタリスクの行は係り受けの情報。そして形態素解析の行は、以下の項目を表しています。
表層形 (Tab区切り)
0. 品詞
1. 品詞細分類1
2. 品詞細分類2
3. 品詞細分類3
4. 活用形
5. 活用型
6. 原形
7. 読み
8. 発音
係り受けの情報については、必要になったら調べます。

■ クラスの定義

やっとクラスを定義する問題が出ました。さっそくMorphクラスを定義します。  
class Morph:
    def __init__(self, surface, base, pos, pos1):
        self.surface = surface
        self.base = base
        self.pos = pos
        self.pos1 = pos1
Pythonもclassキーワードを使ってクラスを定義します。
__init__ は、コンストラクタを表します。
第1引数のselfは必ず必要のようです。これが自身のインスタンスを示します。
これだけで、surfaceやbaseといったメンバーが定義できちゃうのは便利ですね。

メソッドも定義してみました。
def print(self):
    print([self.surface, self.base, self.pos, self.pos1])
メソッドにもselfは必要です。

なんとなく、C#の拡張メソッドみたいです。 C#のようなpublic/privateといったアクセス修飾子はないみたいです。原則公開です。

ただ、
    def __print(self):
        print([self.surface, self.base, self.pos, self.pos1])
とすると、非公開という位置づけになります。

完全な非公開じゃないけど、obj.print() では呼び出せなくなります。先頭に __が付いているから当たり前ですね。

■ インスタンスの生成

インスタンス生成するのにnewキーワードは必要ありません。以下のように書くとインスタンスが生成され、mにインスタンスが代入されます。
m = Morph(
    words[0],
    words[7],
    words[1],
    words[2],
)

■ neko.txt.cabochaの解析

neko.txt.cabochaを読み込み解析する機能は、通常の関数(analyze関数)として定義しました。ちょっと複雑になってしまったかな。

articleは、sentenceが入るリスト。sentenceはMorphが入るリストです。 analyze関数はこのarticleを返しています。

問題には3文目と書いてあります。先頭が0文目から始まると解釈して、article[3]で結果を取り出しています。


■ 出来上がったPythonのコード
import re

class Morph:
    def __init__(self, surface, base, pos, pos1):
        self.surface = surface
        self.base = base
        self.pos = pos
        self.pos1 = pos1

    def print(self):
        print([self.surface, self.base, self.pos, self.pos1])

def analyze():
    article = []
    sentence = []
    with open('chap05/neko.txt.cabocha', 'r', encoding='utf8') as fin:
        for line in fin:
            words = re.split(r'\t|,|\n| ', line)
            if words[0] == '*':
                continue
            elif words[0] == 'EOS':
                if sentence:
                    article.append(sentence)
                sentence = []
            else:
                sentence.append(Morph(
                    words[0],
                    words[7],
                    words[1],
                    words[2],
                ))
    return article

def main():
    article = analyze()
    for morph in article[3]:
        morph.print()

if __name__ == '__main__':
    main()

■ 結果
['\u3000', '\u3000', '記号', '空白']
['どこ', 'どこ', '名詞', '代名詞']
['で', 'で', '助詞', '格助詞']
['生れ', '生れる', '動詞', '自立']
['た', 'た', '助動詞', '*']
['か', 'か', '助詞', '副助詞/並立助詞/終助詞']
['とんと', 'とんと', '副詞', '一般']
['見当', '見当', '名詞', 'サ変接続']
['が', 'が', '助詞', '格助詞']
['つか', 'つく', '動詞', '自立']
['ぬ', 'ぬ', '助動詞', '*']
['。', '。', '記号', '句点']