言語処理100本ノック2020(python)備忘録30-39

30

まずはmecabを使って
$ mecab neko.txt > neko.txt.mecab
を実行します。mecabのインストールは読者が頑張ってください。

word_array = []
with open('neko.txt.mecab') as r:
    r_lines = r.read()
r_test = r_lines.splitlines()
r_test_1bun = r_test[4:10] # 1文目?の範囲 [4:10]
for line in r_test_1bun:
    if line == 'EOS': # EOSは辞書登録しない。
        continue
    #EOSでない単語は辞書登録
    surface = line.split('\t')[0]
    base = line.split('\t')[1].split(',')[6]
    pos = line.split('\t')[1].split(',')[0]
    pos1 = line.split('\t')[1].split(',')[1]
    newword_dict = {'surface':surface, 'base':base, 'pos':pos, 'pos1':pos1}
    word_array.append(newword_dict)
print(word_array)

出力は
['吾輩\t名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ', 'は\t助詞,係助詞,*,*,*,*,は,ハ,ワ', '猫\t名詞,一般,*,*,*,*,猫,ネコ,ネコ', 'で\t助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ', 'ある\t助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル', '。\t記号,句点,*,*,*,*,。,。,。']
[{'surface': '吾輩', 'base': '吾輩', 'pos': '名詞', 'pos1': '代名詞'}, {'surface': 'は', 'base': 'は', 'pos': '助詞', 'pos1': '係助詞'}, {'surface': '猫', 'base': '猫', 'pos': '名詞', 'pos1': '一般'}, {'surface': 'で', 'base': 'だ', 'pos': '助動詞', 'pos1': '*'}, {'surface': 'ある', 'base': 'ある', 'pos': '助動詞', 'pos1': '*'}, {'surface': '。', 'base': '。', 'pos': '記号', 'pos1': '句点'}]

1文目を「吾輩は猫である。」の文と解釈した。
line.split('\t')[1].split(',')[6]とline.split('\t')[1].split(',')[0]とline.split('\t')[1].split(',')[1]を同時に処理できたら美しいが、思いつかなかった。
最初に
from pprint import pprint
とimportして
pprint(word_array)で表示すると
[{'base': '吾輩', 'pos': '名詞', 'pos1': '代名詞', 'surface': '吾輩'},
{'base': 'は', 'pos': '助詞', 'pos1': '係助詞', 'surface': 'は'},
{'base': '猫', 'pos': '名詞', 'pos1': '一般', 'surface': '猫'},
{'base': 'だ', 'pos': '助動詞', 'pos1': '*', 'surface': 'で'},
{'base': 'ある', 'pos': '助動詞', 'pos1': '*', 'surface': 'ある'},
{'base': '。', 'pos': '記号', 'pos1': '句点', 'surface': '。'}]

となって見やすい。

31

path = 'neko.txt.mecab'

word_array = []
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()
for line in r_split:
    if line == 'EOS': # EOSは辞書登録しない。
        continue
    #EOSでない単語は辞書登録
    surface = line.split('\t')[0]
    base = line.split('\t')[1].split(',')[6]
    pos = line.split('\t')[1].split(',')[0]
    pos1 = line.split('\t')[1].split(',')[1]
    newword_dict = {'surface':surface, 'base':base, 'pos':pos, 'pos1':pos1}
    word_array.append(newword_dict)
#####
verb_surface_set = set() #動詞のsurfaceのset
for word in word_array:
    if word['pos']=='動詞':
        verb_surface_set.add(word['surface'])
print(verb_surface_set)

#####までは問題30と同様である。
出力は例えば
{'出さ', '押しやる', 'もらう',
で始まり
'背く', 'うっ', '勝る'}
で終わる。(set型なので順不同)

すべて抽出は重複しているものを除外すると解釈し、set()で管理した。

32

path = 'neko.txt.mecab'

word_array = []
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()
for line in r_split:
    if line == 'EOS': # EOSは辞書登録しない。
        continue
    #EOSでない単語は辞書登録
    surface = line.split('\t')[0]
    base = line.split('\t')[1].split(',')[6]
    pos = line.split('\t')[1].split(',')[0]
    pos1 = line.split('\t')[1].split(',')[1]
    newword_dict = {'surface':surface, 'base':base, 'pos':pos, 'pos1':pos1}
    word_array.append(newword_dict)

