PCAとNMFを用いてアニメ顔の特徴量を検出しようと思ったらホラーになった

　機械学習といえば画像処理です。PCA（主成分分析）やNMF（非負値行列因子分析）といった古典的な解析手法によって、多数の顔画像から主要な特徴量を抽出できるということで、さっそくアニメ顔を対象にやってみました。対象となるのは以下のような21,551通りの顔データです。https://www.kaggle.com/soumikrakshit/anime-faces からお借りしました。ありがとうございます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/a1bf3739-838c-e1ec-ac83-3e1ef329b3d0.png)

　解析によって、輪郭を決定づける成分、髪を決定づける成分などが視覚的に分解できたら面白いですね。

#PCA

```Python
import cv2
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

sample = 20000

X = []

for i in range(sample):
    file_name = str(i+1) + '.png'
    img = cv2.imread(file_name)
    img = img.reshape(12288)/255
    X.append(img)

pca = PCA(n_components=9, whiten=True)
pca.fit(X)
X_pca = pca.transform(X)

fig, axes = plt.subplots(3,3,figsize=(10,10))
for i,(component, ax) in enumerate(zip(pca.components_,axes.ravel())):
    ax.imshow(0.5-component.reshape((64,64,3))*10)
    ax.set_title('PC'+str(i+1))

print(pca.explained_variance_ratio_)

plt.show()
```

　解析結果がこちら。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/5e40bce1-a663-cad1-d023-70ce7a31ed60.png)

　ホラーだ！　壁に滲んだ怨念の顔みたいになっとる！

　PC1から順に画像の主要な部分を説明するように変数（ここでいうと座標とピクセル）を束ねたものになっているはずですが……

- PC1　全体の明るさ
- PC2　髪の全体的なボリューム？
- PC3　左を向いているかどうか？

　みたいな傾向を**無理すれば**読めなくもないですが、明確な特徴量とはとても言えないですね。PCによる説明率を見ても、それほど集約できているとは言えなさそうです。残念。

```Python
print(pca.explained_variance_ratio_)
```

```Python:Output
[0.21259875 0.06924239 0.03746094 0.03456278 0.02741101 0.01864574
 0.01643447 0.01489064 0.0133781 ]
```

# NMF

```Python
import cv2
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import NMF

sample = 20000

X = []

for i in range(sample):
    file_name = str(i+1) + '.png'
    img = cv2.imread(file_name)
    img = img.reshape(12288)/255
    X.append(img)

nmf = NMF(n_components=9)
nmf.fit(X)
#X_nmf = nmf.transform(X)

fig, axes = plt.subplots(3,3,figsize=(10,10))
for i,(component, ax) in enumerate(zip(nmf.components_,axes.ravel())):
    ax.imshow(component.reshape((64,64,3)))
    ax.set_title('component'+str(i+1))
    
plt.show()
```

　解析結果がこちら。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/719a1922-047d-db51-37de-00d1e59aef18.png)

　ホラーだ！　フィルムに映り込んだ亡霊のネガになっとる！　もはや解釈する気も起きない。

# キルミーベイベー

　先人に倣ってキルミーベイベーでも解析を試みていきます。

PCA
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/46d97bbc-7005-88bc-00bb-ea9a9673b1c0.png)

NMF
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/443ce343-c969-03fe-f310-c27efb3a0dce.png)

　南無。

# 結論

　やはり線形的な手法だけで（それも教師なしで）画像の特徴量を抽出することは難しいようです。GANなどの発展的な手法を用いてさらなる解析を進めることが望まれるので勉強します、ハイ。

# コードの解説

　なんか短くなってしまったのでコードの解説。画像の読み込みと展開について。実行するpythonファイルと同じ場所に画像ファイルを置いておけば、ファイル名を指定するだけでアクセスできます。`1.jpg`、`2.jpg`というように名付けていけば単純なイテレーションでファイル名を指定できます。より一般的なコードだと、`os.listdir`でファイル名を取得しますが、今回はラクをしました。

　読み込んだファイルは`cv2`モジュールにある`imread`という関数を使って配列化します。しかしこの配列は縦×横×RGBという3次元配列になっているので、後の解析に使うため、`reshape`で1行の長いベクトルにします。今回は縦64、横64、RGBなので`64*64*3=12288`となります。また、配列の値が生データだとRGBの値が`0～255`なっているので、255で割ることにより`0～1`の間に収めます。

```Python
    file_name = str(i+1) + '.png'
    img = cv2.imread(file_name)
    img = img.reshape(12288)/255
```

　以上の内容をコードにしたものが上です。

　解析によって得られた成分は、PCAでもNMFでも`components_`に格納されています。リファレンスはhttps://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html とhttps://scikit-learn.org/stable/modules/generated/sklearn.decomposition.NMF.html の`Attributes:`を参照。これを`pyplot`モジュールにある`imshow`という関数でグラフに表示します。これは先ほどとは逆に、解析結果（1行の長いベクトル）をまた縦×横×RGBに整形しなおす必要があるのでまた`reshape`していきます。

```Python
fig, axes = plt.subplots(3,3,figsize=(10,10))
for i,(component, ax) in enumerate(zip(pca.components_,axes.ravel())):
    ax.imshow(0.5-component.reshape((64,64,3))*10)
    ax.set_title('PC'+str(i+1))
```

　以上の内容をコードにしたものが上です。`axes`は多数のグラフの中でどのグラフの位置にあるかとういことを示していますが、`axes.ravel`ではそれを一連の配列として取得することができます。これも一種の`reshape`のようなものですね。