Eigenの特異値分解を利用した主成分分析
今日はEigenを利用してデータ分析の手法として良く知られた主成分分析を実装してみます.基本的な部分は以下の記事と同じで,C言語やVBAから利用可能なDLLをビルドしようというものです.
さて,主成分分析は次元のデータベクタ,()について,その共分散行列の固有値問題を解くことで行うことができます.しかしながら,特にである場合,共分散行列の計算に時間がかかるなどの問題があります.こうした場合は特異値分解を利用した方法が推奨されます.具体的には平均減算を行ったデータベクタを横に並べた以下の行列の特異値問題を解くものです.
ただし,は,()の平均ベクタです.このの左特異行列が主成分ベクタに,特異値の二乗が主成分に対応します.
Eigenを利用する場合,実装は非常に簡単であり,その骨子は以下のようになります.
- 配列を受け取って
Map
オブジェクトで行列とみなす rowwise()
とmean()
で平均ベクタを計算- 平均減算した行列を
JacobiSVD
オブジェクトで特異値分解 - 左特異行列から主成分ベクタを,特異値の二乗から主成分を得る
Map
を利用することでコピー操作が不要になり,また,組み込み関数を利用するだけでfor
文などを利用した反復処理さえ不要になります.以下はデータベクタを列優先で行列として与え,平均ベクタと本の主成分ベクタ,それに対応する主成分を計算する関数です.
// This code is licensed under the GPL. // simplepca.cpp #include "eigen_vba.h" // Eigen #include <Eigen/Dense> using namespace Eigen; #ifdef _DEBUG #include <iostream> using namespace std; #endif // 特異値分解を用いた単純な主成分分析 EIGEN_VBA_API int CALL_VBADLL SimplePCA_(int m, int n, int r, double *_a, double *_mu, double *_lambda, double *_v){ // Map オブジェクト (既定通りの列優先) Map<MatrixXd> a(_a, m, n); Map<VectorXd> mu(_mu, m); Map<VectorXd> lambda(_lambda, r); Map<MatrixXd> u(_v, m, r); // 平均を計算する mu = a.rowwise().mean(); // 平均減算した行列を特異値分解する // (ComputeThinUを与えて特異値と左特異ベクタだけを計算する) JacobiSVD<MatrixXd> SVD((a.colwise() - mu), ComputeThinU); // 特異値の二乗と左特異ベクタを返す lambda = (SVD.singularValues()).block(0, 0, r, 1).array().square(); u = (SVD.matrixU()).block(0, 0, m, r); #ifdef _DEBUG cout << "A=[" << a << "];" << endl; cout << "mu=[" << mu << "];" << endl; cout << "lambda=[" << lambda << "];" << endl; cout << "V=[" << u << "];" << endl; #endif return 0; }
ビルドの方法は前回の記事を参照してください.乱数で構成した行列の第二主成分までの計算結果は以下です.
A=[0.423394 0.104033 0.561979 0.988998 0.477972 0.18587 0.0909832 0.281566 0.924881 0.155299 0.271587 0.481722]; mu=[0.363135 0.550947 0.432477 0.302869]; lambda=[0.749016 0.128381]; V=[ 0.170522 0.830388 -0.631153 0.5002 0.70674 0.245461 0.270346 0.00231725];
平均ベクタ,主成分,主成分ベクタが正しく計算されていることが分かります.