LoginSignup
1
2

More than 3 years have passed since last update.

言語処理100本ノック-45:動詞の格パターンの抽出

Posted at

言語処理100本ノック 2015「第5章: 係り受け解析」45本目「動詞の格パターンの抽出」記録です。
ifの条件分岐も増え、少しずつ複雑になっています。アルゴリズム考えるのがやや面倒です。

参考リンク

リンク 備考
045.動詞の格パターンの抽出.ipynb 回答プログラムのGitHubリンク
素人の言語処理100本ノック:45 多くのソース部分のコピペ元
CaboCha公式 最初に見ておくCaboChaのページ

環境

CRF++とCaboChaはインストールしたのが昔すぎてインストール方法忘れました。全然更新されていないパッケージなので、環境再構築もしていません。CaboChaをWindowsで使おうと思い、挫折した記憶だけはあります。確か64bitのWindowsで使えなかった気がします(記憶が曖昧だし私の技術力の問題も多分にあるかも)。

種類 バージョン 内容
OS Ubuntu18.04.01 LTS 仮想で動かしています
pyenv 1.2.16 複数Python環境を使うことがあるのでpyenv使っています
Python 3.8.1 pyenv上でpython3.8.1を使っています
パッケージはvenvを使って管理しています
Mecab 0.996-5 apt-getでインストール
CRF++ 0.58 昔すぎてインストール方法忘れました(多分make install)
CaboCha 0.69 昔すぎてインストール方法忘れました(多分make install)

第5章: 係り受け解析

学習内容

『吾輩は猫である』に係り受け解析器CaboChaを適用し,係り受け木の操作と統語的な分析を体験します.

クラス, 係り受け解析, CaboCha, 文節, 係り受け, 格, 機能動詞構文, 係り受けパス, Graphviz

ノック内容

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

45. 動詞の格パターンの抽出

今回用いている文章をコーパスと見なし,日本語の述語が取りうる格を調査したい.動詞を述語,動詞に係っている文節の助詞を格と考え,述語と格をタブ区切り形式で出力せよ.ただし,出力は以下の仕様を満たすようにせよ.

  • 動詞を含む文節において,最左の動詞の基本形を述語とする
  • 述語に係る助詞を格とする
  • 述語に係る助詞(文節)が複数あるときは,すべての助詞をスペース区切りで辞書順に並べる

「吾輩はここで始めて人間というものを見た」という例文(neko.txt.cabochaの8文目)を考える.この文は「始める」と「見る」の2つの動詞を含み,「始める」に係る文節は「ここで」,「見る」に係る文節は「吾輩は」と「ものを」と解析された場合は,次のような出力になるはずである.

始める  で
見る    は を

このプログラムの出力をファイルに保存し,以下の事項をUNIXコマンドを用いて確認せよ.

  • コーパス中で頻出する述語と格パターンの組み合わせ
  • 「する」「見る」「与える」という動詞の格パターン(コーパス中で出現頻度の高い順に並べよ)

課題補足(「格」について)

プログラムを完成させる目的では特に意識をしませんが、日本語の「格」というのは奥が深そうです。興味が出たらWikipedit「格」を見てみましょう。私はチラ見程度です。
昔、オーストラリアでランゲージ・エクスチェンジをしていたときに「は」と「が」の何が違うのかを聞かれたことを思い出しました。

回答

回答プログラム 045.動詞の格パターンの抽出.ipynb

import re

# 区切り文字
separator = re.compile('\t|,')

# 係り受け
dependancy = re.compile(r'''(?:\*\s\d+\s) # キャプチャ対象外
                            (-?\d+)       # 数字(係り先)
                          ''', re.VERBOSE)

    def __init__(self, line):

        #タブとカンマで分割
        cols = separator.split(line)

        self.surface = cols[0] # 表層形(surface)
        self.base = cols[7]    # 基本形(base)
        self.pos = cols[1]     # 品詞(pos)
        self.pos1 = cols[2]    # 品詞細分類1(pos1)

class Chunk:
    def __init__(self, morphs, dst):
        self.morphs = morphs
        self.srcs = []   # 係り元文節インデックス番号のリスト
        self.dst  = dst  # 係り先文節インデックス番号

        self.verb = ''
        self.joshi = ''

        for morph in morphs:            
            if morph.pos != '記号':
                self.joshi = ''  # 記号を除いた最終行の助詞を取得するため、記号以外の場合はブランク
            if morph.pos == '動詞':
                self.verb = morph.base
            if morph.pos == '助詞':
                self.joshi = morph.base

# 係り元を代入し、Chunkリストを文のリストを追加
def append_sentence(chunks, sentences):

    # 係り元を代入
    for i, chunk in enumerate(chunks):
        if chunk.dst != -1:
            chunks[chunk.dst].srcs.append(i)
    sentences.append(chunks)
    return sentences, []

morphs = []
chunks = []
sentences = []

