概要
青空文庫からとってきた5人の著者['太宰治', '森鴎外', '坂口安吾', '夏目漱石', '宮沢賢治']から、
誰が著者なのかを予測する分類器を作成した。
(他にもfastTextでTwitterのラベリングを行うみたいなのもやってみました)
インストール
cloneしてmakeして、(Cython入っていなかったら)事前に入れて、インストール。かんたん
git clone https://github.com/facebookresearch/fastText.git
cd fastText/
sudo make
cd ../
pip install Cython
pip install fastText
やったこと
- 各著者に対するファイル作成(1〜3繰返し)。コーパス数:30〜100万 1.テキストファイル読み込み 2.前処理(整形,わかちがき,label情報付与) 3.ファイル作成
- モデル作成
- 各著者に対するテストデータ生成と予測値追加(1〜4繰返し)。テストサンプル数:1000件 1.テキストファイル読み込み 2.前処理(整形,label情報付与) 3.データ生成 4.予測値生成しデータに追加
- 各著者の正解率を算出
データ
前にクローズドで行ったイベントで提供してもらったものなので、公開していいか確認してなかったので、公開は控えますが、下記の感じのテキストデータです。
(例は0001.txtの「太宰治」から一部抜粋)
思いは、ひとつ、窓前花。
十三日。 なし。
十四日。 なし。
十五日。 かくまで深き、
十六日。 なし。
十七日。 なし。
十八日。
ものかいて扇ひき裂くなごり哉
ふたみにわかれ
十九日。
十月十三日より、板橋区のとある病院にいる。来て、三日間、歯ぎしりして泣いてばかりいた。銅貨のふくしゅうだ。ここは、気ちがい病院なのだ。となりの部屋の若旦那は、ふすまをあけたら、浴衣がかかっていて、どうも工合いがわるかった、など言って、みんな私よりからだが丈夫で、大河内昇とか、星武太郎などの重すぎる名を有し、帝大、立大を卒業して、しかも帝王の如く尊厳の風貌をしている。惜しいことには、諸氏ひとしく自らの身の丈よりも五寸ほどずつ恐縮していた。母を殴った人たちである。
四日目、私は遊説に出た。鉄格子と、金網と、それから、重い扉、開閉のたびごとに、がちん、がちん、と鍵の音。寝ずの番の看守、うろ、うろ。この人間倉庫の中の、二十余名の患者すべてに、私のからだを投げ捨てて、話かけた。まるまると白く太った美男の、肩を力一杯ゆすってやって、なまけもの! と罵った。眼のさめて在る限り、枕頭の商法の教科書を百人一首を読むような、あんなふしをつけて大声で読みわめきつづけている一受験狂に、勉強やめよ、試験全廃だ、と教えてやったら、一瞬ぱっと愁眉をひらいた。うしろ姿のおせん様というあだ名の、セル着たる二十五歳の一青年、日がな一日、部屋の隅、壁にむかってしょんぼり横坐りに居崩れて坐って、だしぬけに私に頭を殴られても、僕はたった二十五歳だ、捨てろ、捨てろ、と低く呟きつづけるばかりで私の顔を見ようとさえせぬ故、こんどは私、めそめそするな、と叱って、力いっぱいうしろから抱いてやって激しくせきにむせかえったら、青年いささか得意げに、放せ、放せ、肺病がうつると軽蔑して、私は有難くて泣いてしまった。元気を出せ。みんな、青草原をほしがっていた。私は、部屋へかえって、「花をかえせ。」という帝王の呟きに似た調子の張った詩を書いて、廻診しに来た若い一医師にお見せして、しんみに話合った。午睡という題の、「人間は人間のとおりに生きて行くものだ。」という詩を書いてみせて、ふたりとも、顔を赤くして笑った。五六百万人のひとたちが、五六百万回、六七十年つづけて囁き合っている言葉、「気の持ち様。」というこのなぐさめを信じよう。僕は、きょうから涙、一滴、見せないつもりだ。ここに七夜あそんだならば、少しは人が変ります。豚箱などは、のどかであった。越中富山の万金丹でも、熊の胃でも、三光丸でも五光丸でも、ぐっと奥歯に噛みしめて苦いが男、微笑、うたを唄えよ。私の私のスウィートピイちゃん。
コードサンプル
import re
import pandas as pd
import json
import MeCab
import tqdm
from time import sleep
import fasttext as ft
from requests_oauthlib import OAuth1Session
# その他
N = 1000
FILEPATH = '../../data/'
FILE_NAME_TRAIN = ['0001.txt','0002.txt','0003.txt','0004.txt','0005.txt']
FILE_NAME_TEST = ['0001_test.txt','0002_test.txt','0003_test.txt','0004_test.txt','0005_test.txt']
TRAIN_FILENAME = 'train.txt'
MODEL_NAME = 'model_ft'
AUTHOR = ['太宰治', '森鴎外', '坂口安吾', '夏目漱石', '宮沢賢治'] # 著者
CLASS_LABEL = ['__label__1','__label__2','__label__3','__label__4','__label__5'] # ラベル
# ファイル読み込み
def read_file(file_path, file_name):
fr = open(file_path + file_name, 'r')
texts = fr.read()
fr.close()
ptn = re.compile(r'\n|。')
sp = ptn.split(texts)
contents = [i for i in sp if (i != '') & (len(i) >= 5)]
return contents
# わかち書き
def get_wakati(content):
tagger = MeCab.Tagger('')
tagger.parse('')
surf = []
node = tagger.parseToNode(content)
while node:
surf.append(node.surface)
node = node.next
return surf
# わかち書きを行ったlist作成
def get_surfaces(contents):
results = []
for row in contents:
content = format_text(row)
surf = get_wakati(content)
results.append(surf)
return results
# fasttext学習実行用ファイル作成
def write_txt(contents, class_label):
try:
if(len(contents) > 0):
filename = class_label + '.txt'
labelText = class_label + ','
fw = open(FILEPATH + filename, 'w') # 各ラベル
fa = open(FILEPATH + TRAIN_FILENAME, 'a') # 全ラベル
for row in contents:
spaceTokens = ' '.join(row)
result = labelText + spaceTokens + '\n'
fw.write(result)
fa.write(result)
fw.close()
fa.close()
print(class_label+':'+str(len(contents))+'行出力')
except Exception as e:
print('書き込み失敗')
print(e)
# 整形
def format_text(text):
text=re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
text=re.sub(r'@[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
text=re.sub(r'&[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
text=re.sub(';', "", text)
text=re.sub('RT', "", text)
text=re.sub('\n', " ", text)
text=re.sub('\u3000','',text)
return text
def create_file():
for i in range(5):
# get
novel = read_file(FILEPATH,FILE_NAME_TRAIN[i])
# foramt
for j in range(len(novel)):
novel[j] = format_text(novel[j])
# wakati
surfaces = get_surfaces(novel)
# create file
write_txt(surfaces, CLASS_LABEL[i])
sleep(3)
print('ファイル作成完了')
# 予測値を取得
def get_predict(word, model_path):
classifier = ft.load_model(model_path + '.bin')
estimate = classifier.predict_proba([word], k=1)[0]
return estimate
# テスト用データ作成
def create_testdata(class_label, file_name_test):
test_contents = []
labelText = class_label + ','
# get
novel = read_file(FILEPATH, file_name_test)
for text in novel:
text = format_text(text)
test_contents.append([labelText, text])
return test_contents
# スコアを取得
def get_predict_score(class_label, file_name_test):
test_contents = create_testdata(class_label, file_name_test)
for i in range(N):
word = " ".join(get_wakati(test_contents[i][1]))
estimate = get_predict(word, FILEPATH + MODEL_NAME)
test_contents[i].extend([estimate[0][0],estimate[0][1]])
df = pd.DataFrame(test_contents[:N],columns=['fac','text','pred','pro'])
df['score_flg'] = 0
df.loc[df['fac'] == df['pred'] , 'ans_flg'] = 1
cnt = len(df)
ans = df['ans_flg'].sum(axis=0)
return cnt, ans
def main():
create_file()
classifier = ft.supervised(FILEPATH + TRAIN_FILENAME, FILEPATH + MODEL_NAME)
for i in range(5):
cnt, ans = get_predict_score(CLASS_LABEL[i], FILE_NAME_TEST[i])
print('{}({}) 正解率:{}% テストサンプル数:{}個'.format(AUTHOR[i],CLASS_LABEL[i], ans/cnt * 100, cnt))
#FILEPATH = '../temp/'
if __name__ == "__main__":
main()
結果
- 正解率は著者によってかなりばらつきがある(全体の正解率は57%程度)
- そもそも分類が難しい著者がいるらしい(だれだか忘れてしまった)
- ハッカソンの1位は70%くらいだった(気がする)ので、著者の取捨選択を適切に行えば、同じくらいの正解率は出せるかも
- 今回はコーパス数が大きいが、モデル生成自体は数十秒で完了した
- fastText単体で使用するのではなく、(ベクトル表現生成がとにかく早いので)fasttextベースの文書ベクトル作成+DL or MLなどで速度を担保しつつ精度を上げることができそう(な気がする)
- subwordのアルゴリズムをしっかり把握する必要ありそう
太宰治(__label__1) 正解率:83.39999999999999% テストサンプル数:1000個
森鴎外(__label__2) 正解率:20.5% テストサンプル数:1000個
坂口安吾(__label__3) 正解率:87.6% テストサンプル数:1000個
夏目漱石(__label__4) 正解率:85.6% テストサンプル数:1000個
宮沢賢治(__label__5) 正解率:11.700000000000001% テストサンプル数:1000個