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という全然関係ない関数があるせいでたまに混乱します。
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)
成功!
ここまでくればあとは、全部見てイギリスのところを出力するだけ
for dic in lst: if dic['title']=='イギリス': print(dic['text'])
補足: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!
文字列の逆順
問題
文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ
s='stressed' print(s[::-1])
出力
desserts
解説不要ですね。
感想
がんばってあと99日続けたいです