3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

言語処理100本ノック 第4章: 形態素解析

Last updated at Posted at 2020-06-11

第4章:形態素解析

夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.

なお,問題37, 38, 39はmatplotlibもしくはGnuplotを用いるとよい.

記事の説明

  • この記事は言語処理にもpythonにも素人な学生が言語処理100本ノック 2020を解いた結果を載せている記事です。間違いや改善点の指摘には大変喜びますので、よろしくお願いします。
  • pythonの勉強のために、pycharmのinspectionに従いまくっているので、無駄なコードが多々あるかもしれないです。
    • pycharm上で""が入力できない問題が解決できないので、今回はAtomを使っています。
  • 1〜3章は飛ばします。

** 環境 **

  • MacBook Pro (13-inch, 2016, Thunderbolt 3ポートx 2)
  • macOS Catalina 10.15.5
  • Python 3.8.1 (Anacondaじゃない)

事前準備

夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.

とのことですので、以下のプログラムを実行し作成します。

**作成したプログラム(click)**
pre_processing.py
# -*- coding: utf-8 -*-

import MeCab
from MeCab import Tagger

analyser: Tagger = MeCab.Tagger("-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd")

with open('./neko.txt', "r") as infile:
    lines = infile.readlines()

with open('./neko.txt.mecab.txt', "w") as outfile:
    for line in lines:
        mors = analyser.parse(line)
        outfile.write(mors)

note

  • 出力された形態素をみてみると、最初の一行目の「吾輩は猫である」が固有名詞として識別されている。どうでもいいけど。

  • 出力フォーマットは(mecabによると)
    表層¥t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音
    となっており要素数10になるはずが、たまに以下のような要素数8になる行があった。(要素数9はない)
    ['頸筋', '名詞', '一般', '*', '*', '*', '*', '*\n'] ['ぎりご', '名詞', '一般', '*', '*', '*', '*', '*\n'] ['韲', '名詞', '一般', '*', '*', '*', '*', '*\n']
    省略されるのかな。

30. 形態素解析結果の読み込み

形態素解析結果(neko.txt.mecab)を読み込むプログラムを実装せよ.ただし,各形態素は表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をキーとするマッピング型に格納し,1文を形態素(マッピング型)のリストとして表現せよ.第4章の残りの問題では,ここで作ったプログラムを活用せよ.

**作成したプログラム(click)**
k30input.py
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import re
import sys

def input_macab(filename):
    with open(filename, "r") as infile:
        sentences = []
        sentence =[]
        for line in infile.readlines():
            # 表層¥t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音
            if line == 'EOS\n':
                if len(sentence) > 0:
                    sentences.append(sentence)
                    sentence =[]
                continue

            sline = re.split('[,\t]', line)

            if len(sline) < 8:
                print("### 読み込みエラー:\n", sline + "\n")
                sys.exit(1)

            sentence.append({'surface': sline[0], 'base': sline[7], 'pos': sline[1], 'pos1': sline[2] })

    print("** 読み込み完了 **\n")
    return sentences

if __name__ == '__main__':
    filename = "neko.txt.mecab.txt"
    ss = input_macab(filename)
    print("")
    print("mainとして実行されました。")

note

  • 他の問題でも使うので関数化しました。
  • 品詞 = pos = part of speech

31.動詞

動詞の表層形をすべて抽出せよ.

**作成したプログラム(click)**
k31verb_surface.py
# -*- coding: utf-8 -*-
import k30input

sentences = k30input.input_macab("neko.txt.mecab.txt")

for sentence in sentences:
    for mor in sentence:
        if mor['pos']=="動詞":
            print(mor['surface'])

32. 動詞の原形

動詞の原形をすべて抽出せよ.

**作成したプログラム(click)**
k32verb_base.py
# -*- coding: utf-8 -*-
import k30input

sentences = k30input.input_macab("neko.txt.mecab.txt")

for sentence in sentences:
    for mor in sentence:
        if mor['pos']=="動詞":
            print(mor['base'])

