渋谷駅前で働くデータサイエンティストのブログ

元祖「六本木で働くデータサイエンティスト」です / 道玄坂→銀座→東京→六本木→渋谷駅前

生TensorFlow七転八倒記(10):テキストデータをTF-Hubでfeature vectorに直してからt-SNEにかけてみる

今回もただの備忘録ですが、どちらかというと番外編です。TensorFlow部分はあくまでもTF-Hubでテキストデータをfeature vectorに直すところまでのみで、そこから先は今まであまり試してこなかったt-SNE (t-distributed stochastic neighbor embedding)を使っています。


参考にした記事は以下の3点です。



ということで、だらだら書いていきます。いつもながらですが、今回の記事もいい加減な理解に基づいて適当なまとめを書いているようなものですので、間違っている点や理解不足に見える点がありましたらどしどしご指摘くださいm(_ _)m


t-SNEとは何か


はっきり言って冒頭に挙げたALBERTさんの解説記事とWikipedia記事と原論文を読んでいただければそれで十分だと思いますので、ここでは超絶大ざっぱなまとめだけ書いておきます。


これはvan der MaatenとHinton先生の2008年の共著論文で提案されたもので、アイデアとしては「高次元空間での2点間の『近さ』を多次元正規分布に従って確率分布で表したものを、次元圧縮して1次元のt分布に従う確率分布に置き換えることを考える。この両者を互いに近づけるために二者の確率分布同士のKL divergenceを計算し、これを最小化することでよりベストに近い次元圧縮を行う」ということのようです。


Wikipedia記事にも同様の解説がありますが、1次元のt分布というかなり裾の長い分布(実はコーシー分布と等価らしい:実際にRで計算してみたら完全に一致した)に変換することで、次元圧縮によって「低次元空間において2つのサンプルを互いにより遠くに引き離して分離しやすくする」ことを目指している模様です。最後のKL divergenceの最適化は普通に勾配法で走るので、実際の計算自体はそこまで煩雑ではないように思われます。



実際のテキストデータで試してみる


今回用いたのは、上記リンク先のQiita記事でも推奨されていたpython-bhtsneです。これはBHTSNE (Barnes-Hut implementation of t-SNE)というオリジナルのt-SNEを高速化した手法で、van der Maaten本人の手によるC++実装をCythonでPythonパッケージに直したもののようです。

pipで入れられる上に、Qiita記事でも書かれているように計算が軽くて早いです。今回はザッと様子を見るのが目的なので、Qiita記事のようなカスタマイズは特に行わずにGitHubに載っているチュートリアル同様の簡単な実践だけを以下に並べておきます。


まず、以前の記事で取り上げたYouTubeのコメントデータセットで試してみます。

import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
import pandas as pd
import sklearn.base
from sklearn.utils import shuffle
import csv
from bhtsne import tsne
import matplotlib.pyplot as plt

if __name__ == "__main__":
    df = pd.read_csv("youtube_dataset.csv",  delimiter='\t')
    df["label"] = df.CLASS.factorize()[0]
    df = shuffle(df)
    df_list = df["CONTENT"].values.tolist()

    embed = hub.Module("https://tfhub.dev/google/nnlm-en-dim128/1")
    m = embed(df_list)

    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        sess.run(tf.tables_initializer())
        text_vector = sess.run(m)
    
    text_vector = np.array(text_vector, dtype = 'float64')
    Y = tsne(text_vector)

    plt.figure(figsize=(12,12))
    
    plt.scatter(Y[:, 0], Y[:, 1], c=df["label"])

    plt.xlabel("component 0")
    plt.ylabel("component 1")
    plt.title("YouTube SPAM Comments t-SNE visualization")

f:id:TJO:20190206000719p:plain

t-SNEで射影した感じでは、とりあえず何かしら非線形分類器(つまりDNN)を使えば綺麗に分類できそうな気が確かにします。左上の方に何やら真ん丸に固まった不思議なクラスタが見えるのが気になりますが、何なんでしょうか。


では、ACC 0.72と苦戦したSentiment140ではどうなるでしょうか。

import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
import pandas as pd
import sklearn.base
from sklearn.utils import shuffle
import csv
from bhtsne import tsne
import matplotlib.pyplot as plt

if __name__ == "__main__":
    df = pd.read_csv("train_tw_sentiment.csv", sep=',', quoting=csv.QUOTE_ALL)
    df = df.loc[:, ["label", "text"]]
    df.loc[df["label"] > 0, "label"] = 1
    df = shuffle(df)
    df = df[:10000]
    df_list = df["text"].values.tolist()

    embed = hub.Module("https://tfhub.dev/google/nnlm-en-dim128/1")
    m = embed(df_list)

    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        sess.run(tf.tables_initializer())
        text_vector = sess.run(m)
    
    text_vector = np.array(text_vector, dtype = 'float64')
    Y = tsne(text_vector)

    plt.figure(figsize=(12,12))
    
    plt.scatter(Y[:, 0], Y[:, 1], c=df["label"])

    plt.xlabel("component 0")
    plt.ylabel("component 1")
    plt.title("Sentiment140 t-SNE visualization 10000 samples")

f:id:TJO:20190206000122p:plain

もう見るからにまるっきりダメですねorz やっぱりSentiment140は相当に難しいデータのようだと思いました。。。


追記


よーくよく__init__.pyを見てみたら、パラメータperplexityが30.0で固定されていました。

一方、以前も拝読したことのあるこちらの記事を見ると、perplexityをチューニングするとかなり結果が変わることが分かります。見た感じでは場合によっては5ぐらいの方が妥当なようにも受け取れます。

ということで、perplexityをいじれるように上記のQiita記事のように多少カスタマイズした方が良いのかなという気がしてきました。