MATHGRAM

主に数学とプログラミング、時々趣味について。

t-SNEの結果をplotlyで3D可視化する

前回のplotlyの記事で実践編は暇あったら書きます的なこと言ったのですが,今回はそれに当たる内容です.

内容量はかなり少なく薄いですが,plotlyの使用例程度に思ってくれると有難いです.

t-SNEとは

t-SNEとは,皆さまご存知の通り次元圧縮の手法ですね.高次元データを人間が認知できる次元まで綺麗に落とし込める手法なので使っている人は多いのではないでしょうか.

今回はplotlyの使い方を重視した記事なので,理論の話はしませんが需要があったらまとめますね.一応参考になる資料をここにまとめておきます.

論文:
https://lvdmaaten.github.io/publications/papers/JMLR_2008.pdf

参考サイト:
理論とか置いといてt-sneをアプリケーションとして使う人は読むべき
高次元のデータを可視化するt-SNEの効果的な使い方 - DeepAge

ざっと理論の概要を知りたい人はこちらで.
t-SNE を用いた次元圧縮方法のご紹介 | ALBERT Official Blog

実践してみる

上のt-SNE を用いた次元圧縮方法のご紹介 | ALBERT Official Blogでやってることをpythonに移植する流れでやりたいと思います.

データセットcoil-20を使用し,sklearnに実装されているtsneを用います.

まずは3D

まずは3次元まで落とし込んでみましょう.

import os
import numpy as np
import cv2
from sklearn.manifold import TSNE
from sklearn import preprocessing

import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()

# 画像の前処理.標準化やらL2正規化やら.
def preprocess_image(path, size):
    img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
    resized = cv2.resize(img, (size, size), cv2.INTER_LINEAR).astype("float")
    normalized = cv2.normalize(resized, None, 0.0, 1.0, cv2.NORM_MINMAX)
    timg = normalized.reshape(np.prod(normalized.shape))
    return timg/np.linalg.norm(timg) 

ROOT = "./coil-20-proc"
ls = os.listdir(ROOT)

# 名前からラベルを持って来ます.
obj_ls = [name.split("_")[0] for name in ls]

ALL_IMAGE_PATH = [ROOT+"/"+path for path in ls]

# 全画像に対して前処理する
preprocess_images_as_vecs = [preprocess_image(path, 32) for path in ALL_IMAGE_PATH]

# tsneを実行
tsne = TSNE(
    n_components=3, #ここが削減後の次元数です.
    init='random',
    random_state=101,
    method='barnes_hut',
    n_iter=1000,
    verbose=2
).fit_transform(preprocess_images_as_vecs)

たったこれだけで次元削減できてしまいます.sklearnに感謝です.

さて,現在tsneという変数に次元削減後のarrayが入っているのでコイツをplotlyを用いて可視化してみます.

# 3Dの散布図が作れるScatter3dを使います.
trace1 = go.Scatter3d(
    x=tsne[:,0], # それぞれの次元をx, y, zにセットするだけです.
    y=tsne[:,1],
    z=tsne[:,2],
    mode='markers',
    marker=dict(
        sizemode='diameter',
        color = preprocessing.LabelEncoder().fit_transform(obj_ls),
        colorscale = 'Portland',
        line=dict(color='rgb(255, 255, 255)'),
        opacity=0.9,
        size=2 # ごちゃごちゃしないように小さめに設定するのがオススメです.
    )
)

data=[trace1]
layout=dict(height=700, width=600, title='coil-20 tsne exmaple')
fig=dict(data=data, layout=layout)
offline.iplot(fig, filename='tsne_example')

こんな感じで出力されます.グリグリ動かしてみてください.とても綺麗に分離できていることがわかります.尚,円形にplotされていることに関する考察などは先のブログでされているので是非参考にしてみてください.

2Dもやってみる.

ほぼ上と同じようにtsneを実行し,Scatter2dを用いて可視化してみます.

# tsneには2dまで落とし込んだarrayが入っている想定です.

trace = go.Scatter(
    x=tsne[:,0],
    y=tsne[:,1],
    mode='markers',
    marker=dict(
        sizemode='diameter',
        color = preprocessing.LabelEncoder().fit_transform(obj_ls),
        colorscale = 'Portland',
        line=dict(color='rgb(255, 255, 255)'),
        opacity=0.9,
        size=4
    )
)

data=[trace]
layout=dict(height=800, width=800, title='coil-20 tsne exmaple 2D')
fig=dict(data=data, layout=layout)
offline.iplot(fig, filename='tsne2D_example')

f:id:ket-30:20170705035109p:plain:w500:h500

この世の生データに比べたらかなり綺麗に分かれていますが,若干バラついてる部分も見受けられますね.やはり3Dと2Dでは元のデータに対する説明量が違いますので,この程度の差は出てしまいます.

内容としては以上です.

あとがき

plotlyは最近かなり使っているのですが,せっかく使っているのに実践編として記事にできてない状況になっています・・・.これからは,今回くらいの内容の薄さでもいいやぁって開き直って記事の更新頻度を上げていきたいと思います.

次はディリクレ分布を可視化してみようと思います.よろしければそちらも是非.

以上です.