LoginSignup
2
6

More than 5 years have passed since last update.

fastTextで小説の著者分類を行う

Posted at

概要

青空文庫からとってきた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個
2
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
6