码迷,mamicode.com
首页 > Windows程序 > 详细

用opencv实现的PCA算法,非API调用

时间:2014-07-31 13:24:06      阅读:634      评论:0      收藏:0      [点我收藏+]

标签:pca   opencv   图像处理   算法   数据压缩   

理论参考文献:但此文没有代码实现,这里自己实现一下,让理解更为深刻

问题:假设在IR中我们建立的文档-词项矩阵中,有两个词项为“learn”和“study”,在传统的向量空间模型中,认为两者独立。然而从语义的角度来讲,两者是相似的,而且两者出现频率也类似,是不是可以合成为一个特征呢?

       《模型选择和规则化》谈到的特征选择的问题,就是要剔除的特征主要是和类标签无关的特征。比如“学生的名字”就和他的“成绩”无关,使用的是互信息的方法。

       而这里的特征很多是和类标签有关的,但里面存在噪声或者冗余。在这种情况下,需要一种特征降维的方法来减少特征数,减少噪音和冗余,减少过度拟合的可能性。

        PCA的思想是将n维特征映射到k维上(k<n),这k维是全新的正交特征。这k维特征称为主元,是重新构造出来的k维特征,而不是简单地从n维特征中去除其余n-k维特征。

         PCA计算过程:

    假设我们得到的2维数据如下:

     bubuko.com,布布扣

    行代表了样例,列代表特征,这里有10个样例,每个样例两个特征。可以这样认为,有10篇文档,x是10篇文档中“learn”出现的TF-IDF,y是10篇文档中“study”出现的TF-IDF。

  第一步分别求x和y的平均值,然后对于所有的样例,都减去对应的均值。这里x的均值是1.81,y的均值是1.91,那么一个样例减去均值后即为(0.69,0.49),得到

     bubuko.com,布布扣

     第二步,求特征协方差矩阵,如果数据是3维,那么协方差矩阵是

     bubuko.com,布布扣

     这里只有x和y,求解得

     bubuko.com,布布扣

     对角线上分别是x和y的方差,非对角线上是协方差。协方差是衡量两个变量同时变化的变化程度。协方差大于0表示x和y若一个增,另一个也增;小于0表示一个增,一个减。如果x和y是统计独立的,那么二者之间的协方差就是0;但是协方差是0,并不能说明x和y是独立的。协方差绝对值越大,两者对彼此的影响越大,反之越小。协方差是没有单位的量,因此,如果同样的两个变量所采用的量纲发生变化,它们的协方差也会产生树枝上的变化。

     第三步,求协方差的特征值和特征向量,得到

     bubuko.com,布布扣

     上面是两个特征值,下面是对应的特征向量,特征值0.0490833989对应特征向量为bubuko.com,布布扣,这里的特征向量都归一化为单位向量。

    第四步,将特征值按照从大到小的顺序排序,选择其中最大的k个,然后将其对应的k个特征向量分别作为列向量组成特征向量矩阵。

     这里特征值只有两个,我们选择其中最大的那个,这里是1.28402771,对应的特征向量是bubuko.com,布布扣

     第五步,将样本点投影到选取的特征向量上。假设样例数为m,特征数为n,减去均值后的样本矩阵为DataAdjust(m*n),协方差矩阵是n*n,选取的k个特征向量组成的矩阵为EigenVectors(n*k)。那么投影后的数据FinalData为

     bubuko.com,布布扣

     这里是

     FinalData(10*1) = DataAdjust(10*2矩阵)×特征向量bubuko.com,布布扣

     得到结果是

     bubuko.com,布布扣

     这样,就将原始样例的n维特征变成了k维,这k维就是原始特征在k维上的投影。

具体代码如下:

#include<cv.h>
#include "opencv2/core/core.hpp"
#include "opencv2/contrib/contrib.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/objdetect/objdetect.hpp"
using namespace std;
using namespace cv;

