言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。
第4章: 形態素解析
夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.
なお,問題37, 38, 39はmatplotlibもしくはGnuplotを用いるとよい.
###34. 「AのB」
2つの名詞が「の」で連結されている名詞句を抽出せよ.
####出来上がったコード:
# coding: utf-8
import MeCab
fname = 'neko.txt'
fname_parsed = 'neko.txt.mecab'
def parse_neko():
'''「吾輩は猫である」を形態素解析
「吾輩は猫である」(neko.txt)を形態素解析してneko.txt.mecabに保存する
'''
with open(fname) as data_file, \
open(fname_parsed, mode='w') as out_file:
mecab = MeCab.Tagger()
out_file.write(mecab.parse(data_file.read()))
def neco_lines():
'''「吾輩は猫である」の形態素解析結果のジェネレータ
「吾輩は猫である」の形態素解析結果を順次読み込んで、各形態素を
・表層形(surface)
・基本形(base)
・品詞(pos)
・品詞細分類1(pos1)
の4つをキーとする辞書に格納し、1文ずつ、この辞書のリストとして返す
戻り値:
1文の各形態素を辞書化したリスト
'''
with open(fname_parsed) as file_parsed:
morphemes = []
for line in file_parsed:
# 表層形はtab区切り、それ以外は','区切りでバラす
cols = line.split('\t')
if(len(cols) < 2):
raise StopIteration # 区切りがなければ終了
res_cols = cols[1].split(',')
# 辞書作成、リストに追加
morpheme = {
'surface': cols[0],
'base': res_cols[6],
'pos': res_cols[0],
'pos1': res_cols[1]
}
morphemes.append(morpheme)
# 品詞細分類1が'句点'なら文の終わりと判定
if res_cols[1] == '句点':
yield morphemes
morphemes = []
# 形態素解析
parse_neko()
# 1文ずつ辞書のリストを取得し抽出
list_a_no_b = [] # 出現順リスト、重複あり
lines = neco_lines()
for line in lines:
if len(line) > 2:
for i in range(1, len(line) - 1):
if line[i]['surface'] == 'の' \
and line[i - 1]['pos'] == '名詞' \
and line[i + 1]['pos'] == '名詞':
list_a_no_b.append(line[i - 1]['surface'] + 'の' + line[i + 1]['surface'])
a_no_b = set(list_a_no_b) # 重複除去
# 確認しやすいようlist_a_no_bを使って出現順にソートして表示
print(sorted(a_no_b, key=list_a_no_b.index))
####実行結果:
長いので先頭部分のみです。
['彼の掌', '掌の上', '書生の顔', 'はずの顔', '顔の真中', '穴の中', '書生の掌', '掌の裏', '何の事', '肝心の母親', '藁の上', '笹原の中', '池の前', '一樹の蔭', '垣根の穴', '隣家の三', '時の通路', '一刻の猶予', '家の内', '彼の書生', '以外の人間', '前の書生', 'おさんの隙', 'おさんの三', '胸の痞', '家の主人', '主人の方', '鼻の下', '吾輩の顔', '自分の住', '吾輩の主人', '家のもの', 'うちのもの', '彼の書斎', '本の上', '皮膚の色', '彼の毎夜', '以外のもの', '主人の傍', '彼の膝', '膝の上', '経験の上', '飯櫃の上', '炬燵の上', 'ここのうち', '供の寝床', '彼等の中間', '供の一', '例の神経', '性の主人', '次の部屋', '自分の勝手', 'へっついの中', '吾輩の方',
結果全体はGitHubにアップしています。
###range()
の仕様
parse_neko()
とneco_lines()
は前問と同じです。今回は先にリストを作り、その後、重複除去のために集合に変換しました。内包表記は混乱するので未挑戦です^^;
今回の条件に該当するためには最低でも形態素が3つ必要ですので、形態素が2つ以下の行は調べる必要がありません。そのため、最初のループの直後でif len(line) > 2:
というチェックを入れています。ただし、この条件はなくても正しく動作することに気づきました。それは次のrange(1, len(line) - 1)
が、len(line)
が2以下の時でもいい具合に動いてくれるためです。
>>> list(range(1, -1))
[]
>>> list(range(1, 0))
[]
>>> list(range(1, 1))
[]
>>> list(range(1, 2))
[1]
>>> list(range(1, 3))
[1, 2]
リストが空になれば実害がありません。そのため、if len(line) > 2:
のチェックは不要ということですね。とりあえず分かりやすいように、コードにはチェックを入れたままにしてあります。range()
の仕様の詳細はこちらをご参照ください。
35本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第4章で用いているデータは青空文庫で公開されている夏目漱石の長編小説『吾輩は猫である』が元になっています。