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(次元削減)

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など他のアルゴリズムを検討してみましょう。

Python × AI - t-SNE(次元削減)

t-SNE(ティースニー)は、2次元または3次元への圧縮に特化しているアルゴリズムです。

(次元数4以上の場合の結果は保証されていません)

MNISTデータの読み込み

0~9の手書き数字の画像データセットMNIST(エムニスト)を読み込みます。

画像データはベクトル化すると高次元になるため、次元削減アルゴリズムがとても有効です。

[Google Colaboratory]

1
2
3
4
from sklearn.datasets import load_digits
digits = load_digits()
print(digits.data.shape)
print(digits.data)

[実行結果]

1797個の画像データがあり、各データは 8 × 8 = 64 個の数値配列、つまり64次元のデータセットになります。

この64次元のデータセットをt-SNEで2次元に削減し可視化します。

データの先頭から100文字を画像として表示するコードは下記の通りです。

[Google Colaboratory]

1
2
3
4
5
import matplotlib.pyplot as plt 
fig, axes = plt.subplots(10, 10, figsize=(8, 8),subplot_kw={"xticks":[], "yticks":[]})
for i, ax in enumerate(axes.flat):
ax.imshow(digits.images[i], cmap="binary", interpolation="nearest")
ax.text(0, 0, str(digits.target[i]))

[実行結果]

手書き数字の画像データが確認できました。

PCAで次元削減

まずはPCAで次元削減を実行し(2行目)、可視化を行います。

[Google Colaboratory]

1
2
3
4
5
6
7
from sklearn.decomposition import PCA
X_reduced = PCA(n_components=2).fit_transform(digits.data)
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()
plt.show()

[実行結果]

数字ごとに分類されて欲しかったのですが、うまく分類できていないようです。

これはPCAが非線形データに対応できていないためです。

t-SNEで次元削減

次にt-SNEで次元削減を実行し(2行目)、可視化を行います。

[Google Colaboratory]

1
2
3
4
5
6
7
from sklearn.manifold import TSNE
X_reduced = TSNE(n_components=2, random_state=0).fit_transform(digits.data)
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()
plt.show()

[実行結果]

t-SNEは、非線形データに対応できるためうまく分類されています。

1,3,8,9がやや混じっているように見えますが、これは人の目から見ても形が似ている数字があるためしかたがないように思えます。

入力データが複雑になればなるほどPCAはうまく次元削減できなくなることが多いのですが、t-SNEは複雑なデータでもかなり高精度な次元削減を行うことができます。

ただし、次のようなデメリットもあるので注意が必要です。

  • 処理に時間がかかる。
  • 4次元以上には不向き。
  • パラメータ調整が必要になる。

Python × AI - Isomap(非線形データの次元削減)

前回まで実行していたPCAは、データが多次元正規分布に従うことを仮定しているため、非線形データに対してはうまく動作しないという問題があります。

この問題に対処するため、非線形データの変換を行う手法がいくつかあります。

今回はその中でIsomap(Isometric mapping)を試してみます。

Isomapは多様体上の距離を測定し、多次元尺度構成法で表現する手法です。

多次元尺度構成法は近いもの同士は近くに配置し、遠いものは遠くに配置する手法で、Isomapは近いもの同士をより考慮した手法になります。

ムーンデータの取得

まずは非線形データとして、ムーンデータを取得します。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import preprocessing, decomposition, manifold
from sklearn import datasets
from sklearn.decomposition import PCA
X,Y = datasets.make_moons(n_samples=200, noise=0.05, random_state=0)
sc=preprocessing.StandardScaler()
sc.fit(X)
X_norm=sc.transform(X)
plt.figure(figsize=(10,3))
plt.scatter(X[:,0],X[:,1], c=Y)
plt.xlabel("x")
plt.ylabel("y")

[実行結果]

Isomap実行

取得したムーンデータに対してIsomapを実行します。(4~8行目)

比較のためPCAも実行しています。(1~2行目)

[Google Colaboratory]

1
2
3
4
5
6
7
8
pca = PCA(n_components=2)
X_reduced = pca.fit_transform(X_norm)

isomap_5 = manifold.Isomap(n_neighbors=5, n_components=2)
X_isomap_5 = isomap_5.fit_transform(X_norm)

isomap_10 = manifold.Isomap(n_neighbors=10, n_components=2)
X_isomap_10 = isomap_10.fit_transform(X_norm)

Isomapは全てのサンプル間の距離を計算しますが、細かく計算するのはサンプルごとに最も近いN個のサンプル間の距離だけです。