33. 「AのB」

2つの名詞が「の」で連結されている名詞句を抽出せよ.

**作成したプログラム(click)**
k33noun_no_noun.py
# -*- coding: utf-8 -*-
import k30input

sentences = k30input.input_macab("neko.txt.mecab.txt")

noun_flag = 0
no_flag = 0
noun1: str

for sentence in sentences:
    for mor in sentence:
        if noun_flag == 0 :
            if mor['pos']=="名詞":
                noun_flag = 1
                noun1 = mor['surface']
        elif noun_flag == 1 and no_flag == 0:
            if mor['surface']=="":
                no_flag = 1
            else:
                noun1 = ""
                noun_flag = no_flag = 0
        elif noun_flag == 1 and no_flag == 1:
            if mor['pos']=="名詞":
                print(noun1+""+mor['surface'])
                noun_flag = no_flag = 0

34. 名詞の連接

名詞の連接(連続して出現する名詞)を最長一致で抽出せよ.

**作成したプログラム(click)**
k34nounoun_longest.py
# -*- coding: utf-8 -*-
import k30input

sentences = k30input.input_macab("neko.txt.mecab.txt")

nouns = []

for sentence in sentences:
    for mor in sentence:
        if mor['pos']=="名詞":
            nouns.append(mor['surface'])
        else:
            if len(nouns) > 1:
                for i in nouns:
                    print(i+" ", end="")
                print("")
            nouns = []

note

  • わかりやすいように名詞間に空白を挟んで出力している。
  • 表層系が同じ「ようやく」でも、mecabは文脈によって副詞と判断されたり、名詞と判断されたりするのを忘れていて、戸惑った。

35. 単語の出現頻度

文章中に出現する単語とその出現頻度を求め,出現頻度の高い順に並べよ.

**作成したプログラム(click)**
k35word_freq.py
# -*- coding: utf-8 -*-
import k30input

sentences = k30input.input_macab("neko.txt.mecab.txt")

nouns = []

mor_freq = dict()

for sentence in sentences:
    for mor in sentence:
        # キーは(表層系, 品詞)のタプル, 値は出現数。
        mor_freq.setdefault((mor['surface'], mor['pos']), 0)
        mor_freq[(mor['surface'], mor['pos'])] = mor_freq[(mor['surface'], mor['pos'])] + 1

ranking = sorted(mor_freq.items(), key=lambda i: i[1], reverse=True)

for i in ranking:
    print(i)

note

  • 表層語が同じ形態素でも、品詞が違うものは区別して数えた。
  • 例えば、副詞の「ようやく」と名詞の「ようやく」を区別して数えている。

36. 頻度上位10語

出現頻度が高い10語とその出現頻度をグラフ(例えば棒グラフなど)で表示せよ.

**作成したプログラム(click)**
k36word10_graph.py
# -*- coding: utf-8 -*-
from matplotlib import pyplot
import k30input

sentences = k30input.input_macab("neko.txt.mecab.txt")

nouns = []

mor_freq = dict()

for sentence in sentences:
    for mor in sentence:
        # キーは(表層系, 品詞)のタプル, 値は出現数。
        mor_freq.setdefault((mor['surface'], mor['pos']), 0)
        mor_freq[(mor['surface'], mor['pos'])] = mor_freq[(mor['surface'], mor['pos'])] + 1

ranking = sorted(mor_freq.items(), key=lambda i: i[1], reverse=True)

top10 = ranking[0:10]

x = []
y = []
for i in top10:
    x.append(i[0][0])
    y.append(i[1])

pyplot.bar(x, y)

#グラフタイトル
pyplot.title('頻度上位10語')

#グラフの軸
pyplot.xlabel('形態素')
pyplot.ylabel('頻度')

pyplot.show()

note

  • matplotlibのデフォルトのフォントでは、日本語が表示できないのでフォントを切り替えた。
    • 具体的には、<パッケージ保管場所>/matplotlib/mpl-data/matplotlibrcが設定ファイルなので、そこにfont.family : Hiragino Maru Gothic Proを追加した。(Mac標準搭載フォント)

