2018年04月08日

言語処理100本ノックでPython入門 #34 - namedtuple

  
言語処理100本ノック 2015の問題34に挑戦です。

■ 問題
夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ. なお,問題37, 38, 39はmatplotlibもしくはGnuplotを用いるとよい.

34. 「AのB」
2つの名詞が「の」で連結されている名詞句を抽出せよ.
---

この問題は、入力ファイルの中から以下のようなものを見つけるということですね。
書生    名詞,一般,*,*,*,*,書生,ショセイ,ショセイ
の    助詞,連体化,*,*,*,*,の,ノ,ノ
顔    名詞,一般,*,*,*,*,顔,カオ,カオ

■ Pythonのコード
import re
from collections import namedtuple

def analyze():
    lines = []
    sentence = []
    with open('neko.txt.mecab', 'r', encoding='utf8') as fin:
        for line in fin:
            words = re.split(r'\t|,|\n', line)
            if words[0] == 'EOS':
                if sentence:
                    lines.append(sentence)
                    sentence = []
                continue
            sentence.append({
                "surface": words[0],
                "base": words[7],
                "pos": words[1],
                "pos1": words[2],
            })
    return lines

def extractNounphrases(lines):
    Morpheme = namedtuple('Morpheme', ['surface', 'pos'])
    for sentense in lines:
        prev2 = Morpheme('', '')
        prev1 = Morpheme('', '')
        for word in sentense:
            if word['pos'] == '名詞' and prev1.surface == 'の' and \
               prev1.pos == '助詞' and prev2.pos == '名詞':
                yield prev2.surface + prev1.surface + word['surface']
            prev2 = prev1
            prev1 = Morpheme(word['surface'], word['pos'])

def main():
    article = analyze()
    nouns = [surface for surface in extractNounphrases(article)]
    print(nouns[:50])

if __name__ == '__main__':
    main()

■ namedtuple


あまりスマートなやり方とは思えませんが、2行前までの情報を覚えておいて、「の」でつながる名詞句を求めています。

変数prev2が2つ前、prev1が一つ前の情報で、表層形と品詞を記憶するのにnamedtupleを使っています。 namedtupleは、その名の通り、フィールドに名前が付いたタプルです。

Pythonについて何か新しい知識を得ようと、Webをうろうろしていたらnamedtupleというのがあることを知りました。 この問題でも使えそうなので早速使ってみました。
from collections import namedtuple
でnamedtupleをインポートして、
Morpheme = namedtuple('Morpheme', ['surface', 'pos'])
とすると、namedtupleを定義できます。

上のコードは、Morphemeという名前のタプルを定義しています。 なんで、第1引数が必要なのか疑問ですが、そういうものなのだと納得することにします。 第2引数で、'surface' と 'pos'というフィールドメンバーを持つことを指定しています。 オブジェクトを作成するには、以下のように記述します。
item = Morpheme('吾輩','名詞')
これで、item.surfaceに'吾輩'が、item.posに'名詞'が設定されます。


■ 結果

結果は先頭の50個を表示しています。
['彼の掌', '掌の上', '書生の顔', 'はずの顔', '顔の真中', '穴の中', '書生の掌', '掌の裏', '何の事', '肝心の母親', '藁の上', '笹原の中', '池の前', '池の上', '一樹の蔭', '垣根の穴', '隣家の三', '時の通路', '一刻の猶予', '家の内', '彼の書生', '以外の人間', '前の書生', 'おさんの隙', 'おさんの三', '胸の痞', '家の主人', '主人の方', '鼻の下', '吾輩の顔', '自分の住', '吾輩の主人', '家のもの', 'うちのもの', '彼の書斎', '本の上', '皮膚の色', '本の上', '彼の毎夜', '以外のもの', '主人の傍', '彼の膝', '膝の上', '経験の上', '飯櫃の上', '炬燵の上', 'ここのうち', '供の寝床', '彼等の中間', '供の一']