4日目 30:形態素解析結果の読み込み 自然言語処理100本ノック

最近睡眠時間が長いです。

第4章

夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.
ようやく、自然言語処理っぽくなってきました。 幸いにもMecabpythonで扱う用意はすでにできていますが、とにかくインストールに苦労した覚えがあります。 (やり方はすでに忘れたので、インストールの解説は割愛します)

さくっと書きましょう

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という全然関係ない関数があるせいでたまに混乱します。