void printMat(CvMat* matric)
{
	int row = matric->rows,col = matric->cols,i,j;
	for(i = 0;i < row;i++)
	{
		for(j = 0;j < col;j++)
		{
			float num = cvGet2D(matric,i,j).val[0];
			cout << num << " ";
		}
		cout << endl;
	}
}
int main()
{
	printf("-------输入原矩阵的行和列-----------\n");
	int row,col,i,j;
	cin >> row >> col;
	float* data = new float [row*col];
	printf("-------输入原矩阵的元素-------------\n");
	for(i = 0;i < col;i++)
	{
		for(j = 0;j < row;j++)cin >> data[j * col + i]; 
	}
	CvMat* inputs = cvCreateMat(row,col,CV_32FC1);
	cvSetData(inputs,data,inputs->step);
	printf("------输入的矩阵为------------------\n");
	printMat(inputs);
	CvMat* means = cvCreateMat(1,col,CV_32FC1);//均值
	CvMat* EigenValue = cvCreateMat(col,1,CV_32FC1);//特征值
	CvMat* EigenVector = cvCreateMat(col,col,CV_32FC1);//特征向量


//	eigen(inputs,EigenValue,EigenVector);

	cvCalcPCA(inputs,means,EigenValue,EigenVector,CV_PCA_DATA_AS_ROW);//以行作为特征维数计算PCA的参数
	printf("-------------均值为-----------------\n");
	printMat(means);
	printf("-------------特征值为---------------\n");
	printMat(EigenValue);
	printf("-------------特征向量为-------------\n");
	printMat(EigenVector);
	printf("-----输入PCA的降维系数,按特征值总和的比例确定PCA的维度,大小在0~1之间------\n");
	float rate;
	cin >> rate;
    float EigenValueSum = cvSum(EigenValue).val[0],curSum = 0.0;
	for(i = 0;i < col;i++)
	{
		curSum += cvGet2D(EigenValue,i,0).val[0];//cvCalcPCA求出的特征值和特征向量都是排好序的,可以直接使用
		if(curSum > EigenValueSum * rate)break;
	}
	int postDim = i;
	cout << "----从原来的 " << col <<" 维降到 " << postDim << " 维"<<endl;
	CvMat* postEigenVector = cvCreateMat(col,postDim,CV_32FC1);//取前k个特征向量组成的投影矩阵
	CvMat sub;
	cvGetSubRect(EigenVector,postEigenVector,cvRect(0,0,postDim,col));
	for(i = 0;i < row;i++)
	{
		cvGetSubRect(inputs,&sub,cvRect(0,i,col,1));
		cvSub(&sub,means,&sub);
	}
	printf("------------投影矩阵为------------\n");
	printMat(postEigenVector);
	printf("-----------原矩阵单位化后为----------\n");
	printMat(inputs);
	CvMat* output = cvCreateMat(row,postDim,CV_32FC1);
	cvmMul(inputs,postEigenVector,output);//cvMul 是多维向量的点乘,cvmMul 是矩阵乘
	printf("-----------PCA处理后的结果为---------\n");
	printMat(output);

	delete[] data;
	cvReleaseMat(&inputs);
	cvReleaseMat(&means);
	cvReleaseMat(&EigenValue);
	cvReleaseMat(&EigenVector);
	cvReleaseMat(&postEigenVector);
	cvReleaseMat(&output);
	return 0;
}
样例测试:

fangjian@fangjian:~/study/code$ ./opencv 
-------输入原矩阵的行和列-----------
10 2
-------输入原矩阵的元素-------------
2.5 0.5 2.2 1.9 3.1 2.3 2 1 1.5 1.1 2.4 0.7 2.9 2.2 3.0 2.7 1.6 1.1 1.6 0.9
------输入的矩阵为------------------
2.5 2.4 
0.5 0.7 
2.2 2.9 
1.9 2.2 
3.1 3 
2.3 2.7 
2 1.6 
1 1.1 
1.5 1.6 
1.1 0.9 
-------------均值为-----------------
1.81 1.91 
-------------特征值为---------------
1.15562 
0.044175 
-------------特征向量为-------------
0.677873 0.735179 
0.735179 -0.677873 
-----输入PCA的降维系数,按特征值总和的比例确定PCA的维度,大小在0~1之间------
0.98
----从原来的 2 维降到 1 维
------------投影矩阵为------------
0.677873 
0.735179 
-----------原矩阵单位化后为----------
0.69 0.49 
-1.31 -1.21 
0.39 0.99 
0.0899999 0.29 
1.29 1.09 
0.49 0.79 
0.19 -0.31 
-0.81 -0.81 
-0.31 -0.31 
-0.71 -1.01 
-----------PCA处理后的结果为---------
0.82797 
-1.77758 
0.992197 
0.27421 
1.6758 
0.912949 
-0.0991095 
-1.14457 
-0.438046 
-1.22382 

注意,输入的降维比例要足够大,至少保证有1维 ,这里使用的是0.98

