您的位置:首页 > 其它

PCA,SVD

2017-04-12 22:57 113 查看


前言:

PCA(principal component analysis)和SVD(Singular value decomposition)是两种常用的降维方法,在机器学习等领域有广泛的应用。本文主要介绍这两种方法之间的区别和联系。


一、PCA:



图1.寻找主成分方向

PCA的中文名叫做主成分分析,是降维和去噪的一种重要方法。PCA选取包含信息量最多的方向对数据进行投影。其投影方向可以从最大化方差或者最小化投影误差两个角度理解(详细推导见机器学习圣经PRML)。假设有n×d矩阵X,每一行是一个d维样本xi,寻找投影方向vj以最大化投影方差:

maxvj1n∑i=1n(xivj−x¯)⊤(xivj−x¯)=v⊤jCvj,s.t.v⊤j⋅vj=1



图2.X’X

其中x¯是均值,为了简化公式,本文假设X已经进行过零均值化处理,即x¯=0;vj是数据的投影方向。d×d 协方差矩阵C=1n∑ni=1(xi)⊤(xi)=1nX⊤X。由于C是实对称矩阵,可以进行对角化处理:

C=VLV⊤

d×d正交矩阵V的每一列是特征向量,d×d矩阵L对角线上的每一个元素是特征值,且特征值按递减顺序排列。把C 代回式子v⊤jCvj:

v⊤jCvj=v⊤jVLV⊤vj=λj

λj是特征向量vj对应的特征值。可以发现当投影方向是C的最大特征值对应的特征向量时,投影方向上数据的方差最大。所以用PCA进行降维时通常选取较大特征值对应的特征向量作为投影方向:XVk,Vk是最大的k个特征值对应的特征向量矩阵。


二、SVD:

如果对X做奇异值矩阵分解(SVD分解):

X=USV⊤

对角阵S对角线上的元素是奇异值,U和V是正交矩阵:U⊤U=I,V⊤V=I。把X的奇异值分解代入协方差矩阵:

C=1nX⊤X=1nVS⊤U⊤USV⊤=VS2nV⊤

d×d正交矩阵V的每一列是特征向量,不难发现特征值与奇异值之间存在着对应关系λi=S2ii/n。对X主成分方向进行投影:

XVk=USV⊤Vk=UkSk

Uk包含U的前k列,Sk包含S左上角的k×k个元素。


三、区别与联系:


SVD另一个方向上的主成分

SVD可以获取另一个方向上的主成分,而PCA只能获得单个方向上的主成分:

1nXX⊤=1nUSV⊤VS⊤U⊤=US2nU⊤


SVD计算伪逆

求解矩阵的最小二乘问题需要求伪逆,使用SVD可以很容易得到矩阵X的伪逆:

X+=VS−1U⊤


LSI

隐语义索引(Latent semantic indexing,简称LSI)通常建立在SVD的基础上,通过低秩逼近达到降维的目的。

Xk=minArank(A)=k∥X−A∥

注意到PCA也能达到降秩的目的,但是PCA需要进行零均值化,且丢失了矩阵的稀疏性。


数值稳定性

通过SVD可以得到PCA相同的结果,但是SVD通常比直接使用PCA更稳定。因为PCA需要计算X⊤X的值,对于某些矩阵,求协方差时很可能会丢失一些精度。例如Lauchli矩阵:

X=⎡⎣⎢⎢⎢1e0010e0100e⎤⎦⎥⎥⎥

在Lauchli矩阵里,e是很小的数,e2无法用计算机精确表示,从而计算X⊤X会丢失e这部分信息。


四、参考资料

[1] Pattern Recognition and Machine Learning

[2] Mathematics Stack Exchange:http://math.stackexchange.com/questions/3869/what-is-the-intuitive-relationship-between-svd-and-pca

[3] Cross Validated:http://stats.stackexchange.com/questions/134282/relationship-between-svd-and-pca-how-to-use-svd-to-perform-pca

作者:朱正超

链接:https://www.zhihu.com/question/24283387/answer/124338094

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

协方差就是这样一种用来度量两个随机变量关系的统计量;从协方差可以引出“相关系数”的定义。协方差矩阵是一个对称的矩阵,而且对角线是各个维度上的方差。协方差矩阵计算的是不同维度之间的协方差,而不是不同样本之间的。

对于机器学习领域的PCA来说,如果遇到的矩阵不是方阵,需要计算他的协方差矩阵来进行下一步计算,因为协方差矩阵一定是方阵,而特征值分解针对的必须是方阵,svd针对的可以是非方阵情况。

