PCAとUMAPの組み合わせ(次元削減)

高次元データを扱う場合、UMAPのみで次元削減するのではなくPCAの結果をさらにUMAPで次元削減することでよりよい結果になることがあります。

PCA実行

前回と同様にMNISTのデータセットを使って、PCAで次元削減してみます。

[Google Colaboratory]

1
2
3
4
5
6
pca = PCA(n_components=0.99, random_state=0)
X_pc = pca.fit_transform(digits.data)
df_pca = pd.DataFrame(X_pc, columns=["PC{}".format(i + 1) for i in range(len(X_pc[0]))])
print("主成分の数: ", pca.n_components_)
print("保たれている情報: ", np.sum(pca.explained_variance_ratio_))
display(df_pca.head())

[実行結果(一部略)]

n_componentsに0.99を指定している(1行目)ので、累積寄与率が99%になるPC41までが結果として表示されました。

もともとは64次元でしたので23次元が削減されたことになります。

UMAPとPCA+UMAPの比較

PCAの結果に対してさらにUMAPを実行します。(2行目)

また比較のためにUMAPのみを実行した場合の結果も合わせて表示します。(1行目)

n_neighborsには5, 10, 15を指定します。

[Google Colaboratory]

1
2
create_2d_umap(digits.data, digits.target, digits.target_names, [5,10,15])
create_2d_umap(df_pca, digits.target, digits.target_names, [5,10,15])

[実行結果]

上図がUMAPのみの結果、下図がPCA+UMAPの結果となります。

明確にどちらの分類がよいか判断が難しいところですが、分類結果が変わっていることは確認できます。

PCAとUMAPを組み合わせた手法があるということも覚えておくと検証の幅が広がるかと思います。

Python × AI - UMAP(最適n_neighborsを探索)

n_neighborsは、UMAPの重要なパラメータです。

n_neighborsを大きくするとマクロな構造を反映し、小さくするとミクロな構造を結果に反映することができます。

デフォルト値は15で、2~100の間の値を選択することが推奨されています。

最適n_neighborsを探索(2次元)

最適なn_neighborsを探索する関数を定義します。

n_neighborsを2, 15, 30, 50, 100と設定してUMAPを実行し、結果を2次元に可視化します。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def create_2d_umap(target_X, y, y_labels, n_neighbors_list= [2, 15, 30, 50, 100]):
fig, axes = plt.subplots(nrows=1, ncols=len(n_neighbors_list),figsize=(5*len(n_neighbors_list), 4))
for i, (ax, n_neighbors) in enumerate(zip(axes.flatten(), n_neighbors_list)):
start_time = time.time()
mapper = umap.UMAP(n_components=2, random_state=0, n_neighbors=n_neighbors)
Y = mapper.fit_transform(target_X)
for each_label in y_labels:
c_plot_bool = y == each_label
ax.scatter(Y[c_plot_bool, 0], Y[c_plot_bool, 1], label="{}".format(each_label))
end_time = time.time()
ax.legend(loc="upper right")
ax.set_title("n_neighbors: {}".format(n_neighbors))
print("n_neighbors {} is {:.2f} seconds.".format(n_neighbors, end_time - start_time))
plt.show()

create_2d_umap(digits.data, digits.target, digits.target_names)

[実行結果]

n_neighborsの値によって、結果がかなり変化することが分かります。

上記の2次元グラフからは15, 30あたりが良い結果になっているようです。

最適n_neighborsを探索(3次元)

今度は、3次元で最適なn_neighborsを探索する関数を定義します。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def create_3d_umap(target_X, y, y_labels, n_neighbors_list= [2, 15, 30, 50, 100]):
fig = plt.figure(figsize=(5*len(n_neighbors_list),4))
for i, n_neighbors in enumerate(n_neighbors_list):
ax = fig.add_subplot(1, len(n_neighbors_list), i+1, projection="3d")
start_time = time.time()
mapper = umap.UMAP(n_components=3, random_state=0, n_neighbors=n_neighbors)
Y = mapper.fit_transform(target_X)
for each_label in y_labels:
c_plot_bool = y == each_label
ax.scatter(Y[c_plot_bool, 0], Y[c_plot_bool, 1], label="{}".format(each_label))
end_time = time.time()
ax.legend(loc="upper right")
ax.set_title("n_neighbors_list: {}".format(n_neighbors))
print("n_neighbors_list {} is {:.2f} seconds.".format(n_neighbors, end_time - start_time))
plt.show()

