Python 有村架純を主成分分析（PCA）してみる

#1.はじめに
  最近、sklearnの主成分分析（PCA）を勉強したので、ちょっと応用をやってみたくなった。

せっかくなので、題材は散布図の様なものより、画像が面白そう。なので、丁度手元にあった有村架純さんの顔画像にしたいと思います。

あっ、私、有村架純さんのファンではありません。たまたま、手元にデータがあっただけです。。。。

#2.データの準備
  カレントフォルダー(./arimura)に、223枚の有村架純さんの顔画像(png)を用意して、64*64のモノクロ画像に変換し、データにします。

```python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from sklearn.decomposition import PCA
from PIL import Image
import glob

# 初期設定
image_size = 64

# データ画像の読み込み
x = []
files = glob.glob('./arimura'+'/*.png')
for file in files:
    image = Image.open(file)
    image = image.convert('L')  # カラーを白黒に
    image = image.resize((image_size, image_size))  # image_seize * image_size に縮小
    data = np.asarray(image)
    x.append(data)
X = np.array(x) 
X = X.reshape(X.shape[0], image_size * image_size) 
X = X / 255.0  

# データ画像の表示(最初の50枚)
print('X.shape =', X.shape)
rows, cols = 5, 10  #5行10列
fig, aX_invs = plt.subplots(ncols=cols, nrows=rows, figsize=(18, 10)) 
for i in range(50):
    r = i // cols
    c = i % cols
    aX_invs[r, c].imshow(X[i].reshape(image_size,image_size),vmin=0.0,vmax=1.0, cmap = cm.Greys_r)
    aX_invs[r, c].set_title('data %d' % (i+1))
    aX_invs[r, c].get_xaxis().set_visible(False)
    aX_invs[r, c].get_yaxis().set_visible(False)
plt.show()
```
![スクリーンショット 2019-10-01 15.31.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/67e96735-908b-86a3-6eb6-15eda8cc401c.png)
64*64モノクロの有村架純さんです。画像は全部で223枚ですが、ここでは最初の50枚のみ表示しています。

PILは画像を読み込んでから、カラーから白黒へとか、サイズを変更したりとか、便利ですね。

最終的にデータは、X.shape = (223, 4096）にしています。



#3. 主成分分析
さて、これをどの位、次元削減するかですが、思い切って 1/100 まで次元削減してみたいと思います。数字の切りの良いところで、64*64=4,096次元 →　40次元としましょう。

```python
# PCA(主成分分析)
N = 40  # 40成分でPCA
pca = PCA(n_components=N)
pca.fit(X)
print('n_components = '+str(N))
print('explained_variance_ratio = ', pca.explained_variance_ratio_.sum())

# 第1〜第40成分の画像の表示
rows, cols = 5, 8  #5行8列
fig, aX_invs = plt.subplots(ncols=cols, nrows=rows, figsize=(18, 10)) 
for i in range(N):
    r = i // cols
    c = i % cols
    aX_invs[r, c].imshow(pca.components_[i].reshape(image_size,image_size),vmin=-0.05,vmax=0.05, cmap = cm.Greys_r)
    aX_invs[r, c].set_title('component %d' % (i+1))
    aX_invs[r, c].get_xaxis().set_visible(False)
    aX_invs[r, c].get_yaxis().set_visible(False)
plt.show()
```
![スクリーンショット 2019-10-01 16.30.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/325e057b-8d99-78a9-18d3-7fb9fd835dca.png)
**components1** が第1主成分なのですが、ほとんど人間の顔の原型に近いですね。当たり前か。ただ、気のせいか第2主成分は、なんか有村架純の片鱗がある様な。。。。

下位の成分に行くに従って、様々なバリエーションが現れますが、それが有村架純の特徴かと言われると、なんとも言えないですね。

**explained_variance_ratio = 0.8631** なので、たった40次元ですが、これで全体の86%をカバーしている様です。結構なカバー率ですね。

#4.画像の次元削減と復元
さあ、この40個の主成分情報を使って、有村架純の画像を1/100に次元削減してから、復元してみたいと思います。

```python
# 次元削減と次元復元の係数
X_trans = pca.transform(X)
X_inv = pca.inverse_transform(X_trans)
print ('X.shape =', X.shape)
print ('X_trans.shape =', X_trans.shape)
print ('X_inv.shape =', X_inv.shape)

# オリジナル画像と、次元削減後に復元した画像の表示
rows, cols = 2, 8  # 2行8列
fig, aX_invs = plt.subplots(ncols=cols, nrows=rows, figsize=(18,4))
for i in range(8):
    aX_invs[0, i].imshow(X[i,:].reshape(image_size, image_size),vmin=0.0,vmax=1.0, cmap = cm.Greys_r)
    aX_invs[0, i].set_title('original %d' % (i+1))
    aX_invs[0, i].get_xaxis().set_visible(False)
    aX_invs[0, i].get_yaxis().set_visible(False)
    aX_invs[1, i].imshow(X_inv[i,:].reshape(image_size, image_size),vmin=0.0,vmax=1.0, cmap = cm.Greys_r)
    aX_invs[1, i].set_title('restore %d' % (i+1))
    aX_invs[1, i].get_xaxis().set_visible(False)
    aX_invs[1, i].get_yaxis().set_visible(False)
```
![スクリーンショット 2019-10-01 15.53.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/18af2f8d-475c-75d9-c570-e320da2a04b2.png)
オリジナル(original)と復元後(restore)に若干差はありますが、なんとかギリギリ有村架純らしさは残っているのではないでしょうか。

情報を1/100にしてから戻しているのに、PCA凄いです！