言語処理100本ノック2020 第4章 形態素解析

はじめに

言語処理100本ノック2020

言語処理100本ノックは東北大学が公開している自然言語処理の問題集です。

とても良質なコンテンツで企業の研修や勉強会で使われています。

そんな言語処理100本ノックが2020年に改定されてました。昨今の状況を鑑みて、深層ニューラルネットワークに関する問題が追加されました。(その他にも細かい変更があります)

この記事では、言語処理100本ノック2020にPythonで取り組んでいきます。

他にも色々な解法があると思うので、一つの解答例としてご活用ください!

全100問の解説に戻る

準備

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

mecabをインストールしていない方はインストールしておいて下さい。Macの場合、下記のコマンドでOKです。

1
2
brew install mecab
brew install mecab-ipadic

リンクからneko.txtをダウンロードして、下記のコマンドを実行します。

1
mecab < neko.txt > neko.txt.mecab

これで準備完了です。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)

MeCab公式ページによると、出力フォーマットは以下のようになるとあったので、これを参考にした。

表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音

31. 動詞

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

1
2
3
4
5
6
7
8
9
10
11
12
13
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text[:-1]:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)
[d['surface'] for d in result if d['pos'] == '動詞' ]

表層形は、baseをキーにして格納されています。

32. 動詞の原形

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

1
2
3
4
5
6
7
8
9
10
11
12
13
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text[:-1]:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)
[d['base'] for d in result if d['pos'] == '動詞' ]

原形は、baseをキーにして格納されています。

33. 「AのB」

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text[:-1]:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)
noun_phrase = []
for i in range(len(result)-2):
if (result[i]['pos'] == '名詞' and result[i+1]['surface'] == 'の' and result[i+2]['pos'] == '名詞'):
noun_phrase.append(result[i]['surface']+result[i+1]['surface']+result[i+2]['surface'])
print (noun_phrase)

ループを回して、愚直に条件を確認しています

34. 名詞の連接

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text[:-1]:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)

ls_noun = []
noun = ''
for d in result:
if d['pos']=='名詞':
noun += d['surface']
else:
if noun != '':
ls_noun.append(noun)
noun = ''
else:
if noun != '':
ls_noun.append(noun)
noun = ''
print (ls_noun)

名詞を連結して貯めておき、名詞以外が出現したらリストに追加しています。

35. 単語の出現頻度

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from collections import Counter
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text[:-1]:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)


surface = [d['surface'] for d in result]
c = Counter(surface)
print (c.most_common())

標準ライブラリのcollections.Counterを使うと良いでしょう。

36. 頻度上位10語

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from collections import Counter
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'AppleGothic'
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text[:-1]:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)


surface = [d['surface'] for d in result]
c = Counter(surface)
target = list(zip(*c.most_common(10)))
plt.bar(*target)
plt.show()

上位を取得する際にはCounter.most_commonが便利です。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from collections import Counter
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'AppleGothic'
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
tmp_cooccurrence = []
cooccurrence = []
inCat = False

for line in text[:-1]:
if line == 'EOS':
if inCat:
cooccurrence.extend(tmp_cooccurrence)
else:
pass
tmp_cooccurrence = []
inCat = False
continue

ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)
if ls[0]!='猫':
tmp_cooccurrence.append(ls[0])
else:
inCat = True


c = Counter(cooccurrence)
target = list(zip(*c.most_common(10)))
plt.bar(*target)
plt.show()

猫が出現する文章に含まれる単語をリストに保存していけば良いです。

38. ヒストグラム

単語の出現頻度のヒストグラム(横軸に出現頻度,縦軸に出現頻度をとる単語の種類数を棒グラフで表したもの)を描け.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from collections import Counter
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'AppleGothic'
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text[:-1]:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)


surface = [d['surface'] for d in result]
c = Counter(surface)
plt.hist(c.values(), range = (1,10))
plt.show()

plt.histでヒストグラムを作成します。

39. Zipfの法則

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from collections import Counter
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.family'] = 'AppleGothic'
path = 'neko.txt.mecab'
with open(path) as f:
text = f.read().split('\n')
result = []
for line in text[:-1]:
if line == 'EOS':
continue
ls = line.split('\t')
d = {}
tmp = ls[1].split(',')
d = {'surface':ls[0], 'base':tmp[6], 'pos':tmp[0], 'pos1':tmp[1]}
result.append(d)
surface = [d['surface'] for d in result]
c = Counter(surface)
v = [kv[1] for kv in c.most_common()]
plt.scatter(np.log(range(len(v))),np.log(v))
plt.show()

データをnp.logで変換しました。軸を対数で取っても良いと思います。

最後に

全100問の解説に戻る

記事情報

  • 投稿日:2020年5月9日
  • 最終更新日:2020年5月9日