特徴抽出による情報圧縮(主成分分析)|Pythonで機械学習vol.7

alert最終更新日から半年以上経過した記事です。
エンジニアの初心者でも簡単に機械学習のプログラミングが出来るシリーズ最終回!前回は教師なし学習の第一歩としてクラスタリングについて学習しましたが、今回は特徴抽出による情報圧縮の方法として主成分分析について学習していきましょう。

情報圧縮の必要性について

http://www.turingfinance.com/artificial-intelligence-and-statistics-principal-component-analysis-and-self-organizing-maps/

機械学習を行う上でたくさんの特徴や変数がある場合、それをごく少数の項目に置き換えることで、データを解釈しやすくする必要がでてきます。
このような処理を「次元の縮約(縮小)」や「情報の圧縮」と呼びます。イメージがわかない方のために、簡単な例をあげます。

例えば5次元のデータを2次元のデータに縮小する例として、体形評価に使用される指標であるBMI (Body Mass Index)を思い浮かべてみてください。
BMIはどのように算出されているかというと、体重(kg) ÷ 身長(m)<sup>2</sup>によって導き出します。つまり、2次元データである体重と身長を、1次元データであるBMIに縮小しているのです。
これにより、多くの変数(この場合、体重と身長)を扱うことなく、より少ない変数(この場合、BMI)だけで対象の人物がどんな体形をしているのかがわかるのです。

しかし、次元の縮約には欠点があります。
BMIの例からわかるとおり、本来もっていた情報が損なわれることになります。どのような体形をしているのかはBMIの指標から読み取ることができるのですが、その人物がどのくらいの身長なのかが全くわかりません。

このような欠点をなるべく抑えるよう多次元データのもつ情報をできるだけ損わずに、低次元空間に情報を縮約する方法として主成分分析が使われます。

主成分分析(Principal Component Analysis)とは?

http://archive.cnx.org/contents/02ff5dd2-fe30-4bf5-8e2a-83b5c3dc0333@10/dimensionality-reduction-methods-for-molecular-motion

主成分分析とは(引用:Wikipedia)
主成分分析(しゅせいぶんぶんせき、英: principal component analysis; PCA)とは、相関のある多数の変数から相関のない少数で全体のばらつきを最もよく表す主成分と呼ばれる変数を合成する多変量解析の一手法。データの次元を削減するために用いられる。
主成分を与える変換は、第一主成分の分散を最大化し、続く主成分はそれまでに決定した主成分と直交するという拘束条件の下で分散を最大化するようにして選ばれる。主成分の分散を最大化することは、観測値の変化に対する説明能力を可能な限り主成分に持たせる目的で行われる。選ばれた主成分は互いに直交し、与えられた観測値のセットを線型結合として表すことができる。言い換えると、主成分は観測値のセットの直交基底となっている。主成分ベクトルの直交性は、主成分ベクトルが共分散行列(あるいは相関行列)の固有ベクトルになっており、共分散行列が実対称行列であることから導かれる。
主成分分析は純粋に固有ベクトルに基づく多変量解析の中で最も単純なものである。主成分分析は、データの分散をより良く説明するという観点から、そのデータの内部構造を明らかにするものだと考えられる。多くの場合、多変量データは次元が大きく、各変数を軸にとって視覚化することは難しいが、主成分分析によって情報をより少ない次元に集約することでデータを視覚化できる。集約によって得られる情報は、データセットを元のデータ変数の空間から主成分ベクトルのなす空間へ射影したものであり、元のデータから有用な情報を抜き出したものになっている。主成分分析によるデータ構造の可視化は、可視化に必要なだけ先頭から少数の主成分を選択することで実現される。
主成分分析は探索的データ解析(英語版)における主要な道具であり、予測モデル構築(英語版)にも使われる。主成分分析は観測値の共分散行列や相関行列に対する固有値分解、あるいは(大抵は正規化された)データ行列の特異値分解によって行われる。主成分分析の結果は主成分得点(因子得点、英: score)と主成分負荷量(因子負荷量、英: loadings)によって評価される。主成分得点とは、あるデータ点を主成分ベクトルで表現した場合の基底ベクトルにかかる係数であり、ある主成分ベクトルのデータ点に対する寄与の大きさを示す。主成分負荷量はある主成分得点に対する個々の(正規化された)観測値の重みであり、観測値と主成分の相関係数として与えられる。主成分分析は観測値の間の相対的なスケールに対して敏感である。
主成分分析による評価は主成分得点と主成分負荷量をそれぞれ可視化した主成分プロット、あるいは両者を重ね合わせたバイプロットを通して解釈される。主成分分析を実行するためのソフトウェアや関数によって、観測値の基準化の方法や数値計算のアルゴリズムに細かな差異が存在し、個々の方法は必ずしも互いに等価であるとは限らない(例えば、R言語における prcomp 関数と FactoMineR の PCA 関数の結果は異なる)。
https://ja.wikipedia.org/wiki/%E4%B8%BB%E6%88%90%E5%88%86%E5%88%86%E6%9E%90


と、なんだか難しそうですね、、

とっても簡単にまとめると、データの縮小を行う際になるべく情報損失を避けるため、データの分散が大きいところ(主成分)を見つけて以下のように処理を行います。

