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

はじめに

今回は 言語処理100本ノック 第4章: 形態素解析 の前編です。

これまでの

ohshige.hatenablog.com ohshige.hatenablog.com

第4章: 形態素解析

Python 3.7.0 でやっていきます。 問題の解釈違い、間違い等ありましたら、教えていただけると幸いです。

github.com

指定の「吾輩は猫である」のテキストをダウンロードしてneko.txtとして保存し、mecabをインストールした状態で、

cat neko.txt | mecab > neko.txt.mecab

として、neko.txt.mecabを準備した状態です。

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

問題

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

解答&出力

def get_neko_morphemes():
    morphemes_list = []
    with open("neko.txt.mecab") as f:
        morphemes = []
        for i, line in enumerate(f):
            split_line = line.rstrip("\r\n").split("\t")
            if len(split_line) == 1:
                if len(morphemes) != 0:
                    morphemes_list.append(morphemes)
                    morphemes = []
                continue
            morpheme_map = split_line[1].split(",")
            morpheme = {
                "surface": split_line[0],
                "base": morpheme_map[6],
                "pos": morpheme_map[0],
                "pos1": morpheme_map[1],
            }
            morphemes.append(morpheme)
        else:
            if len(morphemes) != 0:
                morphemes_list.append(morphemes)

    return morphemes_list


if __name__ == "__main__":
    from pprint import pprint
    result = get_neko_morphemes()
    pprint(result[:10])
[[{'base': '一', 'pos': '名詞', 'pos1': '数', 'surface': '一'}],
 [{'base': '\u3000', 'pos': '記号', 'pos1': '空白', 'surface': '\u3000'},
  {'base': '吾輩', 'pos': '名詞', 'pos1': '代名詞', 'surface': '吾輩'},
  {'base': 'は', 'pos': '助詞', 'pos1': '係助詞', 'surface': 'は'},
  {'base': '猫', 'pos': '名詞', 'pos1': '一般', 'surface': '猫'},
  {'base': 'だ', 'pos': '助動詞', 'pos1': '*', 'surface': 'で'},
  {'base': 'ある', 'pos': '助動詞', 'pos1': '*', 'surface': 'ある'},
  {'base': '。', 'pos': '記号', 'pos1': '句点', 'surface': '。'}],

〜

  {'base': '感じ', 'pos': '名詞', 'pos1': '一般', 'surface': '感じ'},
  {'base': 'が', 'pos': '助詞', 'pos1': '格助詞', 'surface': 'が'},
  {'base': 'ある', 'pos': '動詞', 'pos1': '自立', 'surface': 'あっ'},
  {'base': 'た', 'pos': '助動詞', 'pos1': '*', 'surface': 'た'},
  {'base': 'ばかり', 'pos': '助詞', 'pos1': '副助詞', 'surface': 'ばかり'},
  {'base': 'だ', 'pos': '助動詞', 'pos1': '*', 'surface': 'で'},
  {'base': 'ある', 'pos': '助動詞', 'pos1': '*', 'surface': 'ある'},
  {'base': '。', 'pos': '記号', 'pos1': '句点', 'surface': '。'}]]

ひとこと

1行ずつ読み込み、タブとカンマ(,)で区切って、表層形はタブ区切りの左、品詞はタブ区切りの右のうちのカンマ区切りの1つ目など、mapに入れていくだけです。
結果を全て出力すると大変なので、最初の10件だけを出力しています。(以降、同様)

31. 動詞

問題

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

解答&出力

from div04.sec30 import get_neko_morphemes

morphemes_list = get_neko_morphemes()

result = []

for morphemes in morphemes_list:
    for morpheme in morphemes:
        if morpheme["pos"] == "動詞":
            result.append(morpheme["surface"])

print(result[:10])
['生れ', 'つか', 'し', '泣い', 'し', 'いる', '始め', '見', '聞く', '捕え']

ひとこと

問題30を利用して、ループで回して品詞(pos)が「動詞」であるものの表層系(surface)を結果に格納しているだけです。

32. 動詞の原形

問題

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

解答&出力

from div04.sec30 import get_neko_morphemes

morphemes_list = get_neko_morphemes()

result = []

for morphemes in morphemes_list:
    for morpheme in morphemes:
        if morpheme["pos"] == "動詞":
            result.append(morpheme["base"])

print(result[:10])
['生れる', 'つく', 'する', '泣く', 'する', 'いる', '始める', '見る', '聞く', '捕える']

ひとこと

31と同じように、ループで回して品詞(pos)が「動詞」であるものの原形(base)を結果に格納しているだけです。

33. サ変名詞

問題

サ変接続の名詞をすべて抽出せよ.

解答&出力

from div04.sec30 import get_neko_morphemes

morphemes_list = get_neko_morphemes()

result = []

for morphemes in morphemes_list:
    for morpheme in morphemes:
        if morpheme["pos"] == "名詞" and morpheme["pos1"] == "サ変接続":
            result.append(morpheme["surface"])

print(result[:10])
['見当', '記憶', '話', '装飾', '突起', '運転', '記憶', '分別', '決心', '我慢']

ひとこと

ループで回して、品詞(pos)が「名詞」であり且つ品詞細分類1(pos1)が「サ変接続」であるものを結果に格納しています。
問題に指定はありませんが、表層系を格納しています。

34. 「AのB」

問題

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

解答&出力

from div04.sec30 import get_neko_morphemes

morphemes_list = get_neko_morphemes()

result = []

for morphemes in morphemes_list:
    for i in range(1, len(morphemes) - 1):
        if morphemes[i]["surface"] != "の":
            continue
        before = morphemes[i - 1]
        after = morphemes[i + 1]
        if before["pos"] != "名詞" or after["pos"] != "名詞":
            continue
        result.append(before["surface"] + "の" + after["surface"])

print(result[:10])
['彼の掌', '掌の上', '書生の顔', 'はずの顔', '顔の真中', '穴の中', '書生の掌', '掌の裏', '何の事', '肝心の母親']

ひとこと

「の」を探した上で、その前後が「名詞」であるものを抽出しています。
連続した3つの形態素を抽出することになるので、「の」探索の範囲(range(1, len(morphemes) - 1))は最初と最後を除いています。

おわりに

3章は難しかったですが、4章の少なくとも前半は割りと簡単でした。
mecabbrewなどで簡単にインストールできるので、形態素解析に入門するのも簡単なご時世です。

続き

まだ