1
2

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.

GiNZA v3.1で言語処理100本ノック 2020をやった 第4章

Posted at

はじめに

満を持して言語処理100本ノック 2020が公開されたので早速やってみます。

第4章ではMeCabで形態素解析します。が、せっかくなのでGiNZAでやります(といっても、最初しか使いませんでしたが)。

第4章: 形態素解析

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

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

コード
import spacy
import pandas as pd
import pprint
from functools import reduce
import collections
import matplotlib.pyplot as plt
import seaborn as sns

with open('neko.txt') as f:
    raw_text = f.read()
    # 先頭の余計な文字を削除
    raw_text = raw_text.replace('\n\n ', '')
    nlp = spacy.load('ja_ginza')
    doc = nlp(raw_text)
    with open('neko.txt.ginza', 'a') as f2:
        for sent in doc.sents:
            for token in sent:
                # 番号、表層系、基本形、品詞を出力
                f2.write(','.join([str(token.i), token.orth_, token.lemma_, token.tag_]) + '\n')

GiNZAはコマンドラインではMeCabと同じ出力が出せるようですが、PythonからのAPIは見つけられなかったためそのまま出力しています。

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

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

コード
neko_df = pd.read_csv('neko.txt.ginza', header=None)

docs = []
sentence = []
for row in neko_df.itertuples():
    pos, *pos1 = row[4].split('-')
    neko_dict = {
        'surface': row[2],
        'base': row[3],
        'pos': pos,
        'pos1': pos1
    }
    sentence.append(neko_dict)
    # 。で区切る
    if row[2] == '':
        docs.append(sentence)
        sentence = []
pprint.pprint(docs[0])
出力結果
[{'base': '我が輩', 'pos': '代名詞', 'pos1': [], 'surface': '吾輩'},
 {'base': 'は', 'pos': '助詞', 'pos1': ['係助詞'], 'surface': 'は'},
 {'base': '猫', 'pos': '名詞', 'pos1': ['普通名詞', '一般'], 'surface': '猫'},
 {'base': 'だ', 'pos': '助動詞', 'pos1': [], 'surface': 'で'},
 {'base': '有る', 'pos': '動詞', 'pos1': ['非自立可能'], 'surface': 'ある'},
 {'base': '。', 'pos': '補助記号', 'pos1': ['句点'], 'surface': '。'}]

31. 動詞

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

コード
surfaces = []
for sentence in docs:
    for morpheme in sentence:
        surfaces.append(morpheme['surface'])
print(surfaces[:30])
出力結果
['吾輩', 'は', '猫', 'で', 'ある', '。', '名前', 'は', 'まだ', '無い', '。', 'どこ', 'で', '生れ', 'た', 'か', 'と', 'んと', '見当', 'が', 'つか', 'ぬ', '。', '何', 'で', 'も', '薄暗い', 'じめじめ', 'し', 'た']

32. 動詞の原形

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

コード
bases = []
for sentence in docs:
    for morpheme in sentence:
        bases.append(morpheme['base'])
print(bases[:30])
出力結果
['我が輩', 'は', '猫', 'だ', '有る', '。', '名前', 'は', '未だ', '無い', '。', 'どこ', 'で', '生まれる', 'た', 'か', 'と', 'うんと', '見当', 'が', '付く', 'ず', '。', '何', 'で', 'も', '薄暗い', 'じめじめ', '為る', 'た']

33. 「AのB」

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

コード
nouns = []
for sentence in docs:
    for i in range(len(sentence) - 2):
        if sentence[i]['pos'] == '名詞' and sentence[i + 1]['surface'] == '' and sentence[i + 2]['pos'] == '名詞':
            nouns.append(sentence[i]['surface'] + sentence[i + 1]['surface'] + sentence[i + 2]['surface'])
print(nouns[:30])
出力結果
['掌の上', '書生の顔', 'ものの見', 'はずの顔', '顔の真中', '穴の中', '書生の掌', '掌の裏', '今までの所', '藁の上', '笹原の中', '池の前', '池の上', '一樹の蔭', '垣根の穴', '隣家の三毛', '時の通路', '一刻の猶予', '家の内', '以外の人間', '前の書生', 'おさんの隙', 'おさんの三', '胸の痞', '家の主人', '主人の方', 'なしの小猫', '鼻の下', '自分の住家', '家のもの']

