4日目 30:形態素解析結果の読み込み 自然言語処理100本ノック
最近睡眠時間が長いです。
第4章
夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.
ようやく、自然言語処理っぽくなってきました。 幸いにもMecabをpythonで扱う用意はすでにできていますが、とにかくインストールに苦労した覚えがあります。 (やり方はすでに忘れたので、インストールの解説は割愛します)
さくっと書きましょう
neko.txt → neko.txt.mecabへの変換
import MeCab m = MeCab.Tagger ("-Ochasen") with open ('neko.txt','r',encoding='utf-8_sig') as f: output=m.parse(f.read()) with open ('neko.txt.mecab','w',encoding='utf-8_sig') as f: f.write(output)
neko.txt.mecab
一 イチ 一 名詞-数 記号-空白 吾輩 ワガハイ 吾輩 名詞-代名詞-一般 は ハ は 助詞-係助詞 猫 ネコ 猫 名詞-一般 で デ だ 助動詞 特殊・ダ 連用形 ある アル ある 助動詞 五段・ラ行アル 基本形 。 。 。 記号-句点 名前 ナマエ 名前 名詞-一般 は ハ は 助詞-係助詞 まだ マダ まだ 副詞-助詞類接続 無い ナイ 無い 形容詞-自立 形容詞・アウオ段 基本形 。 。 。 記号-句点 . .
形態素解析結果の読み込み
形態素解析結果(neko.txt.mecab)を読み込むプログラムを実装せよ.ただし,各形態素は表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をキーとするマッピング型に格納し,1文を形態素(マッピング型)のリストとして表現せよ.
普通に日本語がつらいです。
def GetSentences(): sentence=[] with open ('neko.txt.mecab','r',encoding='utf-8_sig') as f: for line in f.readlines(): #line : "吾輩 ワガハイ 吾輩 名詞-代名詞-一般" info=line.split('\t') # lineは\tで各要素に区切られる dic={ 'surface':info[0], 'base':info[2], 'pos':info[3].split('-')[0], #名詞-代名詞-一般のようになっているのでハイフンでsplitして0と1を取り出す 'pos1':info[3].split('-')[1], } sentence.append(dic) if dic['surface']=='。': yield sentence sentence=[]#リストを空にする output=GetSentences() print(list(output))
とりあえず「。」が出てくるたびに1文の終わりと判断してyieldするコードにしました。 outputには各文のgeneretorが返ってきます。
出力
File "30.py", line 13, in GetSentences 'pos1':info[3].split('-')[1], IndexError: list index out of range
あれ、エラー・・
原因を探ります
try: dic={ 'surface':info[0], 'base':info[2], 'pos':info[3].split('-')[0], 'pos1':info[3].split('-')[1], } except: print(info) return
出力
['で', 'デ', 'だ', '助動詞', '特殊・ダ', '連用形\n']
どうやら「助動詞」のような品詞細分類1がないものもあるようです。 それらをはじくコードを書きます。
dic={ 'surface':info[0], 'base':info[2], 'pos':tmp[0], } if len(tmp)>1: dic['pos1']=tmp[1] else: dic['pos1']=None
わざわざNoneを入れたのは、この後keyが見つかりませんエラーを吐かないようにとの配慮です。吉と出るか凶と出るか・・
[[{'surface': '一', 'base': '一', 'pos': '名詞', 'pos1': '数'}, {'surface': '\u3000', 'base': '\u3000', 'pos': '記号', 'pos1': '空白'}, {'surface': '吾輩', 'base': '吾輩', 'pos': '名詞', 'pos1': '代名詞'},......
章番号と空白が文に混ざっちゃうのか。
if dic['surface'] in ['。',' ']: if len(sentence)>1: yield sentence sentence=[]
空白でも区切るようにします。「。(空白)次の文」みたいな場合は空白だけの文ができてしまうので、そういう場合ものぞきます。
出力
[[{'surface': '一', 'base': '一', 'pos': '名詞', 'pos1': '数'}, {'surface': '\u3000', 'base': '\u3000', 'pos': '記号', 'pos1': '空白'}], [{'surface': '吾輩', 'base': '吾輩', 'pos': '名詞', 'pos1': '代名詞'},...
いい感じです。
コード全体はこんな感じになりました。
def GetSentences(): sentence=[] with open ('neko.txt.mecab','r',encoding='utf-8_sig') as f: for line in f.readlines(): if line.strip()=='EOS': return #line : "吾輩 ワガハイ 吾輩 名詞-代名詞-一般" info=line.split('\t') # lineは\tで各要素に区切られる tmp=info[3].split('-') dic={ 'surface':info[0], 'base':info[2], 'pos':tmp[0], #名詞-代名詞-一般のようになっているのでハイフンでsplitして0と1を取り出す } if len(tmp)>1: dic['pos1']=tmp[1] else: dic['pos1']=None sentence.append(dic) if dic['surface'] in ['。',' ']: if len(sentence)>1: yield sentence sentence=[]#リストを空にする output=GetSentences() print(list(output))
感想
ようやく本格的なコーディングに入ってきたという印象です。 余談ですが、マッピング型はpythonでいう辞書(dict)型なわけですが、mapという全然関係ない関数があるせいでたまに混乱します。