Edited at

素人の言語処理100本ノック:34

More than 1 year has passed since last update.

言語処理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つの名詞が「の」で連結されている名詞句を抽出せよ.



出来上がったコード:


main.py

# 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章で用いているデータは青空文庫で公開されている夏目漱石の長編小説『吾輩は猫である』が元になっています。