Twitterのタイムラインを眺めていると,同じ学科の人たちが陰キャ,陽キャで話題になっていたので,ツイートをもとに陰キャツイート,陽キャツイートに分類する分類器を作り,自分が陰キャかどうかを調べてみた.
はじめに
息抜きで陰キャ判定器を作ろうかとおもっていて,研究で画像を処理する関係上,CNNを自然言語処理で使えたらなぁ...と思いながら調べていたら,ちょうどいい手法,character-level CNNを見つけた.これで分類器を作ってみた.
character-level CNN
自然言語処理でDeepLearningを使う手法は,LSTMなどが挙げられるが,今回は画像処理のモデルであるCNNを使う.最大の特徴は,分かち書きが要らないところ.簡単に言うと,「文字にはUnicode符号位置というものが割り振られてるし,この数値を画素と考えると,文章は画像になるじゃん.画像ならCNNにかけれるよね!」といったところ.character-level CNNでクリスマスを生き抜くを参考にしました.
モデルに関する処理の方法は以下の通り,
- ツイートを文字の配列に分解
- 各文字をUnicode符号位置に変換
- 固定長配列にする
- Embedding(Kerasのレイヤー)でUnicode座標位置の数値をベクトルの配列にする
- ベクトル列をCNNにかける
- 全結合層(Dense)を通して分類結果を返す
実装
今回の実装は,チェック後GitHubに載せる予定.
(2018/05/28追記) https://github.com/ymmtr6/character-level-cnn-twitter
##環境
- python3.6
- tensorflow 1.5
- keras 2.1.4
##方針
今回陰キャツイートを見つけるためにの流れは以下のようになる.
- 教師データとして陰キャ,陽キャのツイートを集める.
- tweetをUnicode符号位置の配列に変換する
- 教師値を元に分類器(モデル)を作る
- 自分のツイートを分類器にかけて,インキャかどうかを判定する
##教師データ
まず,(独自の判断で)陰キャと陽キャのTwitterアカウントを集めてくる.これらのアカウントがつぶやくツイートが教師データとする.ツイッターの情報を取得するのにTwitterAPIを利用する.TwitterAPIを利用するのに,OAuth認証が必要だが,ここはrequests-oauthlibを利用した.ここでは紹介を省略する.
詳しくは, PythonでTwitterから情報収集(TwitterAPI編)を参照.
データを[アカウント名\tツイートのテキストデータ]といった形(tsv形式)で保存しておく.
##tweetをUnicode符号位置に変換
ツイートを集めたら,ツイートのテキストをUnicode符号位置に変換する.
# tweets: ツイートテキストの配列
def trans(tweets = [], max_length=100, min_length=7):
unicode_tweets = []
for tweet in tweets:
# Unicode符号位置0xffff以下(utf-8の3バイト文字のみを取得する)
tweet = [ord(x) for x in tweet.strip() if ord(x) <= 0xffff]
tweet = tweet[:max_length]
tweet_len = len(tweet)
# 短すぎる文章は却下する
if tweet_len < min_length:
continue
# 0パディングで固定長にする
if tweet_len < max_length:
tweet += ([0] * (max_length - comment_len))
unicode_tweets.append(tweet)
return unicode_tweets
3バイト文字なのに0xffff(2バイト)と比較するのはどうなの?って疑問に思った方もいるかもしれない.僕も思った.UnicodeとUTF-8の仕様を混同していたり,誤解していた.UTF-8の3バイトは,Unicode符号位置では2バイトで表されている(正確にはU+FFFF).動作は問題なかった.4バイト文字は面倒なので切ったが,陽キャは絵文字を使う印象があるので,今後変更するかもしれない.
##モデル作り
先ほど説明したcharacter-level CNNの処理手順を具体的にする,
- shape=(batch_size, max_length)のInputを受け取る
- Embeddingで各文字をembded_size次元に変換
- Reshapeで軸を増やす
- 同一の入力に対して複数のカーネルサイズで畳み込みをかけて,プーリング層を通す
- 結果を結合する
- 結合したものを正規化(平坦にならす)して全結合層にかける
- 最終的に1次元にする(0が陽キャ,1が陰キャ)
from tensorflow.python.keras.layers import *
from tensorflow.python.keras.models import Model
import tensorflow.python.keras
class ClcnnBuldier(object):
# build
def build(self, embed_size=128, max_length=100, filter_sizes=(2, 3, 4, 5), filter_num=64):
self.model = self.__build_layer(embed_size, max_length, filter_sizes, filter_num)
return self.model
# private bulid
def __build_layer(self, embed_size=128, max_length=100, filter_sizes=(2, 3, 4, 5), filter_num=64):
# Input Layer
input_ts = Input(shape=(max_length, ))
# Embedding 各文字をベクトル変換
emb = Embedding(0xffff, embed_size)(input_ts)
emb_ex = Reshape((max_length, embed_size, 1))(emb)
# 各カーネルサイズで畳み込みをかける.
convs = []
# Conv2D
for filter_size in filter_sizes:
conv = Conv2D(filter_num, (filter_size, embed_size), activation="relu")(emb_ex)
pool = MaxPooling2D((max_length - filter_size + 1 , 1))(conv)
convs.append(pool)
# ConcatenateでConv2Dを結合
convs_merged = Concatenate()(convs)
# Reshape
reshape = Reshape((filter_num * len(filter_sizes),))(convs_merged)
# Dense
fc1 = Dense(64, activation="relu")(reshape)
bn1 = BatchNormalization()(fc1)
do1 = Dropout(0.5)(bn1)
# 2class 分類なので,sigmoid関数を用いる.
fc2 = Dense(1, activation='sigmoid')(do1)
# Model generate
model = Model(
inputs=[input_ts],
outputs=[fc2]
)
return model
複数のカーネルサイズで畳み込みを行うことで,N-gramモデルを模倣している感じらしい.
(2018/05/29追記) tensorflowのバージョンを1.8に上げる事でKerasのバグが修正されたらしく,モデルの可視化が可能になった.
##データの読み込み
保存したツイートを,targets(陰キャリスト)に含まれているかどうかで教師値をつける.
def load_data(filepath, targets, max_length=100, min_length=10):
nerd = []
normie = []
with open(filepath) as f:
for l in f:
id, tweet = l.split("\t", 1)
# UNICODE 変換
tweet = [ord(x) for x in tweet.strip() if ord(x) <= 0xffff]
tweet = comment[:max_length]
tweet_len = len(comment)
if tweet_len < min_length:
continue
if tweet_len < max_length:
tweet += ([0] * (max_length - tweet_len))
if id not in targets:
normie.append((0, tweet))
else:
nerd.append((1, tweet))
list = nerd + normie
random.shuffle(list)
return list
学習
学習させる.本来は学習に必要なハイパーパラメータをセットできるようにするべきだけど,面倒なので省略.
モデルは今後利用することを考えて保存しておく.
def train(inputs, targets, batch_size=100, epochs=100, max_length=100, model_filepath="model"):
builder = ClcnnBuldier()
model = builder.build()
model.compile(loss='binary_crossentropy', optimizer='SGD', metrics=['accuracy'])
# 学習
model.fit(inputs, targets, epochs=epochs ,batch_size=batch_size, verbose=1, validation_split=0.1, shuffle=True)
model.save(model_filepath + ".h5")
model.save_weights(model_filepath + "_weight.h5")
if __name__ == '__main__':
nerd = dataprocesser.accounts.nerd
data = trainer.load_data("../dataprocesser/test.tsb", nerd, min_length=7)
input_values = []
target_values = []
for target_value, input_value in data:
input_values.append(input_value)
target_values.append(target_value)
input_values = np.array(input_values)
target_values = np.array(target_values)
trainer.train(input_values, target_values)
だいたい8割くらいのtest_accuracyになった.
##判別
さっそく自分のアカウントで試してみる.
import numpy as np
from tensorflow.python.keras.models import *
model = load_model("clcnn.h5")
if __name__ == "__main__":
screenName = "hoge"
tweetData = TweetData()
raw_tweets = tweetData.get_tweets(sceenName, 100)
raw_tweets = [tweet for (id, text) in raw_tweets]
if len(raw_tweet) == 0:
exit("no tweet")
tweets = tweetData.trans(raw_tweets)
vector = np.array(tweets)
ret = predict(vector)
for comment, r in zip(raw_tweets, ret):
print("[{0:.2f}]{1}".format(r[0], comment))
print("Average Score: {0:.2f}".format(np.mean(ret)))
結果はAverage86%だった.僕陰キャでした.(いや,きっと教師データの選択にミスが...)
まとめ
DeepLearningを使ってTwitter陰キャである僕を見つけることができた.悲しいなぁ...
陰キャかどうかの判定は,自身の価値観に影響を受けるので精度がいいのかは分からない(という言い訳で責任回避)
一応,ツイッター連携を使って自分の陰キャ率を測るWebアプリを作ってみた.
陰キャ推定β