本文将分别介绍特征值分解、奇异值分解、及PCA的相关理论概念。
文章末尾将给出Householder矩阵变换、QR算法求解特征值、特征向量的代码
其中,特征值分解、奇异值分解的相关内容,转载自:
http://www.cnblogs.com/LeftNotEasy/archive/2011/01/19/svd-and-applications.html
考虑到本文50%以上的部分不是那个哥们的博客原文,所以我搞成原创标题了。。。。

一、特征值与特征向量的几何意义

1. 矩阵乘法
在介绍特征值与特征向量的几何意义之前,先介绍矩阵乘法的几何意义。
矩阵乘法对应了一个变换,是把任意一个向量变成另一个方向或长度的新向量。在这个变化过程中,原向量主要发生旋转、伸缩的变化。如果矩阵对某些向量只发生伸缩变换,不产生旋转效果,那么这些向量就称为这个矩阵的特征向量,伸缩的比例就是特征值。
比如:

,它对应的线性变换是下面的形式形式:



因为,这个矩阵乘以一个向量(x,y)的结果是:

。由于矩阵M是对称的,所以这个变换是一个对 x , y 轴的一个拉伸变换。【当M中元素值大于1时,是拉伸;当值小于1时,是缩短】

那么如果矩阵M不是对称的,比如:

,它所描述的变换如下图所示:



这其实是在平面上对一个轴进行的拉伸变换【如蓝色箭头所示】,在图中蓝色箭头是一个最主要的变化方向。变化方向可能有不止一个,但如果我们想要描述好一个变换,那我们就描述好这个变换主要的变化方向就好了。
2. 特征值分解与特征向量

如果说一个向量v是方阵A的特征向量,将一定可以表示成下面的形式:



λ为特征向量 v 对应的特征值。特征值分解是将一个矩阵分解为如下形式:



其中,Q是这个矩阵A的特征向量组成的矩阵,Σ是一个对角矩阵,每一个对角线元素就是一个特征值,里面的特征值是由大到小排列的,这些特征值所对应的特征向量就是描述这个矩阵变化方向(从主要的变化到次要的变化排列)。也就是说矩阵A的信息可以由其特征值和特征向量表示。
对于矩阵为高维的情况下,那么这个矩阵就是高维空间下的一个线性变换。可以想象,这个变换也同样有很多的变换方向,我们通过特征值分解得到的前N个特征向量,那么就对应了这个矩阵最主要的N个变化方向。我们利用这前N个变化方向,就可以近似这个矩阵(变换)。
总结一下,特征值分解可以得到特征值与特征向量,特征值表示的是这个特征到底有多重要,而特征向量表示这个特征是什么。不过,特征值分解也有很多的局限,比如说变换的矩阵必须是方阵。

二、奇异值分解

1. 奇异值
特征值分解是一个提取矩阵特征很不错的方法,但是它只是对方阵而言的,在现实的世界中,我们看到的大部分矩阵都不是方阵,比如说有N个学生,每个学生有M科成绩,这样形成的一个N * M的矩阵就不可能是方阵,我们怎样才能描述这样普通的矩阵呢的重要特征呢?奇异值分解可以用来干这个事情,奇异值分解是一个能适用于任意的矩阵的一种分解的方法:
分解形式:


假设A是一个M * N的矩阵,那么得到的U是一个M * M的方阵(称为左奇异向量),Σ是一个M * N的矩阵(除了对角线的元素都是0,对角线上的元素称为奇异值),VT(V的转置)是一个N * N的矩阵(称为右奇异向量)。

2. 奇异值与特征值
那么奇异值和特征值是怎么对应起来的呢?我们将一个矩阵A的转置乘以 A,并对(AAT)求特征值,则有下面的形式:



这里V就是上面的右奇异向量,另外还有:



这里的σ就是奇异值,u就是上面说的左奇异向量。【证明那个哥们也没给】

奇异值σ跟特征值类似,在矩阵Σ中也是从大到小排列,而且σ的减少特别的快,在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的99%以上了。也就是说,我们也可以用前r( r远小于m、n )个的奇异值来近似描述矩阵,即部分奇异值分解:



右边的三个矩阵相乘的结果将会是一个接近于A的矩阵,在这儿,r越接近于n,则相乘的结果越接近于A。

三、PCA主成份分析
主成分分析(PrincipalComponents Analysis。即PCA,也称为K-L变换),是图像压缩中的一种最优正交变换。PCA用于统计特征提取构成了子空间法模式识别的基础。它从图像整体代数特征出发,基于图像的总体信息进行分类识别。PCA的核心思想是利用较少数量的特征对样本进行描述以达到降低特征空间维数的目的。