上面的数据可以认为是learn和study特征融合为一个新的特征叫做LS特征,该特征基本上代表了这两个特征。

    上述过程有个图描述:

     bubuko.com,布布扣

     正号表示预处理后的样本点,斜着的两条线就分别是正交的特征向量(由于协方差矩阵是对称的,因此其特征向量正交),最后一步的矩阵乘法就是将原始样本点分别往特征向量对应的轴上做投影。

     如果取的k=2,那么结果是

     bubuko.com,布布扣

     这就是经过PCA处理后的样本数据,水平轴(上面举例为LS特征)基本上可以代表全部样本点。整个过程看起来就像将坐标系做了旋转,当然二维可以图形化表示,高维就不行了。上面的如果k=1,那么只会留下这里的水平轴,轴上是所有点在该轴的投影。

     这样PCA的过程基本结束。在第一步减均值之后,其实应该还有一步对特征做方差归一化。比如一个特征是汽车速度(0到100),一个是汽车的座位数(2到6),显然第二个的方差比第一个小。因此,如果样本特征中存在这种情况,那么在第一步之后,求每个特征的标准差bubuko.com,布布扣,然后对每个样例在该特征下的数据除以bubuko.com,布布扣

     归纳一下,使用我们之前熟悉的表示方法,在求协方差之前的步骤是:

     bubuko.com,布布扣

     其中bubuko.com,布布扣是样例,共m个,每个样例n个特征,也就是说bubuko.com,布布扣是n维向量。bubuko.com,布布扣是第i个样例的第j个特征。bubuko.com,布布扣是样例均值。bubuko.com,布布扣是第j个特征的标准差。

     整个PCA过程貌似及其简单,就是求协方差的特征值和特征向量,然后做数据转换。但是有没有觉得很神奇,为什么求协方差的特征向量就是最理想的k维向量?其背后隐藏的意义是什么?整个PCA的意义是什么?

 PCA理论基础

     要解释为什么协方差矩阵的特征向量就是k维理想特征,我看到的有三个理论:分别是最大方差理论、最小错误理论和坐标轴相关度理论。这里简单探讨前两种,最后一种在讨论PCA意义时简单概述。

 最大方差理论

     在信号处理中认为信号具有较大的方差,噪声有较小的方差,信噪比就是信号与噪声的方差比,越大越好。如前面的图,样本在横轴上的投影方差较大,在纵轴上的投影方差较小,那么认为纵轴上的投影是由噪声引起的。

因此我们认为,最好的k维特征是将n维样本点转换为k维后,每一维上的样本方差都很大。

     比如下图有5个样本点:(已经做过预处理,均值为0,特征方差归一)

     bubuko.com,布布扣

     下面将样本投影到某一维上,这里用一条过原点的直线表示(前处理的过程实质是将原点移到样本点的中心点)。

     bubuko.com,布布扣

     假设我们选择两条不同的直线做投影,那么左右两条中哪个好呢?根据我们之前的方差最大化理论,左边的好,因为投影后的样本点之间方差最大。

     这里先解释一下投影的概念:

     bubuko.com,布布扣

     红色点表示样例bubuko.com,布布扣,蓝色点表示bubuko.com,布布扣在u上的投影,u是直线的斜率也是直线的方向向量,而且是单位向量。蓝色点是bubuko.com,布布扣在u上的投影点,离原点的距离是bubuko.com,布布扣(即bubuko.com,布布扣或者bubuko.com,布布扣)由于这些样本点(样例)的每一维特征均值都为0,因此投影到u上的样本点(只有一个到原点的距离值)的均值仍然是0。

     回到上面左右图中的左图,我们要求的是最佳的u,使得投影后的样本点方差最大。

     由于投影后均值为0,因此方差为:

     bubuko.com,布布扣

     中间那部分很熟悉啊,不就是样本特征的协方差矩阵么(bubuko.com,布布扣的均值为0,一般协方差矩阵都除以m-1,这里用m)。

     用bubuko.com,布布扣来表示bubuko.com,布布扣bubuko.com,布布扣表示bubuko.com,布布扣,那么上式写作

     bubuko.com,布布扣 

     由于u是单位向量,即bubuko.com,布布扣,上式两边都左乘u得,bubuko.com,布布扣

     即bubuko.com,布布扣

     We got it!bubuko.com,布布扣就是bubuko.com,布布扣的特征值,u是特征向量。最佳的投影直线是特征值bubuko.com,布布扣最大时对应的特征向量,其次是bubuko.com,布布扣第二大对应的特征向量,依次类推。

     因此,我们只需要对协方差矩阵进行特征值分解,得到的前k大特征值对应的特征向量就是最佳的k维新特征,而且这k维新特征是正交的。得到前k个u以后,样例bubuko.com,布布扣通过以下变换可以得到新的样本。

     bubuko.com,布布扣

     其中的第j维就是bubuko.com,布布扣bubuko.com,布布扣上的投影。

     通过选取最大的k个u,使得方差较小的特征(如噪声)被丢弃。