37. 「猫」と共起頻度の高い上位10語

「猫」とよく共起する(共起頻度が高い)10語とその出現頻度をグラフ(例えば棒グラフなど)で表示せよ.

**作成したプログラム(click)**
k37co_cat.py
# -*- coding: utf-8 -*-
from matplotlib import pyplot
import k30input

sentences = k30input.input_macab("neko.txt.mecab.txt") # neko.txt.mecab.clean.txt

nouns = []

tmp_count = dict()
co_cat_count = dict()
cat_flag = 0

for sentence in sentences:
    for mor in sentence:
        # キーは(表層系, 品詞)のタプル, 値は出現数。
        tmp_count.setdefault((mor['surface'], mor['pos']), 0)
        tmp_count[(mor['surface'], mor['pos'])] = tmp_count[(mor['surface'], mor['pos'])] + 1
        if mor['surface'] == "":
            cat_flag = 1

    if cat_flag == 1:
        for k, v in tmp_count.items():
            co_cat_count.setdefault(k, 0)
            co_cat_count[k] = co_cat_count[k] + v
        cat_flag = 0
    tmp_count = {}

ranking = sorted(co_cat_count.items(), key=lambda i: i[1], reverse=True)

top10 = ranking[0:10]

x = []
y = []
for i in top10:
    x.append(i[0][0])
    y.append(i[1])

pyplot.bar(x, y)

#グラフタイトル
pyplot.title('「猫」と共起頻度の高い上位10語')

#グラフの軸
pyplot.xlabel('形態素')
pyplot.ylabel('頻度')

pyplot.show()

note

共起(きょうき,英:Co-occurrence)は、ある単語がある文章(または文)中に出たとき、その文章(文)中に別の限られた単語が頻繁に出現すること。1

  • 今回だと"猫"が含まれる文中に、よく出る単語(形態素)TOP10ということですね。
  • "猫"自体は本来除外するべきですが、一つの基準として面白いのと、消そうと思えば簡単に消せるので残しました。

結果

1 2 3 4 5 6 7 8 9 10

句点・助詞・助動詞も単語としてカウントしているので結果が、"猫"だからこその結果という感じがしなくて面白くないですね。つまり、どんな単語だってこれらの単語とは共起するでしょうから、面白くない。

結果2

ということで、neko.txt.mecabから品詞が句点・助詞・助動詞の形態素情報を除外したファイルneko.txt.mecab.clean.txtを用意してやってみた結果

1 2 3 4 5 6 7 8 9 10
吾輩 いる ある する 人間 この
  • 幾分ましになったものの、まだ"猫"の特徴が捕らえられている感じはしませんね。
    • 「いる、ある、する」なんてどの文にだって入っているでしょうからね。
  • "の"は名詞判定されるものがあって、除外されずにランクインしていて煩わしい
    • 名詞"の"ってどんなの?
      • A. 「野」と形式名詞(例文: 僕が見つからない)の場合など2
  • こういった全ての単語と共起頻度の高い単語というのも計算して、除外するようにプログラムすれば、もっと意味のある共起頻度を求めることができますが、面倒なのでしません。
  • 現状の共起頻度の結果から得られる意味のある情報は、名著『吾輩は猫である』では「"猫"と"人間"がよく共起する」ということぐらいでしょうか。
    • 『吾輩は猫である』では、人間と猫を対比する文章が多いんでしょうかね。
    • あとは猫の一人称は吾輩だろうなとかも(あまりに有名な事実)

38. ヒストグラム

単語の出現頻度のヒストグラムを描け.ただし,横軸は出現頻度を表し,1から単語の出現頻度の最大値までの線形目盛とする.縦軸はx軸で示される出現頻度となった単語の異なり数(種類数)である.

**作成したプログラム(click)**
k38histogram.py
# -*- coding: utf-8 -*-
from matplotlib import pyplot
import k30input

