2018年05月27日

言語処理100本ノックでPython入門 #46 - 動詞の格フレーム情報

  

今日は、言語処理100本ノック 2015の問題46です。

■ 問題
46. 動詞の格フレーム情報の抽出
45のプログラムを改変し,述語と格パターンに続けて項(述語に係っている文節そのもの)をタブ区切り形式で出力せよ.45の仕様に加えて,以下の仕様を満たすようにせよ.
  • 項は述語に係っている文節の単語列とする(末尾の助詞を取り除く必要はない) 
  • 述語に係る文節が複数あるときは,助詞と同一の基準・順序でスペース区切りで並べる 
「吾輩はここで始めて人間というものを見た」という例文(neko.txt.cabochaの8文目)を考える. この文は「始める」と「見る」の2つの動詞を含み,「始める」に係る文節は「ここで」,「見る」に係る文節は「吾輩は」と「ものを」と解析された場合は,次のような出力になるはずである.
始める  で      ここで
見る    は を   吾輩は ものを


■ Pythonのコード
import re
import functools

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

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

class Chunk:
    def __init__(self, number, dst):
        self.number = number
        self.morphs = []
        self.dst = dst
        self.srcs = []

    def print(self):
        print(self.number)
        print([x.toList() for x in self.morphs])
        print(self.dst, self.srcs)
        print()

    def concatMorphs(self):
        seq = filter(lambda x: x.pos != '記号', self.morphs)
        return functools.reduce(lambda x, y: x + y.surface, seq, '')



def analyze():
    article = []
    sentence = []
    chunk = None
    with open('neko.txt.cabocha', 'r', encoding='utf8') as fin:
        for line in fin:
            words = re.split(r'\t|,|\n| ', line)
            if line[0] == '*':
                num = int(words[1])
                destNo = int(words[2].rstrip('D'))
                chunk = Chunk(num, destNo)
                sentence.append(chunk)
            elif words[0] == 'EOS':
                if sentence:
                    for index, c in enumerate(sentence, 0):
                        sentence[c.dst].srcs.append(index)
                    article.append(sentence)
                sentence = []
            else:
                chunk.morphs.append(Morph(
                    words[0],
                    words[7],
                    words[1],
                    words[2],
                ))
    return article

def findVerbs(sentence):
    for chunk in sentence:
        for m in chunk.morphs:
            if m.pos == '動詞':
                yield m, chunk.number
                break

def findParticles(sentence, chunkNo):
    for chunk in sentence:
        if chunk.dst == chunkNo:
            for m in reversed(chunk.morphs):
                if m.pos == '助詞':
                    yield m, chunk.concatMorphs()
                    break

def enumPattern(article):
    for sentence in article:
        for v, num in findVerbs(sentence):
            particlesList = []
            paragraphList = []
            for part, para in findParticles(sentence, num):
                particlesList.append(part.surface)
                paragraphList.append(para)
            if particlesList:
                yield v.base, sorted(particlesList, key=lambda x: x), \
                              sorted(paragraphList, key=lambda x: x)

def main():
    article = analyze()
    with open('result46.txt', 'w', encoding='utf8') as w:
        for v, particles, paragraphs in enumPattern(article):
            w.write('{}\t{}\t{}\n'.format(v, ' '.join(particles), ' '.join(paragraphs)))

if __name__ == '__main__':
    main() 

ソースコードは、GitHubで公開しています。

■ 簡単なコードの説明

前回から変更したのは、findParticles関数、enumPattern関数、main関数の3つです。 なお、今回も新しく仕入れた情報は無かったです。

findParticles関数では、タプルを返すように変更します。返すのは、助詞とその助詞を含むchunkです。

enumPattern関数では、findParticles関数から返される情報を使って、項(述語に係っている文節そのもの)のリスト(paragraphList)も返すようにします。 つまり3つの要素を持ったタプルを返しています。

main関数では、enumPatternが返す項も含めてファイルに出力するようにします。 

■ 結果

出力結果の先頭部分だけを掲載します。

生れる	で	どこで
つく	か が	生れたか 見当が
泣く	で	所で
する	て は	いた事だけは 泣いて
始める	で	ここで
見る	は を	ものを 吾輩は
聞く	で	あとで
捕える	を	我々を
煮る	て	捕えて
食う	て	煮て
思う	から	なかったから
載せる	に	掌に
持ち上げる	て と	スーと 載せられて
ある	が	感じが
落ちつく	で	上で
見る	て を	落ちついて 顔を
見る	の	ものの
思う	と	ものだと
残る	が でも	今でも 感じが
する	をもって	第一毛をもって
する	が	顔が
逢う	も	猫にも
出会う	も	一度も
する	が	真中が
吹く	から を	ぷうぷうと煙を 中から
弱る	て	咽せぽくて
飲む	の	人間の
知る	は	事は
坐る	で に	心持に 裏で
おる	て	坐って