PCA算法的原理C++ Eigen庫實現(附源碼下載)

PCA的目的:  pca算法,也叫主成分分析法,能夠對一個多樣本的多維特徵向量構成的矩陣進行分析,分析主成分並去除維度之間的相關性,使線性相關的向量組變成線性無關的向量組。  並且可以對樣本進行降維,降高維向量映射到低維度空間,同時確保緯度之間的信息損失儘可能小。

首先給出PCA算法的過程:

  1. 對每一維度均值化爲0,計算出每一維度的均值,每一個數均減去這個均值
  2. 對於均值化爲零的矩陣,求其維度的協方差矩陣C
  3. 求C矩陣的特徵值V和特徵向量矩陣P
  4. 用P作基底,將原來的特徵矩陣映射到這一正交基上

基礎知識:

首先理解正交基分解:

我們默認一個n維向量v=(x1,x2,x3……xn)是一個n維空間的以n個單位基底(1,0,0,……),(0,1,0,0……)……所表示的座標形式。

例如,向量(1,2)可以表示爲以(1,0),(0,1)爲基底的一個向量。以下稱這種基底爲默認基底。

同時,在一個n維向量空間中,理論上是有無數多對基底的,要從默認基底變化到任意基底的座標表示情況,首先要明確,向量v在n維空間上i維的座標可以表示爲v與第i個基底的內積(數量積),即可表示爲v=(vi, vk, vj……)其中i,j,k……爲選取的基底集合。

那麼由m個樣本構成的n維特徵,表示爲n行m列的矩陣X。則將在默認基底下的矩陣X變換到以n個k維爲基底的表示,其矩陣表示爲Y=PX ,其中P是kxn的n個變換基向量構成的矩陣,Y是變換結果。

那麼一個n維的特徵矩陣就可以通過正交基P變換到k維空間。


接下來寫一下爲什麼要這樣做,爲什麼通過這一系列操作,就能達到PCA的目的呢?

首先理解一下協方差矩陣:  協方差矩陣的對角線上的值代表這一維度的方差,而方差能夠代表能量,方差越大,這一維度的能量越大(代表特徵的能力越大)。非對角線上的元素代表了兩個維度之間的協方差,協方差越小,相關性越小,協方差爲0時,兩個維度線性無關。

所以假設我們最後經過PCA處理要得到矩陣Y,Y是通過P這一正交基映得到的,即Y = PX, 那麼我們要儘可能的使Y的協方差矩陣主對角線上的元素值儘可能大,而非對角線上的元素值儘可能爲0。

假設Y的協方差矩陣爲D,則:

我們要找的P恰好就是能讓C對角化的P,根據線性代數知識,我們知道要使一個矩陣對角化,只需要求其特徵值和特徵向量,使用特徵向量就能通過矩陣乘法將其對角化。

所以P就是C的特徵向量矩陣。

這也就是我們所做的要求協方差矩陣,求其特徵值和特徵向量的原因。

但爲了達到降維的目的,我們不能單純的取全部特徵向量來做,這樣做的話維度是不變的,只是去除了一些相關性。所以我們要選取一部分特徵向量來用。

衡量特徵向量好壞的標準就是特徵值,特徵值越大,其對應的特徵向量的維度分量上能量越大。我們對特徵值進行從大到小排序,選取前K大的特徵值對應的向量作爲基底。

K的選取取決於我們想保留多少信息,這個計算方式爲 sum(前k特徵值)/ sum(所有特徵值)

接下來附c++實現代碼並帶有註釋:

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<fstream>
#include <Eigen/Dense>
using namespace std;
using namespace Eigen;
void featurenormalize(MatrixXd &X)
{
	//計算每一維度均值
	MatrixXd meanval = X.colwise().mean();//每一列的矩陣,列降維
	RowVectorXd meanvecRow = meanval;
	//樣本均值化爲0
	X.rowwise() -= meanvecRow;
	
}
void computeCov(MatrixXd &X, MatrixXd &C)
{
	//計算協方差矩陣C = XTX / n-1;
	C = X.adjoint() * X;
	//C = X * X.adjoint() ;
	MatrixXd Y;
	Y = X.adjoint();//轉置矩陣+
	for (int i = 0; i < Y.rows(); i++)
	{
		for (int j = 0; j < Y.cols(); j++)
		{
			cout << " " << Y(i, j);
		}
		cout << " " << endl;
	}
	//C = C.array() / X.rows() - 1;
	//C = C.array() / (X.rows() - 1);
	C = C.array() / (X.cols() - 1);
	for (int i = 0; i < C.rows(); i++)
	{
		for (int j = 0; j < C.cols(); j++)
		{
			cout << " " << C(i, j);
		}
		cout << " " << endl;
	}
}
void computeEig(MatrixXd &C, MatrixXd &vec, MatrixXd &val)
{
	//計算特徵值和特徵向量,使用selfadjont按照對陣矩陣的算法去計算,可以讓產生的vec和val按照有序排列
	SelfAdjointEigenSolver<MatrixXd> eig(C);

	vec = eig.eigenvectors();
	val = eig.eigenvalues();
}
int computeDim(MatrixXd &val)
{
	int dim;
	double sum = 0;
	for (int i = val.rows() - 1; i >= 0; --i)
	{
		sum += val(i, 0);
		dim = i;

		if (sum / val.sum() >= 0.95)
			break;
	}
	//std::cout << "sum: " << sum << " val.sum(): " << val.sum() << "  rows: " << val.rows() << "  dim: " << dim << std::endl;
	return val.rows() - dim;
}
int main()
{
	ifstream fin("siftsmallD.txt");
	ofstream fout("output.txt");
	/*const int m = 10000, n = 128;
	MatrixXd X(10000, 128), C(128, 128);*/
	const int m = 8, n = 6;
	MatrixXd X(m, n), C(3, 3);
	MatrixXd vec, val;

	//讀取數據
	//init
	double in[200];
	for (int i = 0; i < m; ++i)
	{
		for (int j = 0; j < n; ++j)
			fin >> in[j];
		for (int j = 1; j <= n; ++j)
		{
			X(i, j - 1) = in[j - 1];
			//cout << " " << X(i, j - 1);
		}
		//cout << " " << endl;
	}
	
	//cout << "X.rows(): " << X.rows() << "  X.cols(): " << X.cols() << endl;

	//pca
	//零均值化
	featurenormalize(X);
	//計算協方差
	computeCov(X, C);
	//計算特徵值和特徵向量
	computeEig(C, vec, val);
	//計算損失率,確定降低維數
	int dim = computeDim(val);
	std::cout << "dim: " << dim << std::endl;
	//計算結果
	MatrixXd res = X * vec.rightCols(dim);
	//輸出結果
	fout << "the result is " << res.rows() << "x" << res.cols() << " after pca algorithm." << endl;
	fout << res;
	system("pause");
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章