読者です 読者をやめる 読者になる 読者になる

old school magic

機械学習と統計とプログラミングについてちょっとずつ勉強していきます。

Pythonで主成分分析

Python 機械学習

概要

主成分分析(Principal Component Analysis, PCA)とは、

  1. データの無相関化
  2. データの次元の削減

を行う手法です。
簡単に言うと、データを分析しやすいように再構成し、可能なら次元を下げることです。

なぜ次元を削減する必要があるかと言うと、機械学習や統計において、データの次元が大きすぎると認識精度が悪くなる、次元の呪いという現象を回避するためです。
(2次元や3次元に変換できると可視化できる、というメリットもあります。)

今回は、Pythonを使って主成分分析を試してみようと思います。

主成分分析の例

ライブラリとしてscikit-learn、テストデータとしてiris datasetを用います。

scikit-learnPython機械学習ライブラリです。主成分分析も実装されています。
導入等については、次の記事をご参照ください。
MacでPythonの機械学習環境構築(2014年5月版) - old school magic

iris datasetは、アヤメに関するデータセットです。
150個のデータがあり、3種類のアヤメがあります。がく片と花びらについて、それぞれ長さと幅を計測しているので、4次元のデータということになります。
詳しくはこちらをご参照ください。
Iris flower data set - Wikipedia, the free encyclopedia
UCI Machine Learning Repository: Iris Data Set


まずはデータを読み込みます。

import numpy as np
import matplotlib
import matplotlib.pyplot as plt

from sklearn import datasets

# データセットの読み込み
iris = datasets.load_iris()
X = iris.data
Y = iris.target

# 主成分分析前のサイズ
X.shape

Xは150個の4次元データなので、(150, 4)と出力されます。
これを主成分分析を用いて2次元に変換してみましょう。

# 主成分分析による次元削減
pca = decomposition.PCA(n_components = 2)
pca.fit(X)
X_pca= pca.transform(X)

# 主成分分析後のサイズ
X_pca.shape

変換後のデータはXは150個の2次元データなので、(150, 2)と出力されます。

各アヤメ(3種類)毎に可視化して見るとこんな感じになります。

f:id:breakbee:20140713182157p:plain

主成分分析のアルゴリズム

主成分分析が何をしているか、について簡単に説明します。
主成分分析のメインは固有値問題を解くことです。

固有値問題とは、ある行列 {\bf{A}} について、
{\bf{Ax}=\lambda \bf{x}}
となるような固有値 {\lambda}固有ベクトル {\bf{x}} を求めることです。

元のデータを {\bf{X}} 、データの次元を {N} 、変換後の次元を {M} とすると、

  1. データの共分散行列を求める
  2. 共分散行列の固有値固有ベクトルを求める
  3. 固有値が大きい順に、固有値と対応する固有ベクトルと並べ替える
  4. (対応する固有値の大きい順に選んだ) {M} 個の固有ベクトルを並べた行列 {\bf{P}} を作る
  5. データからその平均ベクトルを引いたデータを {\bf{X_{bar}}} とする
  6. データを行列 {\bf{P}} を用いて変換する ( {\bf{X_{pca}}=\bf{X_{bar}P}} )

となります。
先程、scikit-learnを使ってあっさりやっていた次元削減のコードは、次のコードに対応します。

### 主成分分析による次元削減

# 共分散行列を求める
X_bar = np.array([row - np.mean(row) for row in X.transpose()]).transpose()
m = np.dot(X_bar.T, X_bar) / X.shape[0]
# 固有値問題を解く
(w, v) = np.linalg.eig(m)
v = v.T

# 固有値の大きい順に固有値と固有ベクトルをソート
tmp = {}
for i, value in enumerate(w):
	tmp[value] = i

v_sorted = []
for key in sorted(tmp.keys(), reverse=True):
	v_sorted.append(v[tmp[key]])
v_sorted = np.array(v_sorted)

w_sorted = np.array(sorted(w, reverse=True))

# 次元削減
dim = 2
components = v_sorted[:dim,]
X_pca = np.dot(X_bar, components.T)

# 主成分分析後のサイズ
X_pca.shape

(実際には、scikit-learnの主成分分析は特異値分解と言う手法で主成分分析を行っています。)

参考:
【Python】 主成分分析(PCA) - 音楽プログラミングの超入門(仮)
Python の数値計算ライブラリで固有ベクトルを求める - 備忘録として

どうして次元削減できるのか

主成分分析はシンプルなアルゴリズムです。
しかし、どうして固有値問題を解くと次元削減が出来るの?という疑問が残ります。
そもそも、固有値固有ベクトルってどういう意味があるの?という方もいらっしゃるかと思います。

ここでは、ものすごくざっくりかつ不正確に固有値問題や次元削減の正当性について説明したいと思います。
ある1次元の変数 {y} が、係数 {a_{i}} と変数 {x_{i}} によって表現されるとします。

{y = a_{1}x_{1} + a_{2}x_{2} + ... + a_{M}x_{M}} + ... + a_{N}x_{N}

この時、 {a_{i}} は大きい順に並んでいると思ってください。
さて、例えば {a_{1}}{1000}{a_{2}}{500} といった大きい値なのに対して、 {a_{M}}{1}{a_{M}} 以降がさらに小さい値だったとします。
この時、 {y}{a_{M+1}} 以降を省いた

{y \simeq a_{1}x_{1} + a_{2}x_{2} + ... + a_{M}x_{M}}

で近似することができます。要するに、係数が大きい変数が重要な成分(=主成分)で、係数の小さい変数({x})は、どんな値をとっても結果({y})に影響を与えないので無視しよう、ということです。

実は、この行列版が固有値問題と主成分分析です。
{y = a_{1}x_{1} + a_{2}x_{2} + ... + a_{M}x_{M}} + ... + a_{N}x_{N}
を求めるのが固有値問題、係数 {a_{i}}固有値、変数 {x_{i}}固有ベクトルです。
つまり、固有値問題を解くことは、元のデータを分析しやすく再構成することにつながります。
そして、係数の小さい項を省いた部分が次元削減に対応しています。

より詳しいこと

上述した説明はあくまでもイメージなので、全然正確ではありません。
もし主成分分析について勉強したい、ということでしたら、次の2冊をおすすめします。

まず、固有値問題(線形代数)については、この本をおすすめします。

キーポイント線形代数 (理工系数学のキーポイント 2)

キーポイント線形代数 (理工系数学のキーポイント 2)

線形代数をイメージしやすく理解できるという点で、とても素晴らしい本だと思います。
(私が先程した固有値問題の説明は、この本の改悪でしかありません...)

そして、主成分分析の入門については、この本をおすすめします。

はじめてのパターン認識

はじめてのパターン認識

ここで述べた主成分分析はとても基本的な事項で、ここからカーネル主成分分析や部分空間法などに発展していきます。この本は、これらの事項を簡潔にまとめています。

補遺

今回のコードはgithub(というかiPython Notebook)で公開しています。
http://nbviewer.ipython.org/github/breakbee/PyNote/tree/master/

iPython Notebook があまりに便利で感動しています。

主成分分析のイメージの説明について、間違ってることや、こう説明したほうがいいよ、ということがありましたら、お知らせ頂けると幸いです。