最小平方误差理论:

  bubuko.com,布布扣

     假设有这样的二维样本点(红色点),回顾我们前面探讨的是求一条直线,使得样本点投影到直线上的点的方差最大。本质是求直线,那么度量直线求的好不好,不仅仅只有方差最大化的方法。再回想我们最开始学习的线性回归等,目的也是求一个线性函数使得直线能够最佳拟合样本点,那么我们能不能认为最佳的直线就是回归后的直线呢?回归时我们的最小二乘法度量的是样本点到直线的坐标轴距离。比如这个问题中,特征是x,类标签是y。回归时最小二乘法度量的是距离d。如果使用回归方法来度量最佳直线,那么就是直接在原始样本上做回归了,跟特征选择就没什么关系了。

     因此,我们打算选用另外一种评价直线好坏的方法,使用点到直线的距离d’来度量。

     现在有n个样本点bubuko.com,布布扣,每个样本点为m维(这节内容中使用的符号与上面的不太一致,需要重新理解符号的意义)。将样本点bubuko.com,布布扣在直线上的投影记为bubuko.com,布布扣,那么我们就是要最小化

     bubuko.com,布布扣

     这个公式称作最小平方误差(Least Squared Error)。

     而确定一条直线,一般只需要确定一个点,并且确定方向即可。

     第一步确定点:

     假设要在空间中找一点bubuko.com,布布扣来代表这n个样本点,“代表”这个词不是量化的,因此要量化的话,我们就是要找一个m维的点bubuko.com,布布扣,使得

     bubuko.com,布布扣

     最小。其中bubuko.com,布布扣是平方错误评价函数(squared-error criterion function),假设m为n个样本点的均值:

     bubuko.com,布布扣

     那么平方错误可以写作:

     bubuko.com,布布扣

     后项与bubuko.com,布布扣无关,看做常量,而bubuko.com,布布扣,因此最小化bubuko.com,布布扣时,

     bubuko.com,布布扣 

     bubuko.com,布布扣是样本点均值。

     第二步确定方向:

     我们从bubuko.com,布布扣拉出要求的直线(这条直线要过点m),假设直线的方向是单位向量e。那么直线上任意一点,比如bubuko.com,布布扣就可以用点me来表示

     bubuko.com,布布扣 

     其中bubuko.com,布布扣bubuko.com,布布扣到点m的距离。

     我们重新定义最小平方误差:

     bubuko.com,布布扣

     这里的k只是相当于ibubuko.com,布布扣就是最小平方误差函数,其中的未知参数是bubuko.com,布布扣e

     实际上是求bubuko.com,布布扣的最小值。首先将上式展开:

     bubuko.com,布布扣

     我们首先固定e,将其看做是常量,bubuko.com,布布扣,然后对bubuko.com,布布扣进行求导,得

     bubuko.com,布布扣

     这个结果意思是说,如果知道了e,那么将bubuko.com,布布扣e做内积,就可以知道了bubuko.com,布布扣e上的投影离m的长度距离,不过这个结果不用求都知道。

     然后是固定bubuko.com,布布扣,对e求偏导数,我们先将公式(8)代入bubuko.com,布布扣,得 

     bubuko.com,布布扣

     其中bubuko.com,布布扣 与协方差矩阵类似,只是缺少个分母n-1,我们称之为散列矩阵(scatter matrix)。

     然后可以对e求偏导数,但是e需要首先满足bubuko.com,布布扣,引入拉格朗日乘子bubuko.com,布布扣,来使bubuko.com,布布扣最大(bubuko.com,布布扣最小),令

     bubuko.com,布布扣

     求偏导

     bubuko.com,布布扣

     这里存在对向量求导数的技巧,方法这里不多做介绍。可以去看一些关于矩阵微积分的资料,这里求导时可以将bubuko.com,布布扣看作是bubuko.com,布布扣,将bubuko.com,布布扣看做是bubuko.com,布布扣

     导数等于0时,得

     bubuko.com,布布扣

     两边除以n-1就变成了,对协方差矩阵求特征值向量了。

     从不同的思路出发,最后得到同一个结果,对协方差矩阵求特征向量,求得后特征向量上就成为了新的坐标,如下图:

     bubuko.com,布布扣

     这时候点都聚集在新的坐标轴周围,因为我们使用的最小平方误差的意义就在此。

