【言語処理100本ノック解いてみた】 第4章:形態素解析 No.30~No.34

プログラミング

はじめに

今回は第4章の前半を解いていきたいと思います.
第4章では,文章を意味を持つ最小単位の形態素に分割する方法やその形態素を分析することを実施していきます.
形態素解析はword2vecなどの自然言語処理技術等に使われる大事な一要素ですので,しっかりと扱えるようにしたいですね.

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

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

問題文:
形態素解析結果(neko.txt.mecab)を読み込むプログラムを実装せよ.ただし,各形態素は表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をキーとするマッピング型に格納し,1文を形態素(マッピング型)のリストとして表現せよ.第4章の残りの問題では,ここで作ったプログラムを活用せよ.
まずは,小説に対して形態素解析した結果をファイルに保存したいと思います.
下記がファイルの読み込みと形態素解析の結果を保存するためのShellコマンドです.
今回も実装はGoogle Colaboratory を利用しています.
コマンド
!wget https://nlp100.github.io/data/neko.txt
!apt install mecab libmecab-dev mecab-ipadic-utf8
!mecab neko.txt -o neko.txt.mecab
#作成したファイルの確認
!head -10 neko.txt.mecab
#出力
一	名詞,数,*,*,*,*,一,イチ,イチ
    記号,一般,*,*,*,*,*
EOS
	  記号,一般,*,*,*,*,*
EOS
 	記号,空白,*,*,*,*, , , 
吾輩	名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
猫	名詞,一般,*,*,*,*,猫,ネコ,ネコ
で	助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ

これで,形態素解析済みのファイルの準備はできたので,一文を形態素のリストとして表現する部分を作っていきたいと思います.

filename = 'neko.txt.mecab'
with open(filename, mode='r', encoding='utf-8') as f:
    sentence_block_list = f.read().split("EOS\n")

sentence_blocks = []
for sentence_block in sentence_block_list:
    morpheme_list = []
    block = sentence_block.split("\n")
    for m in block:
        if m == "":
            continue
        surface, attr = m.split("\t")
        if surface == "":
            continue
        attr = attr.split(",")
        morpheme ={
            "surface": surface,
            "base": attr[6],
            "pos": attr[0],
            "pos1": attr[1],
        }
        morpheme_list.append(morpheme)
    if len(morpheme_list) > 0:
        sentence_blocks.append(morpheme_list)
print(sentence_blocks[:5])
#出力(一部編集)
[[{'surface': '一', 'base': '一', 'pos': '名詞', 'pos1': '数'}], 
[{'surface': '\u3000', 'base': '\u3000', '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': '句点'}], ・・・
]]
解説

形態素解析の実施までできているので,ここから解析結果の読みこみます.
結果は出力で確認したように形態素毎に下記のように出力されます.
表層形<tab>品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音
また,一文終わる毎に,”EOS”と出力されています.
上記の出力特性を利用して一文毎に指定されている表層形,原形,品詞,品詞細分類1のリストを作成していきます.

コードの解説

形態素解析結果ファイルを読み込み,一文毎のリストとして保持します.
上記ではまだ,一文毎の態素解析結果が生の文字列でリスト化されてしまっています.
そこで次に,一文毎の形態素のマッピング型のリストを作成していきます.
形態素解析の一文分の結果を改行毎(形態素毎の結果)でリスト化し,それらの中から必要な情報だけをマッピング型で保持します.
最後に,マッピング型のリストを一文単位でまとめて本問題は完了です.
この後の問題ではこの”sentence_blocks”を利用しています.

31. 動詞

問題文: 動詞の表層形をすべて抽出せよ.
コマンド
verb_list = []
for sentence_block in sentence_blocks:
    verb = [block["surface"] for block in sentence_block if block["pos"]=="動詞"]
    verb_list += verb
verb_surface_list = list(set(verb_list))
print(len(verb_surface_list))
print(verb_surface_list[:5])
#出力
3893
['禁じ', 'ぶんなぐる', 'たたく', '出会わ', '枉げ']
解説

前問で作成したsentence_blocksを利用して動詞の表層形を全て出力します.
まず,一文毎に出てきた動詞をリスト内包表記を利用して抽出します.
動詞の抽出結果をverb_listに結合し,最後にset()を用いて重複を許していた動詞のリストをユニークなものにします.

32. 動詞の基本形

問題文:動詞の基本形をすべて抽出せよ.
コマンド
verb_list = []
for sentence_block in sentence_blocks:
    verb = [block["base"] for block in sentence_block if block["pos"]=="動詞"]
    verb_list += verb
verb_base_list = list(set(verb_list))
print(len(verb_base_list))
print(verb_base_list[:5])

出力

#出力
2300
['ぶんなぐる', '攫む', '飛ぶ', '集める', '乗り出す']
解説

こちらの問題は前問では表層形の抽出だった部分を基本形に置き換えるだけですね.

33. 「AのB」

問題文:2つの名詞が「の」で連結されている名詞句を抽出せよ.
コマンド
noun_phrase = []
for sentence_block in sentence_blocks:
    for i in range(len(sentence_block)-2):
        if sentence_block[i]["pos"] == "名詞" and sentence_block[i+1]["surface"] =="の" and sentence_block[i+2]["pos"]=="名詞":
            phrase = sentence_block[i]["surface"] + sentence_block[i+1]["surface"] + sentence_block[i+2]["surface"]
            noun_phrase.append(phrase)
            
noun_phrase = list(set(noun_phrase))
print(len(noun_phrase))
print(noun_phrase[:5])

出力


4924
['車屋の黒', '吾輩の横腹', 'そこの風習', '朝飯の催促', 'こっちの趣向']
解説

形態素のブロックを三つずつ持ってきて,名詞+の+名詞になっているかをif文でチェックすることでリストを作成することができます.

34. 名詞の連接

問題文:名詞の連接(連続して出現する名詞)を最長一致で抽出せよ.
コマンド
noun_phrase = []
for sentence_block in sentence_blocks:
    phrase = ""
    count = 0
    for block in sentence_block:
        if block["pos"] == "名詞":
            phrase += block["surface"]    
            count +=1
        else:
            if count >= 2:
                noun_phrase.append(phrase)
                phrase = ""
                count = 0
            else:
                phrase = ""
                count = 0
                     
noun_phrase = list(set(noun_phrase))
print(len(noun_phrase))
print(noun_phrase[:5])

出力

4454
['暑中休暇後', '百騎町', '明治十一年九月廿八日', 'たまえ月十円', '書生同様取次']
解説

下記を満たすif文を作成することで問題を解くことができます.
・名詞が来たら文字列を連結する.
・名詞以外の場合,すでに名詞が二回以上連続で来ていたら名詞の連接としてリストに保存.
・それ以外の場合はcount,phrase共にリセットする.

デフォルトのmecabだと半角記号が名詞+サ変接続となってしまうので厳密にやる場合は対処する必要があるかもしれません.

 

最後に

今回は第4章の前半の問題を解いてきました.
形態素解析がどのように保存されているかを把握できていれば,後はリストをうまく使ったり,if文を活用することで問題を解くことができました.
例えば,word2vecなどでは使う品詞を制限したりするので,形態素解析の方法を知っておくことでNLPの中でも基本的な分析手法に取り組みやすくなります.
次回は,4章編の後半を解いていきます.

次の記事

前の記事

 

コメント

タイトルとURLをコピーしました