with open('./neko.txt.cabocha') as f:

    for line in f:
        dependancies = dependancy.match(line)

        # EOSまたは係り受け解析結果でない場合
        if not (line == 'EOS\n' or dependancies):
            morphs.append(Morph(line))

        # EOSまたは係り受け解析結果で、形態素解析結果がある場合
        elif len(morphs) > 0:
            chunks.append(Chunk(morphs, dst))
            morphs = []

        # 係り受け結果の場合
        if dependancies:
            dst = int(dependancies.group(1))

        # EOSで係り受け結果がある場合
        if line == 'EOS\n' and len(chunks) > 0:
            sentences, chunks = append_sentence(chunks, sentences)

with open('./045.result_python.txt', 'w') as out_file:
    for sentence in sentences:
        for chunk in sentence:
            if chunk.verb != '' and len(chunk.srcs) > 0:

                # 係り元助詞のリストを作成
                sources = [sentence[source].joshi for source in chunk.srcs if sentence[source].joshi != '']

                if len(sources) > 0:
                    sources.sort()
                    out_file.write(('{}\t{}\n'.format(chunk.verb, ' '.join(sources))))

以下はUNIXコマンド部分です。grepコマンドを初めて使いましたが便利なのですね。

UNIXコマンド部
# ソート、重複除去とカウント、降順ソート
sort 045.result_python.txt | uniq --count | sort --numeric-sort --reverse > "045.result_1_すべて.txt"

# 「(行頭)する(空白)」を抽出、ソート、重複除去とカウント、降順ソート
grep "^する\s" 045.result_python.txt | sort | uniq --count | sort --numeric-sort --reverse > "045.result_2_する.txt"

# 「(行頭)見る(空白)」を抽出、ソート、重複除去とカウント、降順ソート
grep "^見る\s" 045.result_python.txt | sort | uniq --count | sort --numeric-sort --reverse > "045.result_3_見る.txt"

# 「(行頭)与える(空白)」を抽出、ソート、重複除去とカウント、降順ソート
grep "^与える\s" 045.result_python.txt | sort | uniq --count | sort --numeric-sort --reverse > "045.result_4_与える.txt"

回答解説

Chunkクラス

Chunkクラスで動詞と助詞の原型を格納しています。1文節に複数の動詞があった場合は、後勝ちにしています。格となる助詞は文節の最後に出てくるはずなのですが、記号を考慮した条件分岐を入れています。

class Chunk:
    def __init__(self, morphs, dst):
        self.morphs = morphs
        self.srcs = []   # 係り元文節インデックス番号のリスト
        self.dst  = dst  # 係り先文節インデックス番号

        self.verb = ''
        self.joshi = ''

        for morph in morphs:            
            if morph.pos != '記号':
                self.joshi = ''  # 記号を除いた最終行の助詞を取得するため、記号以外の場合はブランク
            if morph.pos == '動詞':
                self.verb = morph.base
            if morph.pos == '助詞':
                self.joshi = morph.base

出力部分

係り元の助詞はリスト内包表記でリスト化して、「辞書順に並べる」を満たすためにソートしています。そして、最後にjoin関数を使ってスペース区切りで出力しています。ネストが深くて、書いていて気持ち悪いです。

with open('./045.result_python.txt', 'w') as out_file:
    for sentence in sentences:
        for chunk in sentence:
            if chunk.verb != '' and len(chunk.srcs) > 0:

                # 係り元助詞のリストを作成
                sources = [sentence[source].joshi for source in chunk.srcs if sentence[source].joshi != '']

                if len(sources) > 0:
                    sources.sort()
                    out_file.write(('{}\t{}\n'.format(chunk.verb, ' '.join(sources))))

出力結果(実行結果)

プログラム実行すると以下の結果が出力されます。多いので10行だけここに表示します。

Pythonの出力結果

045.result_python.txt(冒頭10行)
生れる   で
つく  が と
泣く  で
いる  て は
始める   で
見る  は を
聞く  で
捕える   を
煮る  て
食う  て

UNIXコマンドの出力結果

多いので10行だけここに表示します。

045.result_1_すべて.txt(冒頭10行)
   3176 ある  が
   1997 つく  が と
    800 云う  は
    721 ある  が と に
    464 られる   に
    330 られる   て と
    309 思う  と
    305 見る  の
    301 かく  たり を
    262 ある  まで
045.result_2_する.txt(冒頭10行)
   1099 する  が
    651 する  が と
    221 する  で に は
    109 する  でも に
     86 する  まで
     59 する  と は は は
     41 する  たり は へ
     27 する  たり と は を
     24 する  て まで
     18 する  として
045.result_3_見る.txt(冒頭10行)
    305 見る  の
     99 見る  は を
     31 見る  て て は
     24 見る  から て
     19 見る  から
     11 見る  から て て
      7 見る  が ので
      5 見る  て て て は
      2 見る  ながら に を
      2 見る  で ばかり も

「与える」は出現頻度少なかく、これで全部です。

045.result_4_与える.txt
      7 与える   に を
      4 与える   で に を
      3 与える   て と は を
      1 与える   けれども は を
      1 与える   か として
      1 与える   が て と に は を
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2