verb_base_set = set() #動詞の原型のset
for word in word_array:
    if word['pos']=='動詞':
        verb_base_set.add(word['base'])
print(verb_base_set)

出力は
{'保つ', 'かたちづくる', '漲る',
で始まり
'うたう', '出会う', '求む'}
で終わる
最後の表示でsurfaceをbaseにするだけである。

33

path = 'neko.txt.mecab'

word_array = []
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()
for line in r_split:
    if line == 'EOS': # EOSは辞書登録しない。
        continue
    #EOSでない単語は辞書登録
    surface = line.split('\t')[0]
    base = line.split('\t')[1].split(',')[6]
    pos = line.split('\t')[1].split(',')[0]
    pos1 = line.split('\t')[1].split(',')[1]
    newword_dict = {'surface':surface, 'base':base, 'pos':pos, 'pos1':pos1}
    word_array.append(newword_dict)
#####
AnoB_set = set()
for i in range(len(word_array)-2):
    if (word_array[i]['pos'] == '名詞' and word_array[i+1]['surface'] == 'の' and word_array[i+2]['pos'] == '名詞'):
        AnoB_set.add(word_array[i]['surface']+word_array[i+1]['surface']+word_array[i+2]['surface'])
print(AnoB_set)

range(len(word_array)-2):が気に入らないなら、

path = 'neko.txt.mecab'

surface_old = ''
mode_AnoB = 0
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()

AnoB_set = set()
for line in r_split:
    if line == 'EOS' or line == '': # EOSや空行は辞書登録しない。
        mode_AnoB = 0
        continue
    #EOSでない単語は辞書登録
    surface = line.split('\t')[0]
    pos = line.split('\t')[1].split(',')[0]

    if mode_AnoB == 2 and pos == '名詞':
        AnoB_set.add(surface_old+'の'+surface)
    if mode_AnoB == 1 and surface == 'の':
        mode_AnoB = 2
    elif pos == '名詞':
        surface_old = surface
        mode_AnoB = 1
    else:
        mode_AnoB = 0
print(AnoB_set)

出力は例えば
{'日本一の堪能', '君の親愛', '紫の紐'
で始まり
'年頃の比較', '這裏の消息', '吾輩の顔'}
で終わる。
mode_AnoBは、
名詞があれば1
その直後に「の」があれば2
それ以外で0である変数である。
つまりmode_AnoB==2の段階で次の単語が名詞なら、出力するための集合に入れる。
surface_oldに1個目の名詞を入れている。
「の」は名詞ではない。
今回は改行でmode_AnoBを0としたが、
「倦んじて薫ずる香裏に君の
霊か相思の煙のたなびき」
という改行で区切られた部分の「君の霊」を抽出したい場合はそうしなければよい。
また、

if mode_AnoB < 2 and pos == '名詞':
        surface_old = surface
        mode_AnoB = 1
    elif mode_AnoB == 1 and surface == 'の':
        mode_AnoB = 2
    elif mode_AnoB == 2 and pos == '名詞':
        AnoB_set.add(surface_old+'の'+surface)
        surface_old = surface
        mode_AnoB = 1
    else:
        mode_AnoB = 0

のようにしてもよい。
最初の部分は mode_AnoB==0とすると
[名詞][名詞]の[名詞]
などが漏れるため、mode_AnoB < 2としなければいけないことに注意。

34

path = 'neko.txt.mecab'

successive_noun = ''
successive_noun_mode = 0
successive_noun_set = set()
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()

for line in r_split:
    if line == 'EOS' or line == '':
        continue
    surface = line.split('\t')[0]
    pos = line.split('\t')[1].split(',')[0]
    
    if pos == '名詞':
        successive_noun_mode = 1
        successive_noun = successive_noun+surface
    elif successive_noun_mode == 1:
        successive_noun_set.add(successive_noun)
        successive_noun_mode = 0
        successive_noun = ''