create_3d_umap(digits.data, digits.target, digits.target_names)

projectionパラメータ“3d”を設定(4行目)し、3次元のグラフを表示します。

また、n_componentsパラメータを2から3に変更しています。(6行目)

[実行結果]

上記の3次元グラフからは15, 30あたりでの分類結果がよさそうです。


もう少しn_neighborsを変化させて、再度最適値を調べます。

n_neighborsに[10 , 15, 20, 25, 30]を設定して実行します。

[Google Colaboratory]

1
create_3d_umap(digits.data, digits.target, digits.target_names, [10 , 15, 20, 25, 30])

[実行結果]

n_neighborsが10のときに最もうまく分類されています。

UMAPにおいても、n_neighborsの最適値がいくつかというのはデータセットによって違いますので、このように関数化して設定値を変更させながら比較すると最適な設定値を確認しやすくなります。

Python × AI - UMAP(次元削減)

UMAPは、t-SNEと同様の精度がありながら処理速度も速く、4次元以上の圧縮に対応しているという次元削減のトレンドになりつつある手法です。

ただし、t-SNEと同じようにパラメータの調整は必要です。

非常に高次元で大量のデータについても現実的な時間で実行でき、非線形の高次元データを低次元して可視化する手法として主流になっています。

UMAPライブラリのインストール

まずはUMAPライブラリをインストールします。

[Google Colaboratory]

1
!pip3 install umap-learn

[実行結果]

UMAPを実行

UMAPを実行します。(8行目)

また、比較のためにt-SNEも合わせて実行します。(4行目)

前前回読み込んだMNISTデータを使用しています。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
import umap

start_time_tsne = time.time()
X_reduced = TSNE(n_components=2, random_state=0).fit_transform(digits.data)
interval_tsne = time.time() - start_time_tsne

start_time_umap = time.time()
embedding = umap.UMAP(n_components=2, random_state=0).fit_transform(digits.data)
interval_umap = time.time() - start_time_umap

print("tsne : {}s".format(np.round(interval_tsne,2)))
print("umap : {}s".format(np.round(interval_umap,2)))

[実行結果]

t-SNEよりも、UMAPの方が速く終了していることが確認できます。

可視化

t-SNEの結果とUMAPの結果を可視化します。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
plt.figure(figsize=(10,8))
plt.subplot(2, 1, 1)
for each_label in digits.target_names:
c_plot_bool = digits.target == each_label
plt.scatter(X_reduced[c_plot_bool, 0], X_reduced[c_plot_bool, 1], label="{}".format(each_label))
plt.legend(loc="upper right")
plt.xlabel("tsne-1")
plt.ylabel("tsne-2")

plt.subplot(2, 1, 2)
for each_label in digits.target_names:
c_plot_bool = digits.target == each_label
plt.scatter(embedding[c_plot_bool, 0], embedding[c_plot_bool, 1], label="{}".format(each_label))
plt.legend(loc="upper right")
plt.xlabel("umap-1")
plt.ylabel("umap-2")
plt.show()

[実行結果]

上図のt-SNEと比較して、下図のUMAPの方がそれぞれのグループがきれいに小さくまとまっていて、明確に分類されています。

つまり、より適切に情報を落とさず次元削減できているということになります。

UMAPは、どんなデータにも適用でき複雑なデータの関連性が視覚的に分かりやすくなるので、非常に有効な手法です。

次元削減アルゴリズムの選び方

次元削減をする際のアルゴリズムの選び方としては、まずPCAUMAPの2つを試してみてより分類結果が確認しやすい方を選択するのがよいでしょう。

その2つの分類結果がいまいちであれば、t-SNEなど他のアルゴリズムを検討してみましょう。


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×