1. 全データの重心を求めます。(平均値)
2. 重心からデータの分散が最大となる方向を見つけます。
(=元データの情報損失ができるだけ小さくなる軸を探す。)
3. 新しいデータ表現軸として2.で求めた方向を基底にします。
4. 上記でとった軸と直交する方向に対して分散が最大となる方向を探します。
5. 「2.~4.」を元のデータの次元分だけくり返します。

※2.で求めた軸を第1主成分、4.で求めた軸を第2主成分といいます。

下の図を見てみると、どうして分散が大きいところが重要なのかがわかります。

http://www.statistics.co.jp/reference/software_R/statR_9_principal.pdf

右と左の図を比べてみると、各点がのっている新しい軸へと情報を圧縮した場合、右のほうがバラツキが少なくそれぞれの情報が似通っているように見えてしまいます。

一方、左の図では新しい軸へ情報を圧縮しても、それぞれの値にバラツキがあるため、各データの判別がつきやすい、つまり各データの情報損失が少ないということがわかります。

では実際にscikit-learnで用意されているライブラリを用いてPCAを実行するプログラムを書いていきましょう。

事前準備

まずはいつも通りMacの場合はターミナル、Windowsの場合はコマンドプロンプトから任意のディレクトリに移動し、Jupyterを起動します。

$ jupyter notebook

起動したら新規ファイルを作成して編集画面を表示します。
そして初めに今回利用するライブラリやモジュールを下のコマンドでインポートします。

# 分析・計算用
import numpy as np

# プロット用
import matplotlib.pyplot as plt
%matplotlib inline

# 機械学習用
from sklearn.decomposition import PCA

次に、利用するデータを読み込み、変数にデータを格納します。 今回はscikit-learnで用意されている顔画像を用います。

from sklearn.datasets import fetch_olivetti_faces
oliv=fetch_olivetti_faces()



PCAの実行

事前準備が完了したので、いよいよ分析開始です。
まずは、格納したデータを見てみましょう。

oliv.keys()
oliv.data.shape

64×64(4096)ピクセルの画像が400枚格納されているのがわかります。
次にMatplotlibを利用してデータを可視化して見てみましょう。
※全て表示すると膨大な量になるので、8列8行の64枚だけ表示しています。

fig = plt.figure(figsize=(6,6))
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)

for i in range(64):
  ax = fig.add_subplot(8, 8, i+1, xticks=[], yticks=[])
  ax.imshow(oliv.images[i], cmap=plt.cm.bone, interpolation='nearest')

plt.show()

8×8(64)ピクセルへとデータを縮小してみましょう。

X,y=oliv.data, oliv.target
pca_oliv = PCA(64)
X_proj = pca_oliv.fit_transform(X)
X_proj.shape

分散がどの程度保持されている確認してみましょう。

np.cumsum(pca_oliv.explained_variance_ratio_)

64×64を8×8に圧縮しても、依然として分散が約89.7%も残っていることがわかります。

ちなみに、分散率ごとに何次元必要なのかみてみましょう。

print('データの50%を表すのに軸は: {} 本'.format(np.sum(np.cumsum(pca_oliv.explained_variance_ratio_) < 0.50)))
print('データの75%を表すのに軸は: {} 本'.format(np.sum(np.cumsum(pca_oliv.explained_variance_ratio_) < 0.75)))
print('データの85%を表すのに軸は: {} 本'.format(np.sum(np.cumsum(pca_oliv.explained_variance_ratio_) < 0.85)))

つまりこれらの画像は、本来64x64px = 4096次元のデータですが、64本の軸(64次元)あればこのデータの約89.7%を表現することができ、3次元で約50%、18次元で約75%、39次元で85%の表現ができることがわかりました。

では、1番目の画像を対象として、次元数を増やしていって、その差を見ていきましょう。

まずは3次元。

def draw_face_compressed(img, ndim):
    A = np.dot((face_01 - pca_oliv.mean_), pca_oliv.components_.T[:, :ndim])
    A_inv = pca_oliv.components_.T[:, :ndim].dot(A) + pca_oliv.mean_
    return A_inv

image_shape = (64,64)
face_01 = oliv.images[0].reshape(-1)

n_dim = 3
face_compressed = draw_face_compressed(face_01, n_dim)
plt.imshow(face_compressed.reshape(image_shape), cmap=plt.cm.gray)

次に18次元。

n_dim = 18
face_compressed = draw_face_compressed(face_01, n_dim)
plt.imshow(face_compressed.reshape(image_shape), cmap=plt.cm.gray)

最後に64次元です。

n_dim = 64
face_compressed = draw_face_compressed(face_01, n_dim)
plt.imshow(face_compressed.reshape(image_shape), cmap=plt.cm.gray)

64次元になると、ほぼほぼ原型をとどめているのがわかります。

まとめ

今回は情報縮小の概要から、scikit-learnのライブラリを用いて、実際にPCAを実行してみました。

顔画像を例として用いましたが、その他にscikit-learnで用意されいているアヤメのデータを用いた処理も書いてありますのでご参照ください。

▼Facial Image Compression and Reconstruction with PCA
https://shankarmsy.github.io/posts/pca-sklearn.html

今回が「Pythonで機械学習」シリーズ最終回です。
長い間、お付き合い頂き本当にありがとうございました!