C++ の行列ライブラリ Eigen で SVD と LSI
C++ の行列ライブラリ Eigen(アイゲン)で SVD と LSI。
インストール
% tar zvxf eigen-eigen-3.0.3.tar.bz2 % cd eigen-eigen-3.0.3 % mkdir build_dir % cd build_dir % cmake ../ % sudo make install
サンプルを動かす
Eigen: Getting started にあるサンプルを動かしてみる。インクルードパスを追加するのを忘れずに(例:-I /usr/local/include/eigen3)。
#include <Eigen/Core> #include <iostream> using Eigen::MatrixXd; int main() { MatrixXd m(2, 2); m(0, 0) = 3; m(1, 0) = 2.5; m(0, 1) = -1; m(1, 1) = m(1, 0) + m(0, 1); std::cout << m << std::endl; }
動いた。
% ./eigen 3 -1 2.5 1.5
行列の初期化
Comma-initialization を使うと楽。
Matrix<float, 5, 6> c; c << 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1; std::cout << c << std::endl;
SVD
#include <Eigen/SVD>
して
JacobiSVD< Matrix<float, 5, 6> > svd(c, Eigen::ComputeFullU | Eigen::ComputeFullV); std::cout << "singular values" << std::endl << svd.singularValues() << std::endl; std::cout << "matrix U" << std::endl << svd.matrixU() << std::endl; std::cout << "matrix V" << std::endl << svd.matrixV() << std::endl;
できた。コンパイルに時間がかかるようになった。
low-rank approximation
型名が面倒になってきたので typedef する。
typedef Matrix<float, 5, 6> Matrixf56; typedef JacobiSVD<Matrixf56> SVDOfMatrixf56;
C の近似 C_3 を計算する。Singular Values から対角行列作る方法が泥臭い。もっといい方法あるかも。
static Matrixf56 LowRankApprox(const SVDOfMatrixf56::SingularValuesType& sv, const SVDOfMatrixf56::MatrixVType& v, int rank) { Matrixf56 y = Matrixf56::Zero(); for (int i = 0; i < rank && i < sv.size(); i++) { y(i, i) = sv(i); } return y * v.transpose(); }
c_3 = --1.6189 -0.604876 -0.440347 -0.965693 -0.70302 -0.262673 0.456717 0.842566 0.296174 -0.997319 -0.350573 -0.646747 0.356714 -0.954712 0.569498 -0.259686 0.154906 -0.414592 0 0 0 -0 0 0 0 0 0 -0 0 0
できた。
Cosine Similarity
類似度を計算してみよう。
単語文書行列の列(すなわち文書)同士の類似度を計算する。
float Cosine(const Matrixf56& c, int i, int j) { const VectorXf& col_i = c.col(i); const VectorXf& col_j = c.col(j); float a = col_i.transpose() * col_j; float b = col_i.norm() * col_j.norm(); return a / b; }
類似度が近似された C_k と C がどの程度一致するのか比較を出してみた。
rank が 3 と 2 の間に超えられない壁があって k = 3 の近似は比較的良い結果に見える。
c c_4 c_3 c_2 0 1.000000 1.000000 1.000000 1.000000 1 0.408248 0.422235 0.422235 0.781837 2 0.577350 0.630847 0.785422 0.950136 3 0.408248 0.418053 0.418053 0.474432 4 0.577350 0.608732 0.750473 0.740118 5 0.000000 -0.010525 -0.012915 0.110596
コード
#include <Eigen/Core> #include <Eigen/SVD> #include <iostream> using namespace Eigen; typedef Matrix<float, 5, 6> Matrixf56; typedef JacobiSVD<Matrixf56> SVDOfMatrixf56; static Matrixf56 LowRankApprox(const SVDOfMatrixf56::SingularValuesType& sv, const SVDOfMatrixf56::MatrixVType& v, int rank) { Matrixf56 y = Matrixf56::Zero(); for (int i = 0; i < rank && i < sv.size(); i++) { y(i, i) = sv(i); } return y * v.transpose(); } float Cosine(const Matrixf56& c, int i, int j) { const VectorXf& col_i = c.col(i); const VectorXf& col_j = c.col(j); float a = col_i.transpose() * col_j; float b = col_i.norm() * col_j.norm(); return a / b; } int main() { Matrixf56 c; c << 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1; SVDOfMatrixf56 svd(c, Eigen::ComputeFullU | Eigen::ComputeFullV); const SVDOfMatrixf56::MatrixUType& u = svd.matrixU(); const SVDOfMatrixf56::MatrixVType& v = svd.matrixV(); const SVDOfMatrixf56::SingularValuesType& sv = svd.singularValues(); std::cout << "singular values" << std::endl << sv << std::endl; std::cout << "matrix V" << std::endl << v << std::endl; std::cout << "matrix U" << std::endl << u << std::endl; Matrixf56 c_2 = LowRankApprox(sv, v, 2); Matrixf56 c_3 = LowRankApprox(sv, v, 3); Matrixf56 c_4 = LowRankApprox(sv, v, 4); std::cout << "c_3 = " << std::endl << c_3 << std::endl; std::cout << "cosine(0, 1) = " << std::endl << Cosine(c_3, 0, 1) << std::endl; printf(" c c_4 c_3 c_2 \n"); for (int i = 0; i < c_3.row(0).size(); i++) { printf("%d %5f %5f %5f %5f\n", i, Cosine(c, 0, i), Cosine(c_4, 0, i), Cosine(c_3, 0, i), Cosine(c_2, 0, i)); } return 0; }