print(successive_noun_set)

出力は例えば、
{'奇言奇行', '神酒供え', '筒袖'
で始まり、
'挙動', '肌', '反照'}
で終わる。
「十七味調唐辛子調」なども抽出できている。
名詞が続く間はsuccessive_noun_modeが1、続かなくなったら0。そのときに連続した名詞をsetに追加。

35

path = 'neko.txt.mecab'

word_freq_dict = dict()
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()
for line in r_split:
    if line == 'EOS':
        continue
    surface = line.split('\t')[0]
    if surface in word_freq_dict:
        word_freq_dict[surface] += 1
    else:
        word_freq_dict[surface] = 1
print(sorted(word_freq_dict.items(),reverse=True, key = lambda x: x[1])[:10])

出力は
[('の', 9194), ('。', 7486), ('て', 6868), ('、', 6772), ('は', 6420), ('に', 6243), ('を', 6071), ('と', 5508), ('が', 5337), ('た', 3988)]
collections.Counterを使ってもいいと思う。

36

import matplotlib.pyplot as plt
import matplotlib
import collections
matplotlib.rcParams['font.family'] = 'Hiragino Maru Gothic Pro'

path = 'neko.txt.mecab'

word_array = []
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()
for line in r_split: #line(example) = {一\t名詞,数,*,*,*,*,一,イチ,イチ}
    if line == 'EOS': # EOSは辞書登録しない。
        continue
    #EOSでない単語は辞書登録
    surface = line.split('\t')[0]
    base = line.split('\t')[1].split(',')[6]
    pos = line.split('\t')[1].split(',')[0]
    pos1 = line.split('\t')[1].split(',')[1]
    newword_dict = {'surface':surface, 'base':base, 'pos':pos, 'pos1':pos1}
    word_array.append(newword_dict)

surface_list = [d['surface'] for d in word_array]
word_count_by_surface = collections.Counter(surface_list)
graph_list = word_count_by_surface.most_common()[:10]
x = [taple[0] for taple in graph_list]
y = [taple[1] for taple in graph_list]


fig = plt.figure()
plt.bar(x,y)
plt.title('出現頻度上位10')
plt.xlabel('単語')
plt.ylabel('出現回数')
plt.show()
fig.savefig('36_image.png')

出力は

f:id:D_PLIUS:20200520155925p:plain
36_image
import matplotlib.pyplot as plt
import matplotlib
でグラフの環境を、
import collections
で計数するモジュールを、importし、
matplotlib.rcParams['font.family'] = 'Hiragino Maru Gothic Pro'
でグラフ内の日本語のフォントを設定。
surface_list[]にすべてのsurfaceを入れ、collections.Counterで計数。
そして、.most_common()[:10]によって最頻10単語を取り出す。
ここで、形式は(単語, 回数)のタプルであるため
x = [taple[0] for taple in graph_list]
とリスト内包表記でリストを作る。
fig = plt.figure()
以降でグラフを書き、ラベルを適宜追加する。
plt.show()は処理を止めてしまうため、グラフを閉じずに処理を続けたい場合はplt.pause(0.01)など。

37

import matplotlib.pyplot as plt
import matplotlib
import collections
matplotlib.rcParams['font.family'] = 'Hiragino Maru Gothic Pro'

path = 'neko.txt.mecab'

word_array = []
with open(path) as r:
    r_lines = r.read()
r_sentences = r_lines.split('EOS')
for sentence in r_sentences:
    if '猫' in sentence: #猫と同じ文(=共起)
        sentence_line = sentence.split('\n')
        for word in sentence_line:
            if word == '':
                continue
            surface = word.split('\t')[0]
            base = word.split('\t')[1].split(',')[6]
            pos = word.split('\t')[1].split(',')[0]
            pos1 = word.split('\t')[1].split(',')[1]
            newword_dict = {'surface':surface, 'base':base, 'pos':pos, 'pos1':pos1}
            word_array.append(newword_dict)

surface_list = [d['surface'] for d in word_array]

