2018年05月23日

言語処理100本ノックでPython入門 #45 - 動詞の格パターンの抽出

  

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

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

45. 動詞の格パターンの抽出
今回用いている文章をコーパスと見なし,日本語の述語が取りうる格を調査したい. 動詞を述語,動詞に係っている文節の助詞を格と考え,述語と格をタブ区切り形式で出力せよ. ただし,出力は以下の仕様を満たすようにせよ.
  • 動詞を含む文節において,最左の動詞の基本形を述語とする 
  • 述語に係る助詞を格とする 
  • 述語に係る助詞(文節)が複数あるときは,すべての助詞をスペース区切りで辞書順に並べる 
「吾輩はここで始めて人間というものを見た」という例文(neko.txt.cabochaの8文目)を考える. この文は「始める」と「見る」の2つの動詞を含み,「始める」に係る文節は「ここで」,「見る」に係る文節は「吾輩は」と「ものを」と解析された場合は,次のような出力になるはずである.
始める  で
見る    は を
このプログラムの出力をファイルに保存し,以下の事項をUNIXコマンドを用いて確認せよ.
  • コーパス中で頻出する述語と格パターンの組み合わせ 
  • 「する」「見る」「与える」という動詞の格パターン(コーパス中で出現頻度の高い順に並べよ)

■ 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
                    break

def enumPattern(article):
    for sentence in article:
        for v, num in findVerbs(sentence):
            lst = []
            for p in sorted(findParticles(sentence, num), key=lambda x: x.surface):
                lst.append(p.surface)
            if lst:
                yield v.base, lst

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

if __name__ == '__main__':
    main()


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

■ 簡単なコードの説明 

Morphクラス、Chunkクラスは変更なし、analyze関数も変更なし。 

問題文が随分と複雑になってきました。そのぶんコード量も増えています。ただし、問題を解くのに新しい知識は特になかったです。

findVerbs は、センテンスから動詞のMorph(形態素)を列挙する関数。
列挙する際は、そのMorphを含むChunk(文節)に振った番号も一緒に列挙。chunkオブジェクトには、numberを付加してあるので、それを利用しています。 

findParticlesは、sentenceとChunkの番号を引数で受け取って、動詞に係っている助詞(Morph)を列挙しています。
動詞に係る助詞が一つのChunkの中に複数ある場合を考慮して、Chunk.Morphsを逆順に操作して、最初に見つかった助詞を採用しています。 
Chunk.Morphsを逆順を求めるのに、reversed関数を使っています。 reverseという関数もあるみたいですけど、こちらはリストそのものの順序を変えてしまうので、ここでは使えないです。 

enumPattern は、findVerbs findParticles を使い、文章全体から「述語と助詞」を列挙しています。 この時、助詞が複数ある場合は、sortedを使って並び替えています。 

なお、動詞に係る助詞がない場合は、出力しないようにしています。 


■ 結果

結果の一部だけを示します。
生れる	で
つく	か が
泣く	で
する	て は
始める	で
見る	は を
聞く	で
捕える	を
煮る	て
食う	て
思う	から
載せる	に
持ち上げる	て と
ある	が
落ちつく	で
見る	て を
見る	の
思う	と
残る	が でも
する	をもって
する	が
逢う	も
出会う	も
する	が
吹く	から を
弱る	て
飲む	の
知る	は
坐る	で に
おる	て