主成分分析による次元削減と復元

Posted on 2020/12/29 in 機械学習 , Updated on: 2020/12/29

はじめに

手書き文字データである mnist データを使って、PCA (Principle Component Analysys) : 主成分分析を試す。主成分分析は、多変量データへの次元削減方法として使用されている。今回は、主成分分析を行い、主成分の分類への寄与率や、情報削減量を可視化で確認する。
※ PCA は、データが多次元正規分布に従うことを仮定しているので、その仮定から外れる非線形データでは、うまく機能しない。そのような場合は、LLE (LocallyLinearEmbedding) や t-SNE (t-distributedStochasticNeighborEmbedding) 等を使用する。

データ準備

sklearn を使って、mnist データをダウンロードする。データには、7万枚の「0」〜「9」の手書き数字の画像データが含まれており、それらに対応するラベルが付随している。各サンプルは、784(28x28)のピクセルデータになっており、データは784個の特徴量を持つデータセットとなる。今回はこのデータのうち3万枚を使って評価する。

In [50]:
from sklearn.datasets import fetch_openml

# mnist データをダウンロードする
# 0~9 の手書き文字で構成されており、データ数は7万
mnist = fetch_openml('mnist_784', version=1)
mnist.target = mnist.target.astype(int)

# 今回は 3万枚を使用する。
X = mnist['data'][:30000]
y = mnist['target'][:30000]

# いくつか可視化
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib inline

plt.figure(figsize=(10, 3))
for i in range(10):
    plt.subplot(1, 10, i+1)
    plt.imshow(X[y==i][0].reshape(28, 28),
               cmap=mpl.cm.binary)
    plt.axis('off')
    plt.title('Number:{}'.format(i), fontsize=10)

plt.tight_layout()
plt.show()

PCA (主成分分析)

sklearn.decompositionPCA を使って簡単に実装できる。主成分分析は、各次元(特徴量)の分散を最大限維持する新しい軸を見つける。この軸が主成分となる。この際、データは各次元に対して、正規化しておく必要がある。今回の mnist データは、784個の特徴量は全て画像のピクセル値を示しており、その数値は 0~255 に限定されている。つまり全ての特徴量が同じ範囲内の数値になっているため、正規化せずにそのまま用いれる。

In [51]:
from sklearn.decomposition import PCA

# 主成分分析を実行
pca = PCA()
pca.fit(X)
Out[51]:
PCA()

ここで、pca.explained_variance_ratio_ で、計算された主成分ごとの分類寄与率が格納されている。この寄与率の累積和を可視化して、分類に寄与する成分の数を確認してみる。

In [91]:
cumsum = [0] + list(np.cumsum(pca.explained_variance_ratio_))
d = np.argmax(np.cumsum(pca.explained_variance_ratio_) > 0.9) # 累積和が 0.9 を超えた時の主成分数
plt.figure(figsize=(15, 5))

plt.subplot(121)
plt.plot(cumsum, linewidth=3)
plt.axis([0, 784, 0, 1])
plt.xlabel('Number of Components', fontsize=15)
plt.ylabel('Comulative Contribution Rate', fontsize=15)
plt.grid()
plt.title('All 784 components', fontsize=15)

plt.subplot(122)
plt.plot(cumsum, linewidth=3)
plt.plot([0, d], [0.9, 0.9], ':')
plt.axis([0, 100, 0, 1])
plt.xlabel('Number of Components', fontsize=15)
plt.ylabel('Comulative Contribution Rate', fontsize=15)
plt.grid()
plt.title('Top 100 components', fontsize=15)

plt.tight_layout()
plt.show()

左の図は、PCA にて得られた全成分784個の寄与率の累積和のグラフを示している。この図から上位約300個の成分で、元のデータをほぼ表現可能であることがわかる。
右の図は、上位100個までの累積和であり、オレンジの線は、寄与率が90%の点を示している。上位80個程度の成分で、元のデータを約90%説明できることを表している。

主成分による元データの復元

上のグラフから、ある程度の個数の主成分で元のデータをほとんど表現できることがわかった。ここで、それら上位の主成分を使ってデータを復元してみる。sklearnPCA は、引数 n_components に 0~1 の指定の値を渡すことで、寄与率累積和がその値になる成分だけを返す。また、任意の整数を指定すると、その整数個の主成分を返すようになる。例えば 2 と指定して、可視化してみる際に用いれる。
さらに、inverse_transform を使うことで、主成分分析後の成分から元の次元のデータへ復元させることができる。

In [130]:
pca_95 = PCA(n_components=0.95) # 累積和が 95%
pca_90 = PCA(n_components=0.9)  # 累積和が 90%
pca_80 = PCA(n_components=0.8)  # 累積和が 80%
pca_50 = PCA(n_components=0.5)  # 累積和が 50%

# 各設定で主成分分析
data_95 = pca_95.fit_transform(X)
data_90 = pca_90.fit_transform(X)
data_80 = pca_80.fit_transform(X)
data_50 = pca_50.fit_transform(X)

# 95%, 90%, 80%, 50% に必要な成分数
print('Number of Components for 95% : ', pca_95.n_components_)
print('Number of Components for 90% : ', pca_90.n_components_)
print('Number of Components for 80% : ', pca_80.n_components_)
print('Number of Components for 50% : ', pca_50.n_components_)
Number of Components for 95% :  153
Number of Components for 90% :  87
Number of Components for 80% :  43
Number of Components for 50% :  11

それぞれの設定の主成分分析により、指定の寄与率累積和を達成する成分数がわかった。これらの数字は、上のグラフからも正し いことがわかる。
次に、これらの成分をつかって元データへ復元をしてみる。復元したデータ(手書き文字)を可視化することで、どの程度元画像を表現できるかを確認してみる。

In [131]:
# 各PCAで、元データの次元へデータを復元
data_95 = pca_95.inverse_transform(data_95)
data_90 = pca_90.inverse_transform(data_90)
data_80 = pca_80.inverse_transform(data_80)
data_50 = pca_50.inverse_transform(data_50)


print('Original Pictures ...')
plt.figure(figsize=(10, 3))
for i in range(10):
    plt.subplot(1, 10, i+1)
    plt.imshow(X[y==i][0].reshape(28, 28),
               cmap=mpl.cm.binary)
    plt.axis('off')
plt.tight_layout()
plt.show()

for i, data in zip([95, 90, 80, 50], [data_95, data_90, data_80, data_50]):
    print('Inversed Pictures by {}% Components ...'.format(i))
    plt.figure(figsize=(10, 3))
    for i in range(10):
        plt.subplot(1, 10, i+1)
        plt.imshow(data[y==i][0].reshape(28, 28),
                   cmap=mpl.cm.binary)
        plt.axis('off')
    plt.tight_layout()
    plt.show()
Original Pictures ...
Inversed Pictures by 95% Components ...
Inversed Pictures by 90% Components ...
Inversed Pictures by 80% Components ...
Inversed Pictures by 50% Components ...

可視化した結果をみると、90%以上では、ほぼオリジナル画像と同じ情報(何の数字か)を復元できている。それより下では、画像がぼやけて初見だと判別が難しい画像が現れてきている。
今回、寄与率累積和が 90% 以上になるには、87個の主成分で表現できた。つまりもともと 784次元あった特徴量を、少しの情報損失を許すだけで、87個の特徴量へ次元削減できることがわかった。この次元削減テクニックを使うことで、機械学習モデルへ投入する特徴量数を減らすことができ、モデルの高速化につながる。