fastTextでTwitterのラベリングを行う

# fastTextでTwitterのラベリングを行う


Facebbookの自然言語処理ライブラリである[fastText](https://github.com/facebookresearch/fastText)を利用しようしたことがなかったので、軽く触ってみた。

## fastTextの概要

基本的にはword2vecライクなものだが、2つの大きな特徴がある。

- 学習が非常に高速である（ベクトル表現生成とテキスト分類）
- word2vecとは結果が大きく異なる場合がる（アルゴリズムにsubwordが組み込まれているため）

wordwordのメリデメは[ここ](http://catindog.hatenablog.com/entry/2017/03/31/221644)にわかりやすい例が載っている。


## インストール

cloneしてmakeして、（Cython入っていなかったら）事前に入れて、インストール。かんたん


```bash
git clone https://github.com/facebookresearch/fastText.git
cd fastText/
sudo make
cd ../
pip install Cython
pip install fastText
```

## 検証したいこと

例えば、下記の　['マンガ', 'アニメ', 'ゲーム', '映画', '音楽']　のように、tweet内容に複数キーワードが含まれる可能性の高いものを用意して学習を行うと、比較的あいまいな表現のtweetを検証するとどういった結果が返されるのか。  

例）原作が○○ってマンガの実写映画化がすごく面白いんだけど！主演の○○の演技がすごい上手だった。  
➔　メインは映画に関するtweetなので、確率的には　映画 > マンガ の結果が出てほしい  

## やったこと

1. twitterAPIを使用して特定のキーワード（このときのキーワードをラベルとして取り扱う）を取得
	- 今回キーワードとしたのは　['マンガ', 'アニメ', 'ゲーム', '映画', '音楽']　の５つ
2. わかち書き
3. 学習用テキストファイル作成
4. モデル作成
5. 適当なツイートでラベリングを行う

## コードSample

※実装は[この記事](http://tech.wonderpla.net/entry/2017/10/10/110000)を参考にさせて頂きました。

twitterAPIを使うためのアクセストークンなどは、別途取得する必要あり。  
単位時間あたりのAPIアクセス回数の上限があり、一度に取得できる上限が100らしいので、とりあえず繰り返し回数を30として、各キーワード3000ずつくらい取得した




```python
import sys
sys.path.append('conf.pyファイルのパス')
import re
import json
import MeCab
import tqdm
import subprocess
from time import sleep
import fasttext as ft
from requests_oauthlib import OAuth1Session

#　接続情報（アクセストークンなど）
CK = conf.CK
CS = conf.CS
AT = conf.AT
AS = conf.AS
# その他
API_URL = 'https://api.twitter.com/1.1/search/tweets.json?lang=ja&tweet_mode=extended' # URL
KEYWORD = ['マンガ', 'アニメ', 'ゲーム', '映画', '音楽'] # 取得キーワード
CLASS_LABEL = ['__label__1','__label__2','__label__3','__label__4','__label__5'] # ラベル
N = 30 # 取得繰り返し回数
FILEPATH = 'パス' 
TRAIN_FILENAME = 'ファイル名'
MODEL_NAME = 'モデル名'

# 学習用ファイル初期化
def initialize_file():
    fw = open(FILEPATH + TRAIN_FILENAME, 'w')
    fw.write('')
    fw.close()

# tweet取得
def get_tweet(keyword):
    max_id = 9999999999999999999
    results = []
    for i in range(N):        
        params = {'q':keyword, 'count':100 , 'max_id':max_id}
        twitter = OAuth1Session(CK, CS, AT, AS)
        req = twitter.get(API_URL, params = params)
        if req.status_code == 200:
            tweets = json.loads(req.text)
            for tweet in tweets['statuses']:
                results.append(tweet['full_text'])
                max_id = tweet['id'] - 1
        else:
            print('Error: %d'% req.status_code)
        print('\r取得：',100/len(range(N))*(i+1),'%完了：', end='')
        sleep(5)
    return results

# わかち書き
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

# tweet取得
def get_tweet(keyword):
    max_id = 9999999999999999999
    results = []
    for i in range(N):        
        params = {'q':keyword, 'count':100 , 'max_id':max_id}
        twitter = OAuth1Session(CK, CS, AT, AS)
        req = twitter.get(API_URL, params = params)
        if req.status_code == 200:
            tweets = json.loads(req.text)
            for tweet in tweets['statuses']:
                results.append(tweet['full_text'])
                max_id = tweet['id'] - 1
        else:
            print('Error: %d'% req.status_code)
        print('\r取得：',100/len(range(N))*(i+1),'%完了：', end='')
        sleep(5)
    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)
    return text

def create_file():
    for i in range(5):
        tweets = get_tweet(KEYWORD[i])
        surfaces = get_surfaces(tweets)
        write_txt(surfaces, CLASS_LABEL[i])
    print('ファイル作成完了')

def labeling_tweet(classifier, word):
    classifier = ft.load_model(FILEPATH + MODEL_NAME + '.bin')
    estimate = classifier.predict_proba([word], k=3)[0]
    for i, j in enumerate(estimate):
        if j[0] == "__label__1,":
            print('第',i+1,'候補：','マンガ', j[1])
        elif j[0] == "__label__2,":
            print('第',i+1,'候補：','アニメ', j[1])
        elif j[0] == "__label__3,":
            print('第',i+1,'候補：','ゲーム', j[1])
        elif j[0] == "__label__4,":
            print('第',i+1,'候補：','映画', j[1])
        elif j[0] == "__label__5,":
            print('第',i+1,'候補：','音楽', j[1])

def main():
    initialize_file()
    create_file()
    classifier = ft.supervised(FILEPATH + TRAIN_FILENAME, FILEPATH + MODEL_NAME)
    word = " ".join(get_wakati("検証tweet"))
    labeling_tweet(classifier, word)

if __name__ == "__main__":
    main()
```


## 結果

検証tweetとして

「ファーストガンダムのハリウッド実写化ではなく『「機動戦士ガンダム」シリーズの実写版』の制作というのがポイントですね。」

というのを投げて見た結果、上位3つは以下の通りになった。  
相対的な評価する分には問題なさそうけどそもそもの確率が低い。  
（マルチラベリングする分には、精度はある程度担保出そうだけど。  

```
第 1 候補： 映画 0.480469
第 2 候補： マンガ 0.345703
第 3 候補： アニメ 0.0917969
```


## 複数サンプル数✕複数の検証キーワードで、結果の違いを見る

学習に使うサンプル数[300,1000,3000,10000]を3つのキーワードで見てみました。  
メインの内容としては  
1つ目は、映画に関するtweet  
2つ目は、マンガに関するtweet  
3つ目は、アニメに関するtweet  
として考えると,

- サンプル数が変化すると、確率がかなり変わる
- 取得キーワードが入っているか否かで確率にだいぶ差がありそう
- 学習速度はどれも一瞬（1秒かかってないと思う）だった


ということだけど、サンプル数がそもそも足りないかもしれないのと前処理もうちょっとしっかりやる必要ありそう？  
精度の検証は、ちゃんとラベル付きの教師データを使わないとなんとも言えないので、  
今後は、別のデータを使って検証したい  



```
---------------------------
キーワード： ファーストガンダムのハリウッド実写化ではなく『「機動戦士ガンダム」シリーズの実写版』の制作というのがポイントですね。
学習サンプル数： 300
第 1 候補： 映画 0.203125
第 2 候補： マンガ 0.201172
第 3 候補： 音楽 0.199219
---------------------------
学習サンプル数： 1000
第 1 候補： 映画 0.359375
第 2 候補： マンガ 0.273438
第 3 候補： アニメ 0.203125
---------------------------
学習サンプル数： 3000
第 1 候補： 映画 0.304688
第 2 候補： ゲーム 0.283203
第 3 候補： アニメ 0.228516
---------------------------
学習サンプル数： 10000
第 1 候補： マンガ 0.361328
第 2 候補： 映画 0.238281
第 3 候補： アニメ 0.21875
---------------------------
キーワード： 週末の無料マンガは『BANANA FISH』！TVアニメ放送開始を記念して原作コミック第1～3巻が期間限定無料試し読み！
学習サンプル数： 300
第 1 候補： マンガ 0.203125
第 2 候補： ゲーム 0.199219
第 3 候補： アニメ 0.199219
---------------------------
学習サンプル数： 1000
第 1 候補： マンガ 0.652344
第 2 候補： アニメ 0.322266
第 3 候補： 映画 0.00976564
---------------------------
学習サンプル数： 3000
第 1 候補： アニメ 0.515625
第 2 候補： マンガ 0.482422
第 3 候補： 映画 1.95313e-08
---------------------------
学習サンプル数： 10000
第 1 候補： マンガ 0.875
第 2 候補： アニメ 0.123047
第 3 候補： 映画 1.95313e-08
---------------------------
キーワード： X JAPAN feat. HYDEが「進撃の巨人」3期OPテーマ担当 - 音楽ナタリー 「進撃の巨人」は諫山創の人気マンガを原作としたアニメシリーズで、7月22日（日）深夜24:35よりNHK総合にて「Season3」の放送がスタートする。
学習サンプル数： 300
第 1 候補： マンガ 0.205078
第 2 候補： 音楽 0.199219
第 3 候補： 映画 0.197266
---------------------------
学習サンプル数： 1000
第 1 候補： 音楽 0.501953
第 2 候補： アニメ 0.269531
第 3 候補： マンガ 0.173828
---------------------------
学習サンプル数： 3000
第 1 候補： アニメ 0.613281
第 2 候補： 音楽 0.275391
第 3 候補： マンガ 0.101563
---------------------------
学習サンプル数： 10000
第 1 候補： アニメ 0.746094
第 2 候補： 音楽 0.173828
第 3 候補： マンガ 0.0722656
---------------------------

```