1. PCA理论
给定一副N*N大小图像,将它表示成一个N2*1维向量,向量中元素为像素点灰度,按行存储,如下列公式分别表示第i张图片和n张图片平均值:



令N2*n矩阵X为:



注意,矩阵减去平均值相当于将坐标系原点移动到平均值位置。

设Q=XXT,则Q是一个N2* N2矩阵:



,Q是方阵
,Q是对称矩阵。
,Q被成为协方差矩阵,
,Q的数据量非常庞大
那么,X中的每个元素xj可以被如下表达:



其中,ei是Q中非零特征值对应的特征向量。由特征向量e1,e2,…,en组成的空间叫做张成特征空间。对于N*N图像,e1,e2,…,en是N2*1维相互正交的向量。尺度gji是xj在空间中的坐标。

2. 实现PCA
为了降维,可以对特征值设定阈值或按照其他准则,寻找协方差矩阵Q中前k个特征向量。这里Q十分庞大,对于一副256*256的图像,Q的大小为65536*65536!替代方案是,考虑矩阵



.P和Q都是对称矩阵
.P≠QT
.Q的大小是N2*N2,而P大小为n*n
.n为训练样本图像数量,通常n<<N
设e是矩阵P的特征值λ对应的特征向量,则有:



这里,X*e也是矩阵Q的特征值λ对应的特征向量【这是用求特征值分解方法,下面介绍用SVD方法】

3. PCA与奇异值分解SVD

任何一个m*n矩阵都能进行奇异值分解,拆分为3个矩阵相乘的形式。由于SVD得出的奇异向量也是从奇异值由大到小排列的,按PCA的观点来看,就是方差最大的坐标轴就是第一个奇异向量,方差次大的坐标轴就是第二个奇异向量…。我们可以对Q进行奇异值分解。



.U就是QQT的特征向量
.V就是QTQ的特征向量
.D中奇异值的平方就是QQT和QTQ的特征值

=======================================================================================================================
上面讲了一大堆,就是为了下一篇PCA人脸识别做铺垫的,给你一副图像,要从图像库中得到匹配的图像,怎么弄?如果是两两做像素点比较是不可能完成的任务,时间上废掉了。如果用其他特征点代替也许可以,但容易漏检吧,这边不扩展。我们必须对图像数据的协方差矩阵进行降维,所以用到了PCA。
而具体如何实现PCA呢?关键是特征值及相应特征向量的求取。matlab有个eig函数,OpenCV也有相应的函数。由于不想被别人牵制,我自己查了资料,发现QR算法可以用来求实对称矩阵的全部特征值和特征向量。【雅可比算法也可以,就是速度太慢了;而上面介绍的SVD实现PCA还没见过,文献上说SVD和PCA是等价的】
=======================================================================================================================
以下内容,来自《C常用算法程序集第二版》,这绝对是搞科研的好书!
在用QR算法求解特征值和向量之前,必须将实对称矩阵转化为三对角矩阵。【由于我们的协方差矩阵是实对称矩阵,因此不用转化为Hessen berg矩阵,QR算法是一个迭代的过程,具体算法太长了,我不贴出来了,有需要的,自己去下载这本书的PDF文档或其他资料】
1.约化对称矩阵为三对角矩阵的Householder变换法:







例:



【其他高维矩阵也行,大家可以把数据存在txt文本中,然后读取进来】
代码:

[cpp] view
plain copy







// HouseHolder_Transform.cpp : 定义控制台应用程序的入口点。

//

#include "stdafx.h"

#include "math.h"

void cstrq(double a[],int n,double q[],double b[],double c[]);

int _tmain(int argc, _TCHAR* argv[])

{

int i,j;

static double b[5],c[5],q[25];

static double a[25] = {10.0,1.0,2.0,3.0,4.0,1.0,9.0,-1.0,2.0,-3.0,2.0,-1.0,7.0,3.0,-5.0,3.0,2.0,3.0,12.0,-1.0,4.0,-3.0,-5.0,-1.0,15.0};

cstrq(a,5,q,b,c);

printf("MAT A is:\n");

for (i=0;i<5;i++)

{

for (j=0;j<5;j++)

{

printf("%13.7e ",a[i*5+j]);

}

printf("\n");

}

printf("\n");

printf("MAT Q is:\n");

for (i=0;i<5;i++)

{

for (j=0;j<5;j++)

{

printf("%13.7e ",q[i*5+j]);

}

printf("\n");

}

printf("\n");

printf("MAT B is:\n");

for (i=0;i<5;i++)

{

printf("%13.7e ",b[i]);

}

printf("\n\n");

printf("MAT C is:\n");

for (i=0;i<5;i++)

{

printf("%13.7e ",c[i]);

}

printf("\n\n");

return 0;

}

