t-SNEを使った文書ベクトルの可視化

 

t-SNEを使った文書ベクトルの可視化をしてみました。可視化にはSeabornの散布図を使います。Seabornはmatplotlibをベースにしたグラフ描画ライブラリで、matplotlibよりも美しく扱いやすいライブラリになっています。

可視化する文書ベクトルはこちらの記事でツイートとWikipediaのデータから求めたSCDVです。

 

関連リンク

SCDVを使ったテキスト分類をしてみる - どん底から這い上がるまでの記録

seaborn: statistical data visualization — seaborn 0.9.0 documentation

seaborn.lmplot — seaborn 0.9.0 documentation

sklearn.manifold.TSNE — scikit-learn 0.19.2 documentation

プログラムについて

前回main.pydataset.py, scdv.pyの3つのプログラムがありましたが、今回はdataset.pyscdv.pyはそのままに、main.pyの中身を少し変えて文書ベクトルを散布図を用いて可視化してみます。

具体的にはmain.pyのライブラリをインポートするところ、main()のなかを少し変更します。parse_args()に変更はありません。

以下、main.pyについて書いていきます。

ライブラリのインポート

今回使うライブラリです。

import argparse
import dataset
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from sklearn.manifold import TSNE
from scdv import SparseCompositeDocumentVectors, build_word2vec

冒頭部分に変更はありません。

def main(args):
    sentences, train_x, test_x, train_t, test_t = dataset.load_corpus()
    # Word2Vecを作る
    model = build_word2vec(
        sentences,
        args.embedding_dim,
        args.min_count,
        args.window_size,
        args.sg
    )
    vec = SparseCompositeDocumentVectors(
        model,
        args.num_clusters,
        args.embedding_dim,
        args.pname1,
        args.pname2
    )

データの用意

今回は前回と異なりすべてのデータを使ってSCDVを作ります。そのため、以下のコードで訓練データとテストデータに分けたものをひとつのリストにしつつ、データの正解ラベルも同時にひとつのリストにしています。

    corpus = {'data': [], 'label': []}
    corpus['data'] = train_x + test_x
    corpus['label'] = [["Twitter", "Wikipedia"][label] for label in train_t] \
                    + [["Twitter", "Wikipedia"][label] for label in test_t]

SCDVを求める

すべてのデータを使って確率重み付き単語ベクトルを求め、SCDVを作ります。

    # 確率重み付き単語ベクトルを求める
    vec.get_probability_word_vectors(corpus['data'])
    gwbowv = vec.make_gwbowv(corpus['data'])
    # gwbowv.shape
    # (1484, 200)

TwitterWikipediaはそれぞれ742件のデータがあるので、ひとつにすると1484件になり、文書ベクトルは200次元となっています。

TSNEを使った次元削減

200次元ある文書ベクトルを可視化するために次元削減の手法のひとつ、t-SNEを使って2次元にします。sckit-learnで実装されているものを使います。

    tsne = TSNE(
        n_components=2, random_state=0, verbose=2
    )
    # SCDVの次元削減をする
    tsne.fit(gwbowv)

散布図を作る

pandasのDataFrameにx軸とy軸となるものを作り、そこに2次元のベクトルを入れます。

    df = pd.DataFrame(tsne.embedding_[:, 0], columns=['x'])
    df['y'] = pd.DataFrame(tsne.embedding_[:, 1])
    df['class'] = corpus['label']
    graph = sns.lmplot(
        data=df, x='x', y='y', hue='class', fit_reg=False, size=8
    )

DataFrameができたらSeabornのlmplot()で散布図を作ります。

散布図の描画

最後にplt.show()で結果を見てみましょう。

    # 散布図の表示
    plt.show()

結果

結果はこうなりました。

f:id:pytry3g:20180906160411p:plain

すごいきれいにTwitterWikipediaのデータが分かれてまとまっていますね。

ここまできれいに分かれるとは思いませんでした。

ソースコード

main.py

import argparse
import dataset
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from sklearn.manifold import TSNE
from scdv import SparseCompositeDocumentVectors, build_word2vec

def parse_args():
    parser = argparse.ArgumentParser(
        description="Word2VecとSCDVのパラメータの設定"
    )
    parser.add_argument(
        '--embedding_dim', type=int, default=100
    )

    parser.add_argument(
        '--min_count', type=int, default=5
    )

    parser.add_argument(
        '--window_size', type=int, default=5
    )

    parser.add_argument(
        '--sg', type=int, default=1
    )

    parser.add_argument(
        '--num_clusters', type=int, default=2
    )

    parser.add_argument(
        '--pname1', type=str, default="gmm_cluster.pkl"
    )

    parser.add_argument(
        '--pname2', type=str, default="gmm_prob_cluster.pkl"
    )

    return parser.parse_args()

def main(args):
    sentences, train_x, test_x, train_t, test_t = dataset.load_corpus()
    # Word2Vecを作る
    model = build_word2vec(
        sentences,
        args.embedding_dim,
        args.min_count,
        args.window_size,
        args.sg
    )
    vec = SparseCompositeDocumentVectors(
        model,
        args.num_clusters,
        args.embedding_dim,
        args.pname1,
        args.pname2
    )
    corpus = {'data': [], 'label': []}
    corpus['data'] = train_x + test_x
    corpus['label'] = [["Twitter", "Wikipedia"][label] for label in train_t] \
                    + [["Twitter", "Wikipedia"][label] for label in test_t]
    # 確率重み付き単語ベクトルを求める
    vec.get_probability_word_vectors(corpus['data'])
    gwbowv = vec.make_gwbowv(corpus['data'])
    # gwbowv.shape
    # (1484, 200)
    tsne = TSNE(
        n_components=2, random_state=0, verbose=2
    )
    # SCDVの次元削減をする
    tsne.fit(gwbowv)
    df = pd.DataFrame(tsne.embedding_[:, 0], columns=['x'])
    df['y'] = pd.DataFrame(tsne.embedding_[:, 1])
    df['class'] = corpus['label']
    graph = sns.lmplot(
        data=df, x='x', y='y', hue='class', fit_reg=False, size=8
    )

    # 散布図の表示
    plt.show()
    # 散布図の保存
    graph.savefig('graph.png')

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