言語処理100本ノック第4章: UNIXコマンドの俺の解答。その他の章はこちら。
解答に入る前に環境について。Cygwin上のPythonを使っているのだが、MeCabについては公式Windowsバイナリを普通に入れた。辞書の文字コードはUTF-8を選択。そしてWindowsの環境変数PathにC:\Program Files (x86)\MeCab\binを追加。
するとCygwinコンソールから
$ mecab neko.txt -o neko.txt.mecab
というふうに何の問題もなく使えた。
あと、PythonからMecabを使うにはfugashiとかのラッパーがあるが、あえて使わずに。
30. 形態素解析結果の読み込み
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument('-o', help="outpuf filename")
args = parser.parse_args()
sys.stdin.reconfigure(encoding='utf-8')
sentences = []
sentence = []
for l in sys.stdin:
l = l.rstrip()
if l == 'EOS':
sentences.append(sentence)
sentence = []
else:
(surface, features) = l.split('\t')
features_list = features.split(',')
word = {}
word['surface'] = surface
word['base'] = features_list[6]
word['pos'] = features_list[0]
word['pos1'] = features_list[1]
sentence.append(word)
f_o = open(args.o, "w", encoding="utf_8", newline='\n')
f_o.write(str(sentences))
31-35
まとめて。上記30のsentencesに対する操作として。
def is_a_no_b(w0, w1, w2):
if w0['pos'] != '名詞':
return False
if w1['surface'] != 'の':
return False
if w1['pos'] != '助詞':
return False
if w2['pos'] != '名詞':
return False
return True
verbs = set()
a_no_b = set()
noun_phrases = set()
counts = {}
for s in sentences:
noun_phrase = []
for i in range(len(s)):
if s[i]['pos'] == '動詞':
verbs.add((s[i]['surface'], s[i]['base']))
if i >= 2 and is_a_no_b(s[i-2], s[i-1], s[i]):
a_no_b.add(s[i-2]['surface'] + 'の' + s[i]['surface'])
if s[i]['pos'] == '名詞':
noun_phrase.append(s[i]['surface'])
else:
if len(noun_phrase) > 1:
noun_phrases.add(''.join(noun_phrase))
noun_phrase = []
if s[i]['surface'] not in counts:
counts[s[i]['surface']] = 0
counts[s[i]['surface']] += 1
f_o.write(str(verbs)+'\n')
f_o.write(str(a_no_b)+'\n')
f_o.write(str(noun_phrases)+'\n')
counts_sorted = sorted(counts.items(),
key=lambda x:x[1],
reverse = True)
f_o.write(str(counts_sorted)+'\n')
ところで、文中に「御前は女だけれども many a slip 'twixt the cup and the lip と云う西洋の諺くらいは心得ているだろう」みたいに英語が出てくるところがあるんですね。名詞句の出力時に、間に半角スペース入れずに出すとアルファベットがくっついちゃってなんじゃこりゃになるのですが、まぁ見なかったことにしよう。
36. 頻度上位10語
import argparse
import sys
from matplotlib import pyplot
parser = argparse.ArgumentParser()
parser.add_argument('-o', help="outpuf filename")
args = parser.parse_args()
sys.stdin.reconfigure(encoding='utf-8')
sentences = []
sentence = []
for l in sys.stdin:
l = l.rstrip()
if l == 'EOS':
sentences.append(sentence)
sentence = []
else:
(surface, features) = l.split('\t')
sentence.append(surface)
counts = {}
for s in sentences:
for i in range(len(s)):
if s[i] not in counts:
counts[s[i]] = 0
counts[s[i]] += 1
counts_sorted = sorted(counts.items(),
key=lambda x:x[1],
reverse = True)
pyplot.rcParams['font.family'] = 'MS Gothic'
x = range(0, 10)
labels, y = zip(*counts_sorted[0:10])
pyplot.bar(x, y, tick_label=labels)
pyplot.savefig(args.o)
37. 「猫」と共起頻度の高い上位10語
import argparse
import sys
from matplotlib import pyplot
parser = argparse.ArgumentParser()
parser.add_argument('-o', help="outpuf filename")
args = parser.parse_args()
sys.stdin.reconfigure(encoding='utf-8')
sentences = []
sentence = []
for l in sys.stdin:
l = l.rstrip()
if l == 'EOS':
sentences.append(sentence)
sentence = []
else:
(surface, features) = l.split('\t')
sentence.append(surface)
counts = {}
for s in sentences:
if '猫' not in s:
continue
for w in s:
if w == '猫':
continue
if w not in counts:
counts[w] = 0
counts[w] += 1
counts_sorted = sorted(counts.items(),
key=lambda x:x[1],
reverse = True)
pyplot.rcParams['font.family'] = 'MS Gothic'
x = range(0, 10)
labels, y = zip(*counts_sorted[0:10])
pyplot.bar(x, y, tick_label=labels)
pyplot.savefig(args.o)
結局、助詞みたいな通常ストップワードにするようなものしか取れず、つまらない。
そこで、共起頻度を、文章全体の頻度で割ってみた。なお、後付けでの考察ですが、これは、共起しやすさを計測する方法のひとつであるMI(相互情報量)すなわち$log_2 \frac{C(x,y)\cdot N}{C(x)C(y)}$(参考)を使うのと同等ですね。なぜならxを「猫」で固定し、かつ値の絶対値ではなく値をソートした順位だけを使うとすると、$\frac{C(猫,y)}{C(y)}$を求めれば十分なので。
ところが、実際にやってみると、その値が1.0になる単語が沢山ある。つまり、現れるときは常に猫と一緒である単語が沢山あった。そこでさらに、そのような単語について、共起頻度でソートして上位をとってみた。(下記コードは、途中までは上記コードと一緒のため省略している。)
counts = {}
counts_cat = {}
for s in sentences:
for w in s:
if w not in counts:
counts[w] = 0
counts[w] += 1
if '猫' not in s:
continue
for w in s:
if w == '猫':
continue
if w not in counts_cat:
counts_cat[w] = 0
counts_cat[w] += 1
# 必ず猫と共起する単語のリスト
always_with_cat = {}
for w in counts_cat:
if counts_cat[w] == counts[w]:
always_with_cat[w] = counts[w]
always_with_cat_sorted = sorted(always_with_cat.items(),
key=lambda x:x[1],
reverse = True)
pyplot.rcParams['font.family'] = 'MS Gothic'
x = range(0, 10)
labels, y = zip(*always_with_cat_sorted[0:10])
pyplot.bar(x, y, tick_label=labels)
pyplot.savefig(args.o)
その結果がこちら:
ん?と思って調べてみたら、作中で、亡くなった猫に猫誉信女(みょうよしんにょ)という戒名が付けられているんですね。
38. ヒストグラム
下記コードも途中までは省略している。
counts = {}
for s in sentences:
for w in s:
if w not in counts:
counts[w] = 0
counts[w] += 1
max_count = max(counts.values())
x = range(1, max_count + 1)
histgram = dict.fromkeys(x, 0)
for w in counts:
histgram[counts[w]] += 1
pyplot.rcParams['font.family'] = 'MS Gothic'
pyplot.plot(x, histgram.values())
pyplot.savefig(args.o)
結果は横軸・縦軸ともに0とか1に張り付いた、ほとんど直角なグラフになっちゃうけど、それが次の問題への布石ってことでしょうね。
39. Zipfの法則
下記コードも途中までは省略している。
counts = {}
for s in sentences:
for w in s:
if w not in counts:
counts[w] = 0
counts[w] += 1
counts_sorted = sorted(counts.values(), reverse=True)
pyplot.rcParams['font.family'] = 'MS Gothic'
pyplot.xscale('log')
pyplot.yscale('log')
pyplot.plot(range(1, len(counts_sorted) + 1), counts_sorted)
pyplot.savefig(args.o)