void cstrq(double a[],int n,double q[],double b[],double c[])

{

int i,j,k,u,v;

double h,f,g,h2;

for (i=0; i<=n-1; i++)

for (j=0; j<=n-1; j++)

{ u=i*n+j; q[u]=a[u];}

for (i=n-1; i>=1; i--)

{ h=0.0;

if (i>1)

for (k=0; k<=i-1; k++)

{ u=i*n+k; h=h+q[u]*q[u];}

if (h+1.0==1.0)

{ c[i]=0.0;

if (i==1) c[i]=q[i*n+i-1];

b[i]=0.0;

}

else

{ c[i]=sqrt(h);

u=i*n+i-1;

if (q[u]>0.0) c[i]=-c[i];

h=h-q[u]*c[i];

q[u]=q[u]-c[i];

f=0.0;

for (j=0; j<=i-1; j++)

{ q[j*n+i]=q[i*n+j]/h;

g=0.0;

for (k=0; k<=j; k++)

g=g+q[j*n+k]*q[i*n+k];

if (j+1<=i-1)

for (k=j+1; k<=i-1; k++)

g=g+q[k*n+j]*q[i*n+k];

c[j]=g/h;

f=f+g*q[j*n+i];

}

h2=f/(h+h);

for (j=0; j<=i-1; j++)

{ f=q[i*n+j];

g=c[j]-h2*f;

c[j]=g;

for (k=0; k<=j; k++)

{ u=j*n+k;

q[u]=q[u]-f*c[k]-g*q[i*n+k];

}

}

b[i]=h;

}

}

for (i=0; i<=n-2; i++) c[i]=c[i+1];

c[n-1]=0.0;

b[0]=0.0;

for (i=0; i<=n-1; i++)

{ if ((b[i]!=0.0)&&(i-1>=0))

for (j=0; j<=i-1; j++)

{ g=0.0;

for (k=0; k<=i-1; k++)

g=g+q[i*n+k]*q[k*n+j];

for (k=0; k<=i-1; k++)

{ u=k*n+j;

q[u]=q[u]-g*q[k*n+i];

}

}

u=i*n+i;

b[i]=q[u]; q[u]=1.0;

if (i-1>=0)

for (j=0; j<=i-1; j++)

{ q[i*n+j]=0.0; q[j*n+i]=0.0;}

}

return;

}

计算结果:



即上述计算结果返回的三对角阵T为:



2.下面,我们将在三对角矩阵的基础上使用QR算法计算全部特征值和特征向量







例,同样对上面那个5阶矩阵,先求三对角矩阵,再求其全部特征值和特征向量
最大迭代次数为60,误差为0.000001
代码:

[cpp] view
plain copy







#include "stdafx.h"

#include "math.h"

void cstrq(double a[],int n,double q[],double b[],double c[]);

int csstq(int n,double b[],double c[],double q[],double eps,int l);

int _tmain(int argc, _TCHAR* argv[])

{

int i,j,k,l=60;

static double b[5],c[5],q[25];

static double a[25] = {10.0,1.0,2.0,3.0,4.0,1.0,9.0,-1.0,2.0,-3.0,2.0,-1.0,7.0,3.0,-5.0,3.0,2.0,3.0,12.0,-1.0,4.0,-3.0,-5.0,-1.0,15.0};

double eps = 0.000001;

cstrq(a,5,q,b,c);

k = csstq(5,b,c,q,eps,l);

printf("MAT A is:\n");

for (i=0;i<5;i++)

{

for (j=0;j<5;j++)

{

printf("%13.7e ",a[i*5+j]);

}

printf("\n");

}

printf("\n");

printf("MAT B is:\n");

for (i=0;i<5;i++)

{

printf("%13.7e ",b[i]);

}

printf("\n\n");

printf("MAT Q is:\n");

for (i=0;i<5;i++)

{

for (j=0;j<5;j++)

{

printf("%13.7e ",q[i*5+j]);

}

printf("\n");

}

printf("\n");

return 0;

}

void cstrq(double a[],int n,double q[],double b[],double c[])