surface_list = [elem for elem in surface_list if elem != '猫'] # 猫自体を共起としない
word_count_by_surface = collections.Counter(surface_list)
graph_list = word_count_by_surface.most_common()[:10]
x = [taple[0] for taple in graph_list]
y = [taple[1] for taple in graph_list]

fig = plt.figure()
plt.bar(x,y)
plt.title('出現頻度上位10')
plt.xlabel('単語')
plt.ylabel('出現回数')
plt.show()
fig.savefig('37_image.png')

出力は

f:id:D_PLIUS:20200520162003p:plain
37_image
行ごとではなく'EOS'ごとに分割し、「猫」が含まれる場合にのみ単語を辞書に登録している。
その後、「猫」それ自身は共起の対象ではないとし、「猫」をリスト内包表記で除いている。
その後は問題36と同様に計数してグラフに出力。

38

import matplotlib.pyplot as plt
import matplotlib
import collections
matplotlib.rcParams['font.family'] = 'Hiragino Maru Gothic Pro'

path = 'neko.txt.mecab'

word_array = []
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()
for line in r_split: #line(example) = {一\t名詞,数,*,*,*,*,一,イチ,イチ}
    if line == 'EOS': # EOSは辞書登録しない。
        continue
    #EOSでない単語は辞書登録
    surface = line.split('\t')[0]
    base = line.split('\t')[1].split(',')[6]
    pos = line.split('\t')[1].split(',')[0]
    pos1 = line.split('\t')[1].split(',')[1]
    newword_dict = {'surface':surface, 'base':base, 'pos':pos, 'pos1':pos1}
    word_array.append(newword_dict)

surface_list = [d['surface'] for d in word_array]
word_count_by_surface = collections.Counter(surface_list)
graph_list = word_count_by_surface.most_common()

x = [taple[1] for taple in graph_list]

fig = plt.figure()
plt.hist(x, bins=100, log=True, range=(1,100))
plt.title('出現頻度ヒストグラム')
plt.xlabel('出現頻度')
plt.ylabel('単語数')
plt.show()
fig.savefig('38_image.png')

f:id:D_PLIUS:20200520162736p:plain
38_image
計数後にsurfaceを無視し、出現回数のみをリストに保存する。
plt.hist()で100本のヒストグラムを描画。
視認性をあげるためにlog=Trueとした。

39

import matplotlib.pyplot as plt
import matplotlib
import collections
matplotlib.rcParams['font.family'] = 'Hiragino Maru Gothic Pro'

path = 'neko.txt.mecab'

word_array = []
with open(path) as r:
    r_lines = r.read()
r_split = r_lines.splitlines()
for line in r_split: #line(example) = {一\t名詞,数,*,*,*,*,一,イチ,イチ}
    if line == 'EOS': # EOSは辞書登録しない。
        continue
    #EOSでない単語は辞書登録
    surface = line.split('\t')[0]
    base = line.split('\t')[1].split(',')[6]
    pos = line.split('\t')[1].split(',')[0]
    pos1 = line.split('\t')[1].split(',')[1]
    newword_dict = {'surface':surface, 'base':base, 'pos':pos, 'pos1':pos1}
    word_array.append(newword_dict)

surface_list = [d['surface'] for d in word_array]
word_count_by_surface = collections.Counter(surface_list)
graph_list = word_count_by_surface.most_common()


y = [taple[1] for taple in graph_list]
x = range(1,len(y)+1)

fig = plt.figure()
plt.scatter(x, y, marker='.')
plt.xscale('log')
plt.yscale('log')
plt.title('Zipfの法則')
plt.xlabel('順位')
plt.ylabel('出現回数')
plt.show()
fig.savefig('39_image.png')

出力は

f:id:D_PLIUS:20200520164130p:plain
39_image
yは問題38のxと同様に出現回数である。
順位であるxは単純にrangeで生成。1から始める。
マーカを小さな丸とする散布図をplt.scatter(x, y, marker='.')で描画した。
Zipfの法則とは、

出現頻度が k 番目に大きい要素が全体に占める割合が 1/k に比例するという経験則
ジップの法則 - Wikipedia

であり、両対数グラフで直線的になっていることで確認できる。