ハンズオンUMAP
はじめに
UMAPという新しい可視化の手法が開発された。
私的に、この手法の利点は大きく2つあると考えている。
- t-SNEよりも高速でありながらt-SNEと同じような可視化の結果が得られる。
- t-SNEと違って、学習済みの空間に、あとから新規のサンプルを布置できる。
特に、2番目について言及している。ブログ記事は日本語では見つからなかった。ので、2番目の強みの紹介がこのブログの目的である。
他にもパラメーターについてまとめている記事や
UMAPのgithubのREADMEの翻訳をしている記事が見つかったのでぜひ。
umap – 一様マニホールド近似と投影 – GitHubじゃ!Pythonじゃ!
なぜ、あとからサンプルを布置できると嬉しいのか?
機械学習のタスクに使えるからである。次元削減したら、その削減された空間(潜在空間)での座標を機械学習アルゴリズムの入力したいと考えるだろう。しかし、t-SNEでは潜在空間にあとからサンプルを布置することができなかった。そのためt-SNEは可視化に使うしかなかったのである。
ところが、UMAPはあとから新規のサンプルを布置できる。このためUMAPは強力な可視化手法でありながら、強力な前処理の手法ともなり得る。
ここからドキュメントの例に従って、(コメントを付け足しつつ)、機械学習にUMAPを使えることを確認する。具体的には、手書き数字の識別タスクを行う。
UMAPがどういうものなのか理論的な解説は行わない(理解してないし)
ドキュメントにそって
ドキュメントからコピペ Transforming New Data with UMAP — umap 0.3 documentation
インストール
umapと打たないように注意
pip install umap-learn
import numpy as np import pandas as pd from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split, cross_val_score from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC import matplotlib.pyplot as plt import seaborn as sns %matplotlib inline sns.set(context='notebook', style='white', rc={'figure.figsize':(14,10)}) digits = load_digits()
データの概要
#データの詳細を見てみる print(digits.DESCR)
データ説明から一部抜粋
Data Set Characteristics: :サンプル数: 5620(と書いてあるものの、説明と異なって実際は1797枚しかありません) :説明変数の数: 64 :何を説明変数としているか: 8x8 の画像のそれぞれのピクセルである。0~16の整数が格納されている。 :欠損値: なし
# 適当にいくつか見てみる for i in [2, 10, 42]: print("ラベル:",digits.target[i]) plt.figure(figsize=(1,1)) plt.imshow(digits.data[i].reshape(8,8)) plt.show()
ラベル: 2
ラベル: 0
ラベル: 1
MNISTの軽量版と言ったところか。UMAPで次元削減を行う前に訓練データとテストデータに分けておこう。
訓練とテストに分割
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, stratify=digits.target, random_state=42) print("訓練データの数",len(y_train),"\tテストデータの数",len(y_test))
訓練データの数 1347 テストデータの数 450
UMAPでは、1347の訓練データを用いて学習した潜在空間に、テストデータを新たに布置することができる。これが意味することは、t-SNEと異なり、判別器などの前処理に使えるということである。
訓練データで空間を学習
import umap
# sklearnと同じようなインターフェイス #n_neighborsを変えると結果が結構変わる #処理時間を測るために%timeをしている %time trans = umap.UMAP(n_neighbors=5,n_components=2, random_state=42).fit(X_train) plt.scatter(trans.embedding_[:, 0], trans.embedding_[:, 1], s= 5, c=y_train, cmap='Spectral') plt.title('Embedding of the training set by UMAP', fontsize=24) plt.show()
CPU times: user 7.11 s, sys: 225 ms, total: 7.33 s Wall time: 7.32 s
この潜在空間で学習
#SVMでの学習 svc = SVC().fit(trans.embedding_, y_train) #k-近傍法での学習 knn = KNeighborsClassifier().fit(trans.embedding_, y_train)
さて、二種類のClassifierで潜在空間に布置された訓練データが学習できた。未知のテストデータを判定するためには、テストデータもこの訓練データの潜在空間に落とし込む必要がある。
テストデータの潜在空間への布置
#処理時間を測るために%timeをしている #やはりsklearnと同様にtransformで、データを加工する %time test_embedding = trans.transform(X_test)
CPU times: user 4.08 s, sys: 102 ms, total: 4.18 s Wall time: 4.2 s
PCAやLPPみたいな線形の変換とは異なってやはり少し時間がかかる印象。テストデータが潜在空間のどこに布置されたのか実際に確かめてみよう。
plt.scatter(test_embedding[:, 0], test_embedding[:, 1], s= 5, c=y_test, cmap='Spectral') plt.title('Embedding of the test set by UMAP', fontsize=24) plt.show()
ちなみに、訓練データの可視化の結果は以下のようになっていた。
色の配置が訓練データと同じことが確認できる。こんなにきれいだとむしろ感動する。
テストデータへの汎化性能
では、どれほどテストデータに対して、うまく判別できるのか。正解率を見てみる。
print( "SVM:", svc.score(test_embedding, y_test), "\nk-近傍",knn.score(test_embedding, y_test))
SVM: 0.9822222222222222
k-近傍 0.9822222222222222
パラメーターチューニングもせずにこれほどの正解率がでるとはおそろしい(もっともグリッドサーチしてもほとんど変わらないと思うが)。
まとめ
- UMAPは可視化だけではなくて、機械学習につかえる!!!!使え!!!
- 潜在空間と元の特徴量空間の対応というか、解釈性がほしいと思った。
- (じつは何も前処理を行わない場合のkNNに負けてる。)
svc = SVC().fit(X_train, y_train) knn = KNeighborsClassifier().fit(X_train, y_train) print( "SVM:", svc.score(X_test, y_test), "\nk-近傍:",knn.score(X_test, y_test))
SVM: 0.62
k-近傍: 0.9844444444444445