{

int i,j,k,u,v;

double h,f,g,h2;

for (i=0; i<=n-1; i++)

for (j=0; j<=n-1; j++)

{ u=i*n+j; q[u]=a[u];}

for (i=n-1; i>=1; i--)

{ h=0.0;

if (i>1)

for (k=0; k<=i-1; k++)

{ u=i*n+k; h=h+q[u]*q[u];}

if (h+1.0==1.0)

{ c[i]=0.0;

if (i==1) c[i]=q[i*n+i-1];

b[i]=0.0;

}

else

{ c[i]=sqrt(h);

u=i*n+i-1;

if (q[u]>0.0) c[i]=-c[i];

h=h-q[u]*c[i];

q[u]=q[u]-c[i];

f=0.0;

for (j=0; j<=i-1; j++)

{ q[j*n+i]=q[i*n+j]/h;

g=0.0;

for (k=0; k<=j; k++)

g=g+q[j*n+k]*q[i*n+k];

if (j+1<=i-1)

for (k=j+1; k<=i-1; k++)

g=g+q[k*n+j]*q[i*n+k];

c[j]=g/h;

f=f+g*q[j*n+i];

}

h2=f/(h+h);

for (j=0; j<=i-1; j++)

{ f=q[i*n+j];

g=c[j]-h2*f;

c[j]=g;

for (k=0; k<=j; k++)

{ u=j*n+k;

q[u]=q[u]-f*c[k]-g*q[i*n+k];

}

}

b[i]=h;

}

}

for (i=0; i<=n-2; i++) c[i]=c[i+1];

c[n-1]=0.0;

b[0]=0.0;

for (i=0; i<=n-1; i++)

{ if ((b[i]!=0.0)&&(i-1>=0))

for (j=0; j<=i-1; j++)

{ g=0.0;

for (k=0; k<=i-1; k++)

g=g+q[i*n+k]*q[k*n+j];

for (k=0; k<=i-1; k++)

{ u=k*n+j;

q[u]=q[u]-g*q[k*n+i];

}

}

u=i*n+i;

b[i]=q[u]; q[u]=1.0;

if (i-1>=0)

for (j=0; j<=i-1; j++)

{ q[i*n+j]=0.0; q[j*n+i]=0.0;}

}

return;

}

int csstq(int n,double b[],double c[],double q[],double eps,int l)

{

int i,j,k,m,it,u,v;

double d,f,h,g,p,r,e,s;

c[n-1]=0.0; d=0.0; f=0.0;

for (j=0; j<=n-1; j++)

{ it=0;

h=eps*(fabs(b[j])+fabs(c[j]));

if (h>d) d=h;

m=j;

while ((m<=n-1)&&(fabs(c[m])>d)) m=m+1;

if (m!=j)

{ do

{ if (it==l)

{ printf("fail\n");

return(-1);

}

it=it+1;

g=b[j];

p=(b[j+1]-g)/(2.0*c[j]);

r=sqrt(p*p+1.0);

if (p>=0.0) b[j]=c[j]/(p+r);

else b[j]=c[j]/(p-r);

h=g-b[j];

for (i=j+1; i<=n-1; i++)

b[i]=b[i]-h;

f=f+h; p=b[m]; e=1.0; s=0.0;

for (i=m-1; i>=j; i--)

{ g=e*c[i]; h=e*p;

if (fabs(p)>=fabs(c[i]))

{ e=c[i]/p; r=sqrt(e*e+1.0);

c[i+1]=s*p*r; s=e/r; e=1.0/r;

}

else

{ e=p/c[i]; r=sqrt(e*e+1.0);

c[i+1]=s*c[i]*r;

s=1.0/r; e=e/r;

}

p=e*b[i]-s*g;

b[i+1]=h+s*(e*g+s*b[i]);

for (k=0; k<=n-1; k++)

{ u=k*n+i+1; v=u-1;

h=q[u]; q[u]=s*q[v]+e*h;

q[v]=e*q[v]-s*h;

}

}

c[j]=s*p; b[j]=e*p;

}

while (fabs(c[j])>d);

}

b[j]=b[j]+f;

}

for (i=0; i<=n-1; i++)

{ k=i; p=b[i];

if (i+1<=n-1)

{ j=i+1;

while ((j<=n-1)&&(b[j]<=p))

{ k=j; p=b[j]; j=j+1;}

}

if (k!=i)

{ b[k]=b[i]; b[i]=p;

for (j=0; j<=n-1; j++)

{ u=j*n+i; v=j*n+k;

p=q[u]; q[u]=q[v]; q[v]=p;

}

}

}

return(1);

}

这里,又把householder贴了一遍。。。

计算结果:



这里,我们要注意:
数组q中第j列为数组b中第j个特征值对应的特征向量
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: