TPTブログ

テックポート株式会社のブログです。 技術情報や製品・サービス情報、 また未経験社員がデータサイエンティストを 目指す奮闘記など、更新していきます。

▲心くじけず言語処理100本ノック==45・46==

f:id:TBT_matsu:20200515145048p:plain
こんにちは!
テービーテックの村松です。

「言語処理100本ノック2020]」
nlp100.github.io
に挑戦中!
途中でくじけないか見守ってください・・・。
そして、皆さんも一緒に挑戦してみましょう!

本日は第5章: 係り受け解析 45・46です!
間違い・コード改善点などありましたら教えていただけると嬉しいです。

第5章: 係り受け解析

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

下準備はこちら↓↓で行いました。
ds-blog.tbtech.co.jp

前回からの共通コード
#テキストデータの読み込み
path = 'neko.txt.cabocha'
import re
with open(path, encoding='utf-8') as f:
  _data = f.read().split('\n')

#クラスの定義
class Morph:
  def __init__(self, word):
    self.surface = word[0]  #表層形
    self.base = word[7]     #基本形
    self.pos = word[1]      #品詞
    self.pos1 = word[2]     #品詞細分類
class Chunk:
  def __init__(self, idx, dst):
    self.idx = idx     #文節番号
    self.morphs = []   #形態素(Morphオブジェクト)のリスト
    self.dst = dst     #係り先文節インデックス番号
    self.srcs = []     #係り元文節インデックス番号のリスト

s_list = []
sent = []
temp = []
chunk = None
for line in _data[:-1]:
  text = re.split("[\t, ]", line)
  if text[0] == '*':
    idx = int(text[1])
    dst = int(re.search(r'(.*?)D', text[2]).group(1))
    chunk = Chunk(idx, dst)
    sent.append(chunk)
  elif text[0] == 'EOS':
    if sent:
      for i, c in enumerate(sent, 0):
        if c.dst == -1:
          continue
        else:
          sent[c.dst].srcs.append(i)
      s_list.append(sent)
    sent = []
  else:
    morph = Morph(text)
    chunk.morphs.append(morph)
    temp.append(morph)

では、引き続き問題に取り組んでまいりましょう。

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

「今回用いている文章をコーパスと見なし,日本語の述語が取りうる格を調査したい. 動詞を述語,動詞に係っている文節の助詞を格と考え,述語と格をタブ区切り形式で出力せよ. ただし,出力は以下の仕様を満たすようにせよ.
・動詞を含む文節において,最左の動詞の基本形を述語とする
・述語に係る助詞を格とする
・述語に係る助詞(文節)が複数あるときは,すべての助詞をスペース区切りで辞書順に並べる

このプログラムの出力をファイルに保存し,以下の事項をUNIXコマンドを用いて確認せよ.
・コーパス中で頻出する述語と格パターンの組み合わせ
・「行う」「なる」「与える」という動詞の格パターン(コーパス中で出現頻度の高い順に並べよ)」


はい
・・・はい
・・・・・・
まずは問題の意味を理解するところから整理しても良いでしょうか?
まず、「格」ってなんですか???

格 とは、名詞句の文法的および意味的な役割を表したラベルであり、 格フレーム とは、動詞などの用言を中心に見たときに、それらが取る(取り得る)格要素の構造パターンのことである。
引用:http://www.nltk.org/book-jp/ch12.html#id75

はい
・・・はい
噛み砕きましょう。
例文を使用して何とか噛み砕きましょう。
(ここから先、厳密に考えると正確な説明ではないかもしれません。ご了承ください。)

例文:「吾輩はここで始めて人間というものを見た。」
こちらを問41でやったように係り受け解析結果を表示するとこんな感じです。

0 ['名詞', '助詞'] 吾輩は           係り元(srcs):[]      係り先(dst):5
1 ['名詞', '助詞'] ここで           係り元(srcs):[]      係り先(dst):2
2 ['動詞', '助詞'] 始めて           係り元(srcs):[1]     係り先(dst):3
3 ['名詞', '助詞'] 人間という       係り元(srcs):[2]     係り先(dst):4
4 ['名詞', '助詞'] ものを           係り元(srcs):[3]     係り先(dst):5
5 ['動詞', '助動詞', '記号'] 見た。 係り元(srcs):[0, 4]  係り先(dst):-1

「格」は名詞にくっついている「は」とか「を」とかで、
その名詞が主語なのか目的語なのかなど、名詞が文中でどんな立場なのかを教えてくれるものと考えます。

なので、例文でいうと、
「吾輩’は’」
「ここ’で’」
「人間’という’」
「もの’を’」
の文節の助詞のことですね。
表層格とか深層格とか、細かい種類とかは今回は深堀しません。
さらっとした理解で進めましょう。


次、格フレームは、用言(今回は動詞=述語)を基準に考えます。
例文の「見た。」なら、
係り受け解析結果から「見た。」の係り元を見ると、
「吾輩は 見た。」
「ものを 見た。」
つまり、各フレームは、
「{主語} {述語}」
「{対象物} {述語}」
という関係なります。

