言語処理100本ノック(第4章)
yamakiy.hatenadiary.jp github.com
いよいよ4章
mecabは仕事でも使ったことがあるから早めに終わらせたい
第4章: 形態素解析
夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.
なお,問題37, 38, 39はmatplotlibもしくはGnuplotを用いるとよい.
最初にneko.txt.mecabを作成する
#!/usr/bin/python3.6 import MeCab if __name__ == "__main__": m = MeCab.Tagger() with open("./files/neko.txt", "r") as f: with open("./files/neko.txt.mecab", "w") as f2: f2.write(m.parse(f.read()))
neko.txt.mecab
一 名詞,数,*,*,*,*,一,イチ,イチ 記号,空白,*,*,*,*, , , 吾輩 名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ は 助詞,係助詞,*,*,*,*,は,ハ,ワ 猫 名詞,一般,*,*,*,*,猫,ネコ,ネコ で 助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ ある 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル 。 記号,句点,*,*,*,*,。,。,。 名前 名詞,一般,*,*,*,*,名前,ナマエ,ナマエ は 助詞,係助詞,*,*,*,*,は,ハ,ワ ~~~ 中略 ~~~ られ 動詞,接尾,*,*,一段,未然形,られる,ラレ,ラレ ぬ 助動詞,*,*,*,特殊・ヌ,基本形,ぬ,ヌ,ヌ 。 記号,句点,*,*,*,*,。,。,。 南無阿弥陀仏 名詞,一般,*,*,*,*,南無阿弥陀仏,ナムアミダブツ,ナムアミダブツ 南無阿弥陀仏 名詞,一般,*,*,*,*,南無阿弥陀仏,ナムアミダブツ,ナムアミダブツ 。 記号,句点,*,*,*,*,。,。,。 ありがたい 形容詞,自立,*,*,形容詞・アウオ段,基本形,ありがたい,アリガタイ,アリガタイ ありがたい 形容詞,自立,*,*,形容詞・アウオ段,基本形,ありがたい,アリガタイ,アリガタイ 。 記号,句点,*,*,*,*,。,。,。
30. 形態素解析結果の読み込み
形態素解析結果(neko.txt.mecab)を読み込むプログラムを実装せよ.ただし,各形態素は表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をキーとするマッピング型に格納し,1文を形態素(マッピング型)のリストとして表現せよ.第4章の残りの問題では,ここで作ったプログラムを活用せよ.
公式より、以下のMeCabは以下のフォーマットで解析を行っている
表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音
import re import pickle from pprint import pprint class NekoMecab(object): model = [] def __init__(self): self.load_model() # 形態素解析結果の読み込みを行う。 無ければ作成 def load_model(self): if os.path.exists("./files/neko.model.pickle"): with open("./files/neko.model.pickle", "rb") as f: self.model = pickle.load(f) else: self.save_model() # 形態素解析結果の作成 def save_model(self): with open("./files/neko.txt.mecab", "r") as f: for line in f: # 終了文字EOSが現れたら終了 if line == "EOS\n": break vals = re.split(r"\t|,", line) self.model.append({ "surface": vals[0], "base": vals[7], "pos": vals[1], "pos1": vals[2] }) with open("./files/neko.model.pickle", "wb") as f: pickle.dump(self.model, f) if __name__ == "__main__": nm = NekoMecab() pprint(nm.model) """ [{'base': '一', 'pos': '名詞', 'pos1': '数', 'surface': '一'}, {'base': '\u3000', 'pos': '記号', 'pos1': '空白', 'surface': '\u3000'}, {'base': '吾輩', 'pos': '名詞', 'pos1': '代名詞', 'surface': '吾輩'}, {'base': 'は', 'pos': '助詞', 'pos1': '係助詞', 'surface': 'は'}, {'base': '猫', 'pos': '名詞', 'pos1': '一般', 'surface': '猫'}, : """
以降も利用するとのことで、生成にも時間がかかるためpickleで保管
31. 動詞
動詞の表層形をすべて抽出せよ.
import re import pickle from pprint import pprint class NekoMecab(object): model = [] def __init__(self): self.load_model() # 形態素解析結果の読み込みを行う。 無ければ作成 def load_model(self): if os.path.exists("./files/neko.model.pickle"): with open("./files/neko.model.pickle", "rb") as f: self.model = pickle.load(f) else: self.save_model() # 形態素解析結果の作成 def save_model(self): with open("./files/neko.txt.mecab", "r") as f: for line in f: # 終了文字EOSが現れたら終了 if line == "EOS\n": break vals = re.split(r"\t|,", line) self.model.append({ "surface": vals[0], "base": vals[7], "pos": vals[1], "pos1": vals[2] }) with open("./files/neko.model.pickle", "wb") as f: pickle.dump(self.model, f) def filter_pos(self, pos): self.model = [i for i in self.model if pos in i["pos"]] return self if __name__ == "__main__": nm = NekoMecab() pprint(nm.filter_pos("動詞").model) """ [{'base': '生れる', 'pos': '動詞', 'pos1': '自立'}, {'base': 'つく', 'pos': '動詞', 'pos1': '自立'}, {'base': 'する', 'pos': '動詞', 'pos1': '自立'}, {'base': '泣く', 'pos': '動詞', 'pos1': '自立'}, {'base': 'する', 'pos': '動詞', 'pos1': '自立'} : """
32. 動詞の原形
動詞の原形をすべて抽出せよ.
import re import pickle from pprint import pprint class NekoMecab(object): model = [] def __init__(self): self.load_model() # 形態素解析結果の読み込みを行う。 無ければ作成 def load_model(self): if os.path.exists("./files/neko.model.pickle"): with open("./files/neko.model.pickle", "rb") as f: self.model = pickle.load(f) else: self.save_model() # 形態素解析結果の作成 def save_model(self): with open("./files/neko.txt.mecab", "r") as f: for line in f: # 終了文字EOSが現れたら終了 if line == "EOS\n": break vals = re.split(r"\t|,", line) self.model.append({ "surface": vals[0], "base": vals[7], "pos": vals[1], "pos1": vals[2] }) with open("./files/neko.model.pickle", "wb") as f: pickle.dump(self.model, f) def filter_pos(self, pos): self.model = [i for i in self.model if pos = i["pos"]] return self def get_surface(self): return [i["surface"] for i in self.model] if __name__ == "__main__": nm = NekoMecab() pprint(nm.filter_pos("動詞").get_surface()) """ ['で', 'ある', '生れ', 'た', 'つか', 'ぬ', 'し', : """
33. サ変名詞
サ変接続の名詞をすべて抽出せよ.
import re import pickle from pprint import pprint class NekoMecab(object): model = [] def __init__(self): self.load_model() # 形態素解析結果の読み込みを行う。 無ければ作成 def load_model(self): if os.path.exists("./files/neko.model.pickle"): with open("./files/neko.model.pickle", "rb") as f: self.model = pickle.load(f) else: self.save_model() # 形態素解析結果の作成 def save_model(self): with open("./files/neko.txt.mecab", "r") as f: for line in f: # 終了文字EOSが現れたら終了 if line == "EOS\n": break vals = re.split(r"\t|,", line) self.model.append({ "surface": vals[0], "base": vals[7], "pos": vals[1], "pos1": vals[2] }) with open("./files/neko.model.pickle", "wb") as f: pickle.dump(self.model, f) def filter(self, **kwargs): for key in kwargs: self.model = [i for i in self.model if kwargs[key] == i[key]] return self def get_surface(self): return [i["surface"] for i in self.model] if __name__ == "__main__": nm = NekoMecab() pprint(nm.filter(pos="名詞", pos1="サ変接続").get_surface()) """ ['見当', '記憶', '話', '装飾', '突起', '運転', : """
ちょっとリファクタ
34. 「AのB」
2つの名詞が「の」で連結されている名詞句を抽出せよ.
import re import pickle from pprint import pprint class NekoMecab(object): model = [] def __init__(self): self.load_model() # 形態素解析結果の読み込みを行う。 無ければ作成 def load_model(self): if os.path.exists("./files/neko.model.pickle"): with open("./files/neko.model.pickle", "rb") as f: self.model = pickle.load(f) else: self.save_model() # 形態素解析結果の作成 def save_model(self): with open("./files/neko.txt.mecab", "r") as f: for line in f: # 終了文字EOSが現れたら終了 if line == "EOS\n": break vals = re.split(r"\t|,", line) self.model.append({ "surface": vals[0], "base": vals[7], "pos": vals[1], "pos1": vals[2] }) with open("./files/neko.model.pickle", "wb") as f: pickle.dump(self.model, f) # 2つの名詞が「の」で連結されている名詞句 def get_A_no_B(self): res = [] for i in range(1, len(self.model) - 1): if self.model[i-1]["pos"] == self.model[i+1]["pos"] == "名詞" and self.model[i]["surface"] == "の": res.append(self.model[i-1]["surface"] + self.model[i]["surface"] + self.model[i+1]["surface"]) return res if __name__ == "__main__": nm = NekoMecab() pprint(nm.get_A_no_B()) """ ['彼の掌', '掌の上', '書生の顔', 'はずの顔', '顔の真中', : """
35. 名詞の連接
名詞の連接(連続して出現する名詞)を最長一致で抽出せよ.
import re import pickle from pprint import pprint class NekoMecab(object): model = [] def __init__(self): self.load_model() # 形態素解析結果の読み込みを行う。 無ければ作成 def load_model(self): if os.path.exists("./files/neko.model.pickle"): with open("./files/neko.model.pickle", "rb") as f: self.model = pickle.load(f) else: self.save_model() # 形態素解析結果の作成 def save_model(self): with open("./files/neko.txt.mecab", "r") as f: for line in f: # 終了文字EOSが現れたら終了 if line == "EOS\n": break vals = re.split(r"\t|,", line) self.model.append({ "surface": vals[0], "base": vals[7], "pos": vals[1], "pos1": vals[2] }) with open("./files/neko.model.pickle", "wb") as f: pickle.dump(self.model, f) # 連続した名詞句を取得 def get_continuous_nouns(self): res = [] nonus = [] for line in self.model: if line["pos"] == "名詞": nonus.append(line["surface"]) elif len(nonus) > 1: res.append("".join(nonus)) nonus = [] # 最後の連続した名詞が上記のforから漏れるからここで処理 if len(nonus) > 1: res.append("".join(nonus)) return res if __name__ == "__main__": nm = NekoMecab() pprint(nm.get_continuous_nouns()) """ ['一吾輩', '猫名前', 'どこ見当', '何所', 'ニャーニャーいた事', : """
36. 単語の出現頻度
文章中に出現する単語とその出現頻度を求め,出現頻度の高い順に並べよ.
import collections import re import pickle from pprint import pprint class NekoMecab(object): model = [] def __init__(self): self.load_model() # 形態素解析結果の読み込みを行う。 無ければ作成 def load_model(self): if os.path.exists("./files/neko.model.pickle"): with open("./files/neko.model.pickle", "rb") as f: self.model = pickle.load(f) else: self.save_model() # 形態素解析結果の作成 def save_model(self): with open("./files/neko.txt.mecab", "r") as f: for line in f: # 終了文字EOSが現れたら終了 if line == "EOS\n": break vals = re.split(r"\t|,", line) self.model.append({ "surface": vals[0], "base": vals[7], "pos": vals[1], "pos1": vals[2] }) with open("./files/neko.model.pickle", "wb") as f: pickle.dump(self.model, f) def get_word_frequency(self): res = [] surfaces = self.get_arg("surface") return collections.Counter(surfaces) if __name__ == "__main__": nm = NekoMecab() pprint(nm.get_word_frequency() """ Counter({'の': 9194, '。': 7486, 'て': 6873, '、': 6772, 'は': 6422, 'に': 6268, """
37. 頻度上位10語
出現頻度が高い10語とその出現頻度をグラフ(例えば棒グラフなど)で表示せよ.
グラフとのことなので、4章冒頭でも紹介されていたmatplotlibを入れる
pip3 install matplotlib
matplotlibはそのままだと日本語が使えないため、ipaからttcファイルを落としてくる
wget https://ipafont.ipa.go.jp/old/ipafont/IPAMTTC00303.php -O ipamttc.zip unzip ipamttc.zip
グラフをファイルに出力
import collections import os import re import pickle from pprint import pprint from matplotlib import pyplot from matplotlib.font_manager import FontProperties class NekoMecab(object): model = [] def __init__(self): self.load_model() # 形態素解析結果の読み込みを行う。 無ければ作成 def load_model(self): if os.path.exists("./files/neko.model.pickle"): with open("./files/neko.model.pickle", "rb") as f: self.model = pickle.load(f) else: self.save_model() # 形態素解析結果の作成 def save_model(self): with open("./files/neko.txt.mecab", "r") as f: for line in f: # 終了文字EOSが現れたら終了 if line == "EOS\n": break vals = re.split(r"\t|,", line) self.model.append({ "surface": vals[0], "base": vals[7], "pos": vals[1], "pos1": vals[2] }) with open("./files/neko.model.pickle", "wb") as f: pickle.dump(self.model, f) def get_word_frequency(self): res = [] surfaces = self.get_arg("surface") return collections.Counter(surfaces) def output_frequency_rank_img(self, rank_num): word_frequency = self.get_word_frequency() top10 = word_frequency.most_common(rank_num) height = [i[1] for i in top] xtick = [i[0] for i in top] fp = FontProperties(fname='./IPAMTTC00303/ipam.ttc', size=14) pyplot.bar(range(rank_num), height) pyplot.xticks(range(rank_num), xtick, fontproperties=fp) pyplot.title("頻度上位10語", fontproperties=fp) pyplot.xlabel("単語", fontproperties=fp) pyplot.ylabel("出現回数", fontproperties=fp) pyplot.savefig(f"./files/img/frequency_rank_top{rank_num}.png") if __name__ == "__main__": nm = NekoMecab() nm.output_frequency_rank_img(10)
38. ヒストグラム
単語の出現頻度のヒストグラム(横軸に出現頻度,縦軸に出現頻度をとる単語の種類数を棒グラフで表したもの)を描け.
def output_frequency_histogram_img(self): word_frequency = self.get_word_frequency() top = word_frequency.most_common() datas = [i[1] for i in top] fp = FontProperties(fname='./IPAMTTC00303/ipam.ttc', size=14) # 出現頻度の差が大きいため、上限30で表示を打ち切る pyplot.hist(datas, bins=30, range=(1, 30)) pyplot.title("単語の出現頻度ヒストグラム", fontproperties=fp) pyplot.xlabel("出現頻度", fontproperties=fp) pyplot.ylabel("単語の種類", fontproperties=fp) pyplot.savefig(f"./files/img/frequency_hist.png") if __name__ == "__main__": nm = NekoMecab() nm.output_frequency_histogram_img()
全部表示しようとすると、出現頻度の多い単語は重なりが少なく
見えづらいため上限30まででグラフを出力。
39. Zipfの法則
単語の出現頻度順位を横軸,その出現頻度を縦軸として,両対数グラフをプロットせよ.
def output_frequency_zipf_img(self): word_frequency = self.get_word_frequency() top = word_frequency.most_common() datas = [i[1] for i in top] fp = FontProperties(fname='./IPAMTTC00303/ipam.ttc', size=14) pyplot.scatter(range(len(datas)), datas) pyplot.xlim(1, len(datas) + 1) pyplot.title("zipf", fontproperties=fp) pyplot.xlabel("単語の出現頻度順", fontproperties=fp) pyplot.ylabel("出現頻度", fontproperties=fp) pyplot.xscale("log") pyplot.yscale("log") pyplot.savefig(f"./files/img/frequency_zipf.png") if __name__ == "__main__": nm = NekoMecab() nm.output_frequency_zipf_img()
思ったより時間かかってしまった・・・
11月4日までに終わらせるつもりがぎりぎり間に合わず
matoplotlibの使い方が身についてきた気がする