34. 名詞の連接

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

コード
nouns2 = []
for sentence in docs:
    word = ''
    count = 0
    for morpheme in sentence:
        if morpheme['pos'] == '名詞':
            word += morpheme['surface']
            count += 1
        else:
            if count >= 2:
                nouns2.append(word)
            word = ''
            count = 0 
print(nouns2[:30]) 
出力結果
['見始', '時妙', '一毛', '後猫', '上今まで', 'うち池', '書生以外', '間おさん', '宿なし', 'まま奥', '終日書斎', 'ぎりほとんど', '時々忍び足', '二三ページ', '主人以外', '朝主人', '椽側', '一間', '神経胃弱', '時々同衾', '言語同断', '家内総がかり', '先日玉', '一部始終', 'いくら人間', '後架先生', 'そら宗盛', '一月', '月給日', '水彩絵具']

35. 単語の出現頻度

コード
# 二次元リストをflatten
words = reduce(list.__add__, docs)
# 単語だけを抜き出し
words = collections.Counter(map(lambda e: e['surface'], words))
# 出現頻度を算出
words = words.most_common()
# 出現頻度順にソート
words = sorted(words, key=lambda e: e[1], reverse=True)
print(words[:30])
出力結果
[('の', 9546), ('。', 7486), ('て', 7401), ('に', 7047), ('、', 6772), ('は', 6485), ('と', 6150), ('を', 6118), ('が', 5395), ('で', 4542), ('た', 3975), ('「', 3238), ('」', 3238), ('も', 3229), ('だ', 2705), ('し', 2530), ('ない', 2423), ('から', 2213), ('か', 2041), ('ある', 1729), ('ん', 1625), ('な', 1600), ('いる', 1255), ('事', 1214), ('する', 1056), ('もの', 1005), ('へ', 998), ('です', 978), ('君', 967), ('云う', 937)]

36. 頻度上位10語

コード
words_df = pd.DataFrame(words[:10], columns=['word', 'count'])
sns.set(font='AppleMyungjo')

fig = plt.figure()
ax = fig.add_subplot(111)
ax.bar(words_df['word'], words_df['count'])
plt.show()

出力結果
36.png

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

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

コード
cats = []
for sentence in docs:
    cat_list = list(filter(lambda e: e['surface'] == '', sentence))
    if len(cat_list) > 0:
        for morpheme in sentence:
            if morpheme['surface'] != '':
                cats.append(morpheme['surface'])
cats = collections.Counter(cats)
# 出現頻度を算出
cats = cats.most_common()
# 出現頻度順にソート
cats = sorted(cats, key=lambda e: e[1], reverse=True)

cats_df = pd.DataFrame(cats[:10], columns=['word', 'count'])
sns.set(font='AppleMyungjo')

fig = plt.figure()
ax = fig.add_subplot(111)
ax.bar(cats_df['word'], cats_df['count'])
# plt.show()
plt.savefig('37.png')

出力結果
37.png

38. ヒストグラム

単語の出現頻度のヒストグラム(横軸に出現頻度,縦軸に出現頻度をとる単語の種類数を棒グラフで表したもの)を描け.

コード
hist_df = pd.DataFrame(words, columns=['word', 'count'])
sns.set(font='AppleMyungjo')

fig = plt.figure()
ax = fig.add_subplot(111)
ax.hist(hist_df['count'], range=(1, 100))
# plt.show()
plt.savefig('38.png')

出力結果
38.png

横軸は100までに制限しています。出現回数1付近が圧倒的に多いため、制限なしにして表示すると以下のようになってしまい、まともに可視化できません。
38_2.png

39. Zipfの法則

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

コード
zipf_df = pd.DataFrame(words, columns=['word', 'count'])
sns.set(font='AppleMyungjo')

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_yscale('log')
ax.set_xscale('log')
ax.plot(zipf_df['count'])
# plt.show()
plt.savefig('39.png')

出力結果
39.png

対数グラフにすることで見れるようになります。

おわりに

4章で学べること

  • 形態素解析のやり方
  • matplotlibによるグラフ出力
  • リスト、辞書操作
1
2
2

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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?