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

3日目 20:JSONデータの読み込み 自然言語処理100本ノック

3日目です。 昨日徹夜して、朝から今まで寝てたので時間の感覚が狂ってます

第3章

Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある. 1行に1記事の情報がJSON形式で格納される 各行には記事名が”title”キーに,記事本文が”text”キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される ファイル全体はgzipで圧縮される 以下の処理を行うプログラムを作成せよ

JSONデータの読み込み

Wikipedia記事のJSONファイルを読み込み,「イギリス」に関する記事本文を表示せよ.問題21-29では,ここで抽出した記事本文に対して実行せよ.

いつも通り読み込みます。

import json
with open ('jawiki-country.json','r',encoding='utf-8_sig') as f:
    dic=json.load(f)

おっとエラー発生

json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 38123)

jsonデータのほうを見ると、一つのファイルに複数の辞書構造が詰め込まれているようです。 (文法的に大丈夫なのかこれ)

{"title": "エジプト", "text": "{{otheruses|主...."}{"title": "オーストリア", "text": "{{基礎情報 国\n|略....."}...

仕方がないので、1行ずつ順番に読み込む方法を試します。

import json

lst=[]
with open ('jawiki-country.json','r',encoding='utf-8_sig') as f:
    for line in f.readlines():
        dic=json.loads(line)
        lst.append(dic)

print(lst)

成功!

f:id:moscwa:20200416152215p:plain
実行結果
分かってたけどすごい量・・

ここまでくればあとは、全部見てイギリスのところを出力するだけ

for dic in lst:
    if dic['title']=='イギリス':
        print(dic['text'])

f:id:moscwa:20200416152744p:plain
実行結果2
ひとまず成功・・なのかな?

補足:json.loadとjson.loadsについて

jsonにはjson.loadとjson.loadsの2つの読み込みメソッドがあります。 違いなのですが、どうやら - json.loadはjsonファイルオブジェクト→辞書型への変換 - json.loadsはjson文字列→辞書型への変換  ということみたいです。

感想

そろそろこなれてきました

2日目 10:行数のカウント 自然言語処理100本ノック

2日目にして第二章です!! 読者には不親切の極みで申し訳ないのですが、これには以下の理由があります

  • 自然言語処理100本ノック」は初めが簡単で最後に行くほど難しくなる
  • つまり、00番から順番に解いていくと、後半が毎日重実装になり、受験生の自分にとってstreakがつらくなる

ということで10の位をカウンタアップして、重実装を分散させようという狙いでこのような形になりました。

行数のカウント

popular-names.txtは,アメリカで生まれた赤ちゃんの「名前」「性別」「人数」「年」をタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,popular-names.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

問題

行数をカウントせよ.確認にはwcコマンドを用いよ

回答

ソースコード

with open ('popular-names.txt') as f:
    print(len(f.readlines()))

出力

2780

windowsゆえUNIXコマンドが使えません・・! エディタで目視チェックしたら、きちんと2780行だったので許してください。

さて、ファイルを読み込んであれこれするやつです。 f.readlines()は1行ごとのリストが返ってくるので、その長さを調べてあげれば完成です。

まだ余裕かな

1日目 00:文字列の逆順 自然言語処理100本ノック

自然言語処理100本ノックの2020版が出たということで流行りにのってやってみることにしました。 めざせ100日streak!

nlp100.github.io

文字列の逆順

問題

文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ

s='stressed'
print(s[::-1])

出力

desserts

解説不要ですね。

感想

がんばってあと99日続けたいです