はじめに
主成分分析を使って、画像データの傾向を掴んだり、前景と背景に分離することになりました。
勉強したことを備忘のためにもここに書いていきます。
#この記事で扱う内容
pythonのscikit learnのPCA(Principal Component Analysis)を使って主成分分析を実施し、低次元の主成分の空間に圧縮してみます。データとしては、ここでもMNISTを使ってみます。
MNISTの訓練データを使ってPCAを実施し、主成分の寄与率を求めてデータの75%ほどを説明できる次元数を求めます。そしてこの低い次元数に圧縮した画像を、訓練データとテストデータそれぞれで確認します。
また、第1主成分と第2主成分の二次元まで落として可視化して、0から9までの手書き文字がどのように分布するかを確認します。
#実装
##MNISTの読み込みと寄与率の確認
まずはMNISTデータを読み込みます。ここでは、sklearのfetch_openmlを使います。
読み込んだ後は、[0,1]に規格化して、訓練データ(train)とテストデータ(test)に分けます。
from sklearn.datasets import fetch_openml
X, y = fetch_openml('mnist_784', version=1, return_X_y=True)
#rescale
X = X / 255.
X_train, X_test = X[:60000], X[60000:]
y_train, y_test = y[:60000], y[60000:]
続いて、数値計算するためのnumpyとscikit learnのPCAをインポートして、訓練データに対して主成分分析を実施します。
import numpy as np
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
pca = PCA()
pca.fit(X_train)
各主成分の寄与率は"explained_variance_ratio_"という変数に入ります。累積寄与率を確認するため、numpyのaccumulateを使って累積寄与率をプロットします。
accumulated_ratio_ = np.add.accumulate(pca.explained_variance_ratio_)
plt.plot(accumulated_ratio_)
最初の30成分でデータの分散の73%を説明できています。
30次元に次元圧縮
次は、潜在空間の次元数を30と指定して、主成分分析を行って次元圧縮します。元の次元が784(=28×28)ですから、情報量は元の約4%とかなり圧縮していることになります。
fit関数で主成分を求めた後、transform関数を使ってこの30次元の主成分に変換して圧縮します。それからinverse_transformを使って圧縮された情報から元の画像を復元します。まずは、訓練データに対して実施します。
n_comp = 30
pca = PCA(n_components=n_comp)
pca.fit(X_train)
#30次元の潜在空間に変換して圧縮
X_train_latent = pca.transform(X_train)
#圧縮した情報から元の784次元の画像に復元
X_train_inv = pca.inverse_transform(X_train_latent)
復元した画像を、元の画像と比較してみます。左が圧縮してから復元した画像、右側が元の画像になります。
for ii in range(0,5):
plt.subplot(1,2,1)
plt.imshow(X_train_inv[ii].reshape(28,28), cmap='gray')
plt.subplot(1,2,2)
plt.imshow(X_train[ii].reshape(28,28), cmap='gray')
plt.show()
plt.close()
かなり圧縮した情報から復元した割には、元の画像をだいぶ表しているのではないでしょうか。
30この主成分が画像のどのような特徴を表しているかを確認します。主成分の固有ベクトルから成る行列は、"components_"という変数に入っています。これを可視化してみます。
(マイナスを含むcomponents_をmatplotlibのimshowに入れて、規格化などをimshowの仕様に従って可視化するというのは少ししっくりこない部分が個人的にはあるのですが、これは標準的な確認の仕方のようです。人の顔画像を扱ったこちらの記事などを参考にしました)
plt.figure(figsize=(15,15))
for jj in range(0,30):
plt.subplot(6,5,jj+1)
plt.imshow(pca.components_[jj].reshape(28,28), cmap='gray')
左上が第一主成分で、右下に進んでいきます。第一主成分は0のような全体的に輪を描く画像、第二主成分は8,9のようなものを表しているのでしょうか。
では、主成分分析の固有ベクトルを求めるのに使っていないアウトサンプルである、テストデータに適用して実際にどの程度復元できるかを確認します。
pca.fit(X_test)
X_test_latent = pca.transform(X_test)
X_test_inv = pca.inverse_transform(X_test_latent)
plt.figure(figsize=(10,10))
for ii in range(0,5):
plt.subplot(5,2,2*ii+1)
plt.imshow(X_test_inv[ii].reshape(28,28), cmap='gray')
plt.subplot(5,2,2*ii+2)
plt.imshow(X_test[ii].reshape(28,28), cmap='gray')
plt.show()
plt.close()
テストデータについても、訓練データと遜色なく復元できていると思われます。
テストデータ10,000件について、第一主成分と第二主成分の2次元で散布図を描いてみました。
#2次元の潜在空間でプロット
list_y = np.unique(y_test)
list_y
for kk in range(0, len(list_y)):
cond = np.where(y_test == list_y[kk])
plt.scatter( X_test_latent[cond][:,0], X_test_latent[cond][:,1], s=5, label=list_y[kk])
plt.xlabel('1st principal component')
plt.ylabel('2nd principal component')
plt.legend()
plt.show()
0と1は他とまずまず分離していますが、6と8、または7と9などはこの2次元ではあまり区別できないですね。
今回は次元圧縮を中心に画像に対して主成分分析を適用しました。
主成分分析は、前景と背景の分離などにも使われますので、次の記事ではこういった進んだ内容を扱いたいと思います。