詰まる所、
格を解析すると、係り受け解析だけでは曖昧な名詞と用言の関係がわかってくるよ
と解釈します。



はい、では、改めて問45に挑みます。
大まかな流れとしては、

①文節中の初めの動詞を見つける=述語

②動詞の係り元の文節を確認し、助詞を見つける=格

③「{述語}タブ{格}スペース{格}・・・・」の形で出力

path = '保存先パス/45.txt'
with open(path, mode='w')as f:
  #①動詞を探す
  for s in s_list:            #s→1文
    for chunk in s:           #chunk→文節
      for m in chunk.morphs:  #m→形態素解析結果
        if m.pos == '動詞':   #品詞が動詞なら
          #②助詞を探す
          c = []                            #格の仮置き場
          for sr in chunk.srcs:             #srcs→係り元インデックス番号
            for mor in s[sr].morphs:     #係り元の形態素
              if mor.pos == '助詞':         #助詞ならcに表層形を格納
                c = c + [mor.base]
          if len(c) > 0:                    #cに要素が入っていれば、
            c = sorted(list(set(c)))        #かぶりを無くして、ソートする
            #③出力
            output = m.base + '\t' + ' '.join(c) + '\n'
            f.write(output)

出来上がったファイルの内容を確認します。

!cat ./45.txt | sort | uniq -c | sort -nr | head -n 10
##結果
   3309 ある	が
   2134 つく	か が
    947 する	が で と
    680 云う	に
    492 られる	に
    396 られる	て と
    360 かく	たり を
    344 見る	の
    295 する	て に は を
    285 ある	まで
!cat ./45.txt | grep 'する' | sort | uniq -c | sort -nr | head
##
   1198 する	が
    791 する	が と
    323 する	が で と
    140 する	でも に
    110 する	て に は を
    106 する	まで
     82 する	だけ て は
     45 する	は まで
     44 する	たり て と を
     38 する	くらい も
!cat ./45.txt | grep '見る' | sort | uniq -c | sort -nr | head
##結果
    344 見る	の
    108 見る	は を
     35 見る	が に を
     27 見る	から て
     22 見る	から
     10 見る	たり て に
     10 見る	が ので
      7 見る	か と ね を
      3 見る	て ばかり
      3 見る	で より を
!cat ./45.txt | grep '与える' | sort | uniq -c | sort -nr | head
##結果
      5 与える	ば を
      3 与える	て に は を
      3 与える	け に を
      2 与える	だけ で に を
      1 与える	に に対して のみ は も
      1 与える	けれども に は を
      1 与える	か じゃあ て と は を
      1 与える	か として を
      1 与える	が て と に は を
46.動詞の格フレーム情報の抽出

「45のプログラムを改変し,述語と格パターンに続けて項(述語に係っている文節そのもの)をタブ区切り形式で出力せよ.45の仕様に加えて,以下の仕様を満たすようにせよ.
・項は述語に係っている文節の単語列とする(末尾の助詞を取り除く必要はない)
・述語に係る文節が複数あるときは,助詞と同一の基準・順序でスペース区切りで並べる」

問45のコードにちょこっと付け足します。

path = '保存先パス/46.txt'
with open(path, mode='w')as f:
  for s in s_list:
    for chunk in s:
      for m in chunk.morphs:
        if m.pos == '動詞':
          c = []
          chu = []  #係り受け元文節の単語仮置き場
          for sr in chunk.srcs:
            for mor in s[sr].morphs:
              if mor.pos == '助詞':
                c = c + [mor.base]
                chu.append(''.join(k.surface for k in s[sr].morphs))#係り受け元文節の単語を格納
          if len(c) > 0:
            c = sorted(list(set(c)))
            output = m.base + '\t' + ' '.join(c) + '\t' + ' '.join(chu) + '\n'
            f.write(output)

出来上がったファイルの内容を確認します。

!cat ./46.txt | head
##結果
生れる	で	 どこで
つく	か が	生れたか 見当が
泣く	で	所で
する	だけ て は	泣いて いた事だけは いた事だけは
いる	だけ て は	泣いて いた事だけは いた事だけは
始める	で	ここで
見る	は を	吾輩は ものを
聞く	で	あとで
捕える	を	我々を
煮る	て	捕えて

長くなってきてしまったので、一旦区切ります。
残りは次回!

ここまでご覧いただきありがとうございます。
以上、第5章44・46でした!

:::余談:::
これを書き終えた時に気付いたのですが、
100本ノック5章で使用しているテキストデータ変わりましたね。
日本語Wikipediaの「人工知能」に関する記事からテキスト部分を抜き出したファイルになっていました。。。。
どうやら6月8日に修正されたようです。
修正された理由はこちら
気づかなかったのは根本的に日本語のお勉強不足ですね・・・。精進します。
順次ブログの内容も直したいと思います。あしからず・・・