LoginSignup
17
14

More than 5 years have passed since last update.

character-level CNNでTwitter陰キャを見つける

Last updated at Posted at 2018-05-28

Twitterのタイムラインを眺めていると,同じ学科の人たちが陰キャ,陽キャで話題になっていたので,ツイートをもとに陰キャツイート,陽キャツイートに分類する分類器を作り,自分が陰キャかどうかを調べてみた.

はじめに

息抜きで陰キャ判定器を作ろうかとおもっていて,研究で画像を処理する関係上,CNNを自然言語処理で使えたらなぁ...と思いながら調べていたら,ちょうどいい手法,character-level CNNを見つけた.これで分類器を作ってみた.

character-level CNN

自然言語処理でDeepLearningを使う手法は,LSTMなどが挙げられるが,今回は画像処理のモデルであるCNNを使う.最大の特徴は,分かち書きが要らないところ.簡単に言うと,「文字にはUnicode符号位置というものが割り振られてるし,この数値を画素と考えると,文章は画像になるじゃん.画像ならCNNにかけれるよね!」といったところ.character-level CNNでクリスマスを生き抜くを参考にしました.

モデルに関する処理の方法は以下の通り,
1. ツイートを文字の配列に分解
2. 各文字をUnicode符号位置に変換
3. 固定長配列にする
4. Embedding(Kerasのレイヤー)でUnicode座標位置の数値をベクトルの配列にする
5. ベクトル列をCNNにかける
6. 全結合層(Dense)を通して分類結果を返す

実装

今回の実装は,チェック後GitHubに載せる予定.
(2018/05/28追記) https://github.com/ymmtr6/character-level-cnn-twitter

環境

  • python3.6
  • tensorflow 1.5
  • keras 2.1.4

方針

今回陰キャツイートを見つけるためにの流れは以下のようになる.
1. 教師データとして陰キャ,陽キャのツイートを集める.
2. tweetをUnicode符号位置の配列に変換する
3. 教師値を元に分類器(モデル)を作る
4. 自分のツイートを分類器にかけて,インキャかどうかを判定する

教師データ

まず,(独自の判断で)陰キャと陽キャの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の処理手順を具体的にする,
1. shape=(batch_size, max_length)のInputを受け取る
2. Embeddingで各文字をembded_size次元に変換
3. Reshapeで軸を増やす
4. 同一の入力に対して複数のカーネルサイズで畳み込みをかけて,プーリング層を通す
5. 結果を結合する
6. 結合したものを正規化(平坦にならす)して全結合層にかける
7. 最終的に1次元にする(0が陽キャ,1が陰キャ)

clcnn.py
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のバグが修正されたらしく,モデルの可視化が可能になった.

model1.png

データの読み込み

保存したツイートを,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アプリを作ってみた.
陰キャ推定β

17
14
1

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
17
14