0
1

More than 3 years have passed since last update.

言語処理100本ノック2020やってみた part4

Posted at

はじめに

言語処理100本ノック 2020 (Rev 1)第4章: 形態素解析の自分なりの解答です。
どんどん雑になってきてる
* 環境
* google colab

目次

第4章: 形態素解析

30. 形態素解析結果の読み込み
31. 動詞
32. 動詞の原形
33. 「AのB」
34. 名詞の連接
35. 単語の出現頻度
36. 頻度上位10語
37. 「猫」と共起頻度の高い上位10語
38. ヒストグラム
39. Zipfの法則

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

なお,問題37, 38, 39はmatplotlibもしくはGnuplotを用いるとよい

30. 形態素解析結果の読み込み

形態素解析結果(neko.txt.mecab)を読み込むプログラムを実装せよ.ただし,各形態素は表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をキーとするマッピング型に格納し,1文を形態素(マッピング型)のリストとして表現せよ.第4章の残りの問題では,ここで作ったプログラムを活用せよ.

イメージ
[
  [{surface:hoge, base:hoge, pose:hoge ,pose1:hoge},{surface:hoge, base:hoge, p...], 1文
  [{surface:hoge, base:hoge, pose:hoge ,pose1:hoge},{surface:hoge, base:hoge, p...],
  .
  .
  [{surface:hoge, base:hoge, pose:hoge ,pose1:hoge},{surface:hoge, base:hoge, p...]
]

import MeCab

#改行ごとに区切る
def split_sentence(sentence):
  li = sentence.split('\n')
  return li

def split_words(words):
  li = []
  for i in words:
    li.append(i.split('\t'))
  return li

def get_element(morphoed_sentence):
  li =[]
  for i in morphoed_sentence:
    if i[0] == 'EOS':
      break  
    pos = i[4].split('-')
    #print(i)
    #print(pos)
    if len(pos) == 1:
      dic = {'surface':i[0], 'base':i[3], 'pos':pos[0] ,'pos1':pos[0]}
    elif len(pos) >= 2:
      dic = {'surface':i[0], 'base':i[3], 'pos':pos[0] ,'pos1':pos[1]}
    li.append(dic)
  return li

#1文づつ入れていく
def get_morpho(sentence):
  # 形態素解析
  sentence =  tagger.parse(sentence)
  # 各形態素ごとにリスト化 
  sentence = split_sentence(sentence)
  # 各形態素の要素のリスト化 ['坊主', 'ボーズ', 'ボウズ', '坊主', '名詞-普通名詞-一般', '', '', '1']
  sentence = split_words(sentence)
  #指定要素の取得
  elements_list = get_element(sentence)
  return elements_list

#1行ごとにリスト化
def load_text(path):
  '''
  path   : file path
  return : 行ごとのリスト
  '''
  li = []
  with open(path,'r') as neko:
    for sentence in neko:
      sentence = sentence.strip('\r\n')
      #空行を避ける
      if sentence == '':
        continue
      li.append(sentence)
    return li
#neko.txt.mecabを読み込む
path = 'neko.txt.mecab'

def processor(path):
  li = []
  sentence = load_text(path)
  for i in sentence:
    li.append(get_morpho(i))
  return li


res = processor(path)
for i in range(5):
  print(res[i])

31. 動詞

動詞の表層形をすべて抽出せよ.

for i in res:
  for j in i:
    if j["pos"] == "動詞":
      print(j["surface"])
出力(途中から)
行く
し
廻っ
する
ある
択ば
出
来
ある

32. 動詞の原形

動詞の原形をすべて抽出せよ.

for i in res:
  for j in i:
    if j["pos"] == "動詞":
      print(j["base"])
途中から(32と見比べて)
行く
為る
回る
為る
有る
選ぶ
出る
来る
有る

33. 「AのB」

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

コメント

「の」がきた時にその前の単語と後の単語が名詞だとそれを抽出する。
各文の単語の+1,-1としててout of rangeとかになるかなと思いましたが文構造的にならないと思ったのでこのようにしました.


for ind,i in enumerate(res):
  #print(i)
  for index,j in enumerate(i):
    #print(index)
    #print(j)
    if j['surface'] == 'の'  and res[ind][index-1]['pos'] == '名詞' and res[ind][index+1]['pos'] == '名詞':
      print('{}の{}'.format(res[ind][index-1]['surface'],res[ind][index+1]['surface']))
出力
掌の上
書生の顔
ものの見
はずの顔
顔の真中
穴の中
書生の掌
掌の裏

34. 名詞の連接

名詞の連接(連続して出現する名詞)を最長一致で抽出せよ.

コメント

一文ごとに名詞のインデックスをリスト(noun_li)に追加する、それ以外は−1を追加
連結の操作はコードみてください

最後にnoun_liをリフレッシュしてなくてしばらく迷走してた。前も同じようなことした気がする

コメントアウトの出力文も合わせて出力すると処理がわかりやすい

#noun_liをリフレッシュする
noun_li =[]
noun = ''
for ind,i in enumerate(res): #各行を送り出す   ind:行数
#  print(i)
  for index,j in enumerate(i): #各行の要素を調べる index:要素のある場所
#    print(j)
    if j['pos'] == '名詞':
      noun_li.append(index)
    else:
      noun_li.append(-1)
#  print('-------------------------------')
  noun_li.append("EOL")
#  print(noun_li)
  for k in noun_li:
    if k != 'EOL':
      if k > 0:
        noun += res[ind][k]['surface']
      else:
        if len(noun) > 0:
          print(noun)
          noun = ''
    else:
        if len(noun) > 0:
          print(noun)
          noun = ''
  noun_li.clear()
出力(途中から)
いたずら
両人
揃い
事
最初
うち
各自任意
行動

35. 単語の出現頻度

文章中に出現する単語とその出現頻度を求め,出現頻度の高い順に並べよ.

def word_counter(data):
  dic = {}
  for i in data:
    for j in i:
      key = j['surface']
      dic[key] = dic.get(key,0) + 1
  return dic

li = []
word_counter_dic = word_counter(res)
for word, count in sorted(word_counter_dic.items(),key=lambda x:-x[1]):
  li.append('{} : {}'.format(word,count))

for i in range(10):
  print(li[i])
出力
の : 9541
。 : 7486
て : 7418
に : 7060
、 : 6773
は : 6501
と : 6156
を : 6119
が : 5394
で : 4543

36. 頻度上位10語

出現頻度が高い10語とその出現頻度をグラフ(例えば棒グラフなど)で表示せよ.

コメント

日本語は文字化けするからいろいろ処理が必要、追い詰められたらやります

内包表記使えば自動で欲しい番目までリスト化できるの失念してた

import matplotlib.pyplot as plt

label = words[0:10]
left = [1,2,3,4,5,6,7,8,9,10]
height = counts[0:10]
plt.bar(left, height, tick_label=label, align="center")

ダウンロード.png

37. 「猫」と共起頻度の高い上位10語

「猫」とよく共起する(共起頻度が高い)10語とその出現頻度をグラフ(例えば棒グラフなど)で表示せよ.

コメント

共起する単語の定義は文の中にがあるモノの単語とします

import matplotlib.pyplot as plt
import japanize_matplotlib

#猫と共起する単語とその頻度の辞書を返す関数
def neko_co(data):
  neko_co_occurence ={}
  for sentence in data:
    if '猫' in [words['surface'] for words in sentence]:
      #print(sentence)
      for word in [words['surface'] for words in sentence]: # word は key 
        #print(word)
        neko_co_occurence[word] = neko_co_occurence.get(word,0) + 1
  return neko_co_occurence

neko_co_dic = neko_co(res)
print(neko_co_dic)
del neko_co_dic['猫']

neko_co_word,neko_co_count = [],[]
for word, count in sorted(neko_co_dic.items(),key=lambda x:-x[1]):
  neko_co_word.append(word)
  neko_co_count.append(count)

label = neko_co_word[0:10]
left = [i+1 for i in range(10)]
height = neko_co_count[0:10]
plt.bar(left, height, tick_label=label, align="center")
print(neko_co_word)
print(neko_co_count)

ダウンロード (3).png

{'吾輩': 55, 'は': 262, '猫': 237, 'で': 181, 'ある': 96, '。': 200,

['の', 'に', 'は', 'て', '、', 'と', 'を', '。', 'で', 'が', 'も', 'た', 'ある', 'し', 'ない', 'だ', 'な', 'から', '事', '吾輩',
[377, 264, 262, 240, 240, 221, 208, 200, 181, 169, 133, 117, 96, 96, 88, 66, 62, 61, 58, 55, 

38. ヒストグラム

単語の出現頻度のヒストグラムを描け.ただし,横軸は出現頻度を表し,1から単語の出現頻度の最大値までの線形目盛とする.縦軸はx軸で示される出現頻度となった単語の異なり数(種類数)である.

コメント

あってるのかわからない
1、2とかの数が多すぎて他の頻度が潰れてる.
横軸の1棒の範囲が1000になってるからだと思う

import matplotlib.pyplot as plt
#1-9541
x = re_counts 
plt.hist(x)
plt.show()

ダウンロード (1).png

39. Zipfの法則

単語の出現頻度順位を横軸,その出現頻度を縦軸として,両対数グラフをプロットせよ.

#rank r
#freaquence f
# words words
r = [i+1 for i in range(len(counts))]
f = counts

import matplotlib.pyplot as plt
import japanize_matplotlib
fig, ax = plt.subplots()
ax.plot(r, f,'.')
plt.scatter(r,f,s=0.5)
ax.set_yscale('log')  # y軸をlogスケールで描く
ax.set_xscale('log')  # x軸をlogスケールで描く
plt.show()

ダウンロード (4).png

感想

ゴリ押しでなんとかなったような気もするけど自分の回答が効率的なのかどうかよくわからないので他の人のもみてみようと思う

0
1
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
0
1