sentences = k30input.input_macab("neko.txt.mecab.txt") # neko.txt.mecab.clean.txt

nouns = []

tmp_count = dict()
co_cat_count = dict()
cat_flag = 0

for sentence in sentences:
    for mor in sentence:
        # キーは(表層系, 品詞)のタプル, 値は出現数。
        tmp_count.setdefault((mor['surface'], mor['pos']), 0)
        tmp_count[(mor['surface'], mor['pos'])] = tmp_count[(mor['surface'], mor['pos'])] + 1
        if mor['surface'] == "":
            cat_flag = 1

    if cat_flag == 1:
        for k, v in tmp_count.items():
            co_cat_count.setdefault(k, 0)
            co_cat_count[k] = co_cat_count[k] + v
        cat_flag = 0
    tmp_count = {}

ranking = sorted(co_cat_count.items(), key=lambda i: i[1], reverse=True)

x = []
for i in ranking:
    x.append(i[1])

pyplot.hist(x, range=(0,ranking[0][1]))

#グラフタイトル
pyplot.title('単語の出現頻度')

#グラフの軸
pyplot.xlabel('出現頻度')
pyplot.ylabel('種類数')

pyplot.show()

note

  • 他のqiitaの投稿者で、この縦軸を出現頻度にしている方がいましたが、縦軸はその頻度で現れている単語数(異なり数)です。

結果

Figure_1.png

対数ヒストグラムにするとこんな感じ

Figure_1.png

39. Zipfの法則

単語の出現頻度順位を横軸,その出現頻度を縦軸として,両対数グラフをプロットせよ.

**作成したプログラム(click)**
k39loglog_graph.py
# -*- coding: utf-8 -*-
from matplotlib import pyplot
import k30input
import numpy as np

sentences = k30input.input_macab("neko.txt.mecab.txt") # neko.txt.mecab.clean.txt

nouns = []

tmp_count = dict()
co_cat_count = dict()
cat_flag = 0

for sentence in sentences:
    for mor in sentence:
        # キーは(表層系, 品詞)のタプル, 値は出現数。
        tmp_count.setdefault((mor['surface'], mor['pos']), 0)
        tmp_count[(mor['surface'], mor['pos'])] = tmp_count[(mor['surface'], mor['pos'])] + 1
        if mor['surface'] == "":
            cat_flag = 1

    if cat_flag == 1:
        for k, v in tmp_count.items():
            co_cat_count.setdefault(k, 0)
            co_cat_count[k] = co_cat_count[k] + v
        cat_flag = 0
    tmp_count = {}

ranking = sorted(co_cat_count.items(), key=lambda i: i[1], reverse=True)

y = []
for i in ranking:
    y.append(i[1])
x = range(len(ranking))

print("size", len(ranking))

pyplot.title('単語の出現頻度')
pyplot.xlabel('出現頻度順位 log(y)')
pyplot.ylabel('種類数 log(x)')

# scatter(散布図)で対数メモリにする方法が分からなかったので、ここだけカンニングした。だからnumpy使ってる。
pyplot.scatter(np.log(x),np.log(y))
pyplot.show()

結果

Figure_1.png

####note

  • Zipfの法則

出現頻度が k 番目に大きい要素が全体に占める割合が1/kに比例するという経験則である。3

  • この法則って自然言語だけの経験則ってわけではなく、様々な現象に成り立つらしいです。

ウィキペディア(30ヶ国語版)における単語の出現頻度

  • ウィキペディア(30ヶ国語版)における単語の出現頻度です。似てますね。3

感想

まだまだpythonのお勉強の範疇です。だというのに、numpy, pandas, collectionsモジュールを勉強するのがめんどくさくて、使わずに済ませた。けどもしかして使わない方が難しくないか?
あと、同じような処理は関数化してカッコよく済ませたかった。
次回に続く。(確実にやる)

  1. 共起 - Wikipedia

  2. の - ウィクショナリー日本語版

  3. ジップの法則 - Wikipedia 2

3
3
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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?