PCA理论意义:

   PCA将n个特征降维到k个,可以用来进行数据压缩,如果100维的向量最后可以用10维来表示,那么压缩率为90%。同样图像处理领域的KL变换使用PCA做图像压缩。但PCA要保证降维后,还要保证数据的特性损失最小。再看回顾一下PCA的效果。经过PCA处理后,二维数据投影到一维上可以有以下几种情况:

     bubuko.com,布布扣

     我们认为左图好,一方面是投影后方差最大,一方面是点到直线的距离平方和最小,而且直线过样本点的中心点。为什么右边的投影效果比较差?直觉是因为坐标轴之间相关,以至于去掉一个坐标轴,就会使得坐标点无法被单独一个坐标轴确定。

     PCA得到的k个坐标轴实际上是k个特征向量,由于协方差矩阵对称,因此k个特征向量正交。看下面的计算过程。

     假设我们还是用bubuko.com,布布扣来表示样例,m个样例,n个特征。特征向量为ebubuko.com,布布扣表示第i个特征向量的第1维。那么原始样本特征方程可以用下面式子来表示:

     前面两个矩阵乘积就是协方差矩阵bubuko.com,布布扣(除以m后),原始的样本矩阵A是第二个矩阵m*n。

     bubuko.com,布布扣

     上式可以简写为bubuko.com,布布扣

     我们最后得到的投影结果是bubuko.com,布布扣,E是k个特征向量组成的矩阵,展开如下:

     bubuko.com,布布扣

     得到的新的样例矩阵就是m个样例到k个特征向量的投影,也是这k个特征向量的线性组合。e之间是正交的。从矩阵乘法中可以看出,PCA所做的变换是将原始样本点(n维),投影到k个正交的坐标系中去,丢弃其他维度的信息。举个例子,假设宇宙是n维的(霍金说是11维的),我们得到银河系中每个星星的坐标(相对于银河系中心的n维向量),然而我们想用二维坐标去逼近这些样本点,假设算出来的协方差矩阵的特征向量分别是图中的水平和竖直方向,那么我们建议以银河系中心为原点的x和y坐标轴,所有的星星都投影到x和y上,得到下面的图片。然而我们丢弃了每个星星离我们的远近距离等信息。

     bubuko.com,布布扣

总结与讨论:

PCA技术的一大好处是对数据进行降维的处理。我们可以对新求出的“主元”向量的重要性进行排序,根据需要取前面最重要的部分,将后面的维数省去,可以达到降维从而简化模型或是对数据进行压缩的效果。同时最大程度的保持了原有数据的信息。

     PCA技术的一个很大的优点是,它是完全无参数限制的。在PCA的计算过程中完全不需要人为的设定参数或是根据任何经验模型对计算进行干预,最后的结果只与数据相关,与用户是独立的。 
但是,这一点同时也可以看作是缺点。如果用户对观测对象有一定的先验知识,掌握了数据的一些特征,却无法通过参数化等方法对处理过程进行干预,可能会得不到预期的效果,效率也不高。

     bubuko.com,布布扣

     图表 4:黑色点表示采样数据,排列成转盘的形状。 
     容易想象,该数据的主元是bubuko.com,布布扣或是旋转角bubuko.com,布布扣

     如图表 4中的例子,PCA找出的主元将是bubuko.com,布布扣。但是这显然不是最优和最简化的主元。bubuko.com,布布扣之间存在着非线性的关系。根据先验的知识可知旋转角bubuko.com,布布扣是最优的主元(类比极坐标)。则在这种情况下,PCA就会失效。但是,如果加入先验的知识,对数据进行某种划归,就可以将数据转化为以bubuko.com,布布扣为线性的空间中。这类根据先验知识对数据预先进行非线性转换的方法就成为kernel-PCA,它扩展了PCA能够处理的问题的范围,又可以结合一些先验约束,是比较流行的方法。

     有时数据的分布并不是满足高斯分布。如图表 5所示,在非高斯分布的情况下,PCA方法得出的主元可能并不是最优的。在寻找主元时不能将方差作为衡量重要性的标准。要根据数据的分布情况选择合适的描述完全分布的变量,然后根据概率分布式

     bubuko.com,布布扣

     来计算两个向量上数据分布的相关性。等价的,保持主元间的正交假设,寻找的主元同样要使bubuko.com,布布扣。这一类方法被称为独立主元分解(ICA)。

     bubuko.com,布布扣

     图表 5:数据的分布并不满足高斯分布,呈明显的十字星状。 
     这种情况下,方差最大的方向并不是最优主元方向。

     另外PCA还可以用于预测矩阵中缺失的元素。


用opencv实现的PCA算法,非API调用,布布扣,bubuko.com

用opencv实现的PCA算法,非API调用

标签:pca   opencv   图像处理   算法   数据压缩   

原文地址:http://blog.csdn.net/fangjian1204/article/details/38313977

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!