可視化した結果が良くない場合は、Nを変更して再び試行することになります。

このNに対応するのがn_neighborsパラメータになります。

4行目ではn_neighborsを5に、7行目ではn_neighborsを10に設定しています。(上記のソースコード)

Isomap実行結果の可視化

順番に、PCAの結果、Isomapでn_neighborsが5の結果、Isomapでn_neighborsが10の結果を可視化します。

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
plt.figure(figsize=(10,6))
plt.subplot(3, 1, 1)
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=Y)
plt.xlabel("pca-1")
plt.ylabel("pca-2")

plt.subplot(3, 1, 2)
plt.scatter(X_isomap_5[:,0],X_isomap_5[:,1], c=Y)
plt.xlabel("isomap_n5-1")
plt.ylabel("isomap_n5-2")

plt.subplot(3, 1, 3)
plt.scatter(X_isomap_10[:,0],X_isomap_10[:,1], c=Y)
plt.xlabel("isomap_n10-1")
plt.ylabel("isomap_n10-2")
plt.show

[実行結果]

一番下のIsomapでn_neighborsが10の結果が、一番きれいに分かれていて新たな軸が作成されていることが確認できました。

Python × AI - 主成分分析(PCA)

主成分分析(PCA:Principal Component Analysis)は、多次元データのもつ情報をできるだけ損なわずに低次元とする方法です。

次元削減で最も簡単な方法であり、広い分野で使われています。

アイリスデータの読み込み

まずアイリスデータを読み込みます。

結果の確認用にtarget_nameに正解の花の名称を追加しています。(5~8行目)

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
import pandas as pd
from sklearn.datasets import load_iris
iris = load_iris()
df=pd.DataFrame(iris.data, columns=iris.feature_names)
df["target"] = iris.target
df.loc[df["target"]==0, "target_name"] = "setosa"
df.loc[df["target"]==1, "target_name"] = "versicolor"
df.loc[df["target"]==2, "target_name"] = "virginica"
df.head()

[実行結果]

アイリスデータの散布図行列

読み込んだアイリスデータを散布図行列で可視化します。

hueパラメータに名称”target_name”を設定することで色をつけています。(2行目)

[Google Colaboratory]

1
2
import seaborn as sns
sns.pairplot(df, vars=df.columns[:4], hue="target_name")

[実行結果]

品種ごとに色分けされていて分類されていることは把握できますが、次元が多いため解釈するのが大変です。

アイリスの3次元立体図

花の品種ごとにpetal_width以外の情報を取得します。(5~6行目)

その後、3次元での可視化を行います。(7~12行目)

[Google Colaboratory]

1
2
3
4
5
6
7
8
9
10
11
12
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1, 1, 1, projection="3d")
for c in df["target_name"].unique():
ax.scatter(df.iloc[:, 0][df["target_name"]==c], df.iloc[:, 1][df["target_name"]==c] , df.iloc[:, 2][df["target_name"]==c], label=c)
ax.set_title("iris 3D")
ax.set_xlabel("sepal_length")
ax.set_ylabel("sepal_width")
ax.set_zlabel("petal_length")
ax.legend(loc=2, title="legend", shadow=True)
plt.show()

[実行結果]

3品種に分かれているように見えますが、petal_widthの情報は全く無視してしまっています。

この情報が特徴量のある重要なデータだったという可能性もあります。

PCAを使用すれば、この4次元データを2次元データで表現することが可能になります。

PCA実行

PCAを実行し(3~4行目)、結果である主成分得点を表示します(5~8行目)。

[Google Colaboratory]

1
2
3
4
5
6
7
8
from sklearn.decomposition import PCA
import numpy as np
pca = PCA(random_state=0)
X_pc = pca.fit_transform(df.iloc[:, 0:4])
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())

[実行結果]

横軸が主成分(PC:Principal Component)で、縦軸が各サンプルを表します。

主成分(PC)とは、データを要約(縮小)したあとの新しい合成変数で、第1主成分(PC1)に最も多くの情報が集まっていて第2主成分(PC2)以降にだんだんと情報が小さくなります。

PC1とPC2を可視化

多くの情報が集まっているPC1PC2を可視化します。

[Google Colaboratory]

1
sns.scatterplot(x="PC1", y="PC2", data=df_pca, hue=df["target_name"])

[実行結果]

4次元あったデータを2次元で可視化することができました。

うまく3種類に分類されています。

PCAの用途はいくつかあるのですが、多次元データの特徴を低次元に次元削減し可視化する手段としてとても有効です。


Your browser is out-of-date!

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

×