LoginSignup
7
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-11-09

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

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1