PCA原理以及python实现(另外说说SVD奇异值分解)

说明:大部分内容摘自这个链接http://blog.codinglabs.org/articles/pca-tutorial.html,由于没联系到博主,若侵权,望谅解,联系我删除,我觉得原文写得非常好所以分享到这里,算法实现部分以及第3点还有少点杂七杂八的是我自己写的。

 

一.基于最大可分性来解释PCA

(1)简单说明

对于原有的数据我们希望降维后能够尽量的离散,这样能保留原有信息,方差可以衡量离散程度,试想如果有的数据点降维后重叠在一起了,信息就丢失了,如果降低到k维,要做的就是,找到k个方向向量使得把原数据投影到每个方向向量后数据的方差最大,这k个方向向量是k维空间的一组标准正交基

(2)详细说明

一般的,如果我们有M个N维向量,想将其变换为由R个N维向量表示的新空间中,那么首先将R个基按行组成矩阵A,然后将向量按列组成矩阵B,那么两矩阵的乘积AB就是变换结果,其中AB的第m列为A中第m列变换后的结果。

数学表示为:

PCA原理以及python实现(另外说说SVD奇异值分解)_第1张图片

其中pi是一个行向量,表示第i个基,aj是一个列向量,表示第j个原始数据记录。

特别要注意的是,这里R可以小于N,而R决定了变换后数据的维数。也就是说,我们可以将一N维数据变换到更低维度的空间中去,变换后的维度取决于基的数量。因此这种矩阵相乘的表示也可以表示降维变换。

接下来最关键的问题:如何选择基才是最优的。或者说,如果我们有一组N维向量,现在要将其降到K维(K小于N),那么我们应该如何选择K个基才能最大程度保留原有的信息?

为了避免过于抽象的讨论,我们仍以一个具体的例子展开。假设我们的数据(已经均值归一化)由五条二维记录组成,将它们表示成矩阵形式:

在坐标轴上如图:

 

PCA原理以及python实现(另外说说SVD奇异值分解)_第2张图片

如何选择这个方向(或者说基)才能尽量保留最多的原始信息呢?一种直观的看法是:希望投影后的投影值尽可能分散。

 

以上图为例,可以看出如果向x轴投影,那么最左边的两个点会重叠在一起,中间的两个点也会重叠在一起,于是本身四个各不相同的二维点投影后只剩下两个不同的值了,这是一种严重的信息丢失,同理,如果向y轴投影最上面的两个点和分布在x轴上的两个点也会重叠。所以看来x和y轴都不是最好的投影选择。我们直观目测,如果向通过第一象限和第三象限的斜线投影,则五个点在投影后还是可以区分的。

现在要做的就是最大化投影后的数据间的方差,由于已经均值归一化了所以方差表示如下:

对于上面二维降成一维的问题来说,找到那个使得方差最大的方向就可以了。不过对于更高维,还有一个问题需要解决。考虑三维降到二维问题。与之前相同,首先我们希望找到一个方向使得投影后方差最大,这样就完成了第一个方向的选择,继而我们选择第二个投影方向。

如果我们还是单纯只选择方差最大的方向,很明显,这个方向与第一个方向应该是“几乎重合在一起”,显然这样的维度是没有用的,因此,应该有其他约束条件。从直观上说,让两个变量尽可能表示更多的原始信息,我们是不希望它们之间存在(线性)相关性的,因为相关性意味着两个变量不是完全独立,必然存在重复表示的信息。

数学上可以用两个变量的协方差表示其相关性,由于已经让每个变量均值为0,则:

可以看到,在变量均值为0的情况下,两个变量的协方差简洁的表示为其内积除以元素数m。

当协方差为0时,表示两个变量完全不相关。为了让协方差为0,我们选择第二个基时只能在与第一个基正交的方向上选择。因此最终选择的两个方向一定是正交的。

至此,我们得到了降维问题的优化目标:将一组N维向量降为K维(K大于0,小于N),其目标是选择K个单位(模为1)正交基,使得原始数据变换到这组基上后,各变量两两间协方差为0,而变量的方差则尽可能大(在正交的约束下,取最大的K个方差)。

接下来就说说应用数学如何解决这个问题:

假设我们只有a和b两个变量,那么我们将它们按行组成矩阵X:

然后我们用X乘以X的转置,并乘上系数1/m:

这个矩阵就是协方差矩阵,对角线上的两个元素分别是两个变量的方差,而其它元素是a和b的协方差。

根据上述推导,我们发现要达到优化目前,等价于将协方差矩阵对角化:即除对角线外的其它元素化为0,并且在对角线上将元素按大小从上到下排列,也就是说哪个特征的方差最大我们就保留它对应的基,这样我们就达到了优化目的。这样说可能还不是很明晰,我们进一步看下原矩阵与基变换后矩阵协方差矩阵的关系:

设原始数据矩阵X对应的协方差矩阵为C,而P是一组基按行组成的矩阵,设Y=PX,则Y为X对P做基变换后的数据。设Y的协方差矩阵为D,我们推导一下D与C的关系:

PCA原理以及python实现(另外说说SVD奇异值分解)_第3张图片

现在事情很明白了!我们要找的P不是别的,而是能让原始协方差矩阵对角化的P。换句话说,优化目标变成了寻找一个矩阵P,满足P*C*PT是一个对角矩阵,并且对角元素按从大到小依次排列,那么P的前K行就是要寻找的基,用P的前K行组成的矩阵乘以X就使得X从N维降到了K维并满足上述优化条件。

由上文知道,协方差矩阵C是一个是对称矩阵,在线性代数上,实对称矩阵有一系列非常好的性质:

1)实对称矩阵不同特征值对应的特征向量必然正交。

2)设特征向量λ重数为r,则必然存在r个线性无关的特征向量对应于λ,因此可以将这r个特征向量单位正交化。

由上面两条可知,一个n行n列的实对称矩阵一定可以找到n个单位正交特征向量,设这n个特征向量为e1,e2,⋯,en,我们将其按列组成矩阵:

则对协方差矩阵C有如下结论:

PCA原理以及python实现(另外说说SVD奇异值分解)_第4张图片

其中Λ为对角矩阵,其对角元素为各特征向量对应的特征值(可能有重复)

以上结论不再给出严格的数学证明,对证明感兴趣的朋友可以参考线性代数书籍关于实对称矩阵对角化的内容。

到这里,我们发现我们已经找到了需要的矩阵P

P是协方差矩阵的特征向量单位化后按行排列出的矩阵,其中每一行都是C的一个特征向量。如果设P按照Λ中特征值的从大到小,将特征向量从上到下排列,则用P的前K行组成的矩阵乘以原始数据矩阵X,就得到了我们需要的降维后的数据矩阵Y

最后再说说pca在做的就是:假如我们有m个n维的样本数据,那么我们做的就是先挑出第一个特征,这组特征投影到一个基向量后方差最大,再挑出第二个特征,投影到一个与之前基向量正交的基向量后方差最大,直到第k个,也就是降维为只有m个k维的样本

总结一下PCA的算法步骤:

设有m条n维数据。

1)将原始数据按列组成n行m列矩阵X

2)将X的每一行(代表一个属性变量)进行零均值化,即减去这一行的均值

3)求出协方差矩阵C=1/mXXT

4)求出协方差矩阵的特征值及对应的特征向量

5)将特征向量按对应特征值大小从上到下按行排列成矩阵,取前k行组成矩阵P

6)Y=PX即为降维到k维后的数据

下面是一个例子:将下面这个矩阵降到一维

直接求协方差矩阵:

PCA原理以及python实现(另外说说SVD奇异值分解)_第5张图片

求解后特征值为:

对应的特征向量分别是:

标准化后特征向量为:

因此我们的矩阵P是:

最后我们用P的第一行乘以数据矩阵,就得到了降维后的表示:

投影后为:

PCA原理以及python实现(另外说说SVD奇异值分解)_第6张图片

(3) 注:降维后保留了原特征的数据的百分之多少(也可称为解释率)可以由下面的公式计算:

一般而言,设λ1,λ2,λ3…λn表示协方差矩阵的特征值(按由大到小顺序排列),使得λj为对应于特征向量uj的特征值。那么如果我们保留前k个成分,则保留的方差百分比可计算为:

二.PCA与SVD

SVD(Singular Value Decomposition奇异值分解)是一种矩阵分解的方法,目的很明确,就是将矩阵A分解为三个矩阵UΣVT的乘积的形式,我们不妨接着PCA的步骤往下做。 我们有A=BVT,我们将B写成归一化的形式,其拆开成B=UΣ,其中U的每一列是单位向量,而Σ=diag{σ1,σ2,⋯,σn}则是B中每个向量的模长且σ1>σ2>⋯>σn。而B中每一列的模长为|Avi|,由于vi是 的特征向量,对应特征值为λi,那么Avi平方为

 = ,这是特征向量的定义

PCA原理以及python实现(另外说说SVD奇异值分解)_第7张图片

所以有|Avi|= ,所以将B写成B=UΣ的形式后,U是正交矩阵,而Σ是一个对角阵,其每一个元素σi= 。 我们再来看SVD的图示:

PCA原理以及python实现(另外说说SVD奇异值分解)_第8张图片

从图中看出,中间的奇异值矩阵由大到小排列,越后面的值对结果的影响越小,因此,如果只保留较大的奇异值和其对应的U、V中的向量,对于矩阵压缩则能起到很好的作用。在该例子中,m>n,能通过得出的ui只有n个,剩下m-n个基直接随便找正交基补全即可,他们在计算中实际上也没什么用。 我们把SVD分解的结果带入协方差矩阵:

可以看出,中间那个U消掉了,这也能部分反映SVD和PCA间的关系。

实际上:

假如我们有m个n维特征的样本,构成矩阵X,PCA就是在对X的协方差矩阵进行特征分解,而SVD直接对X进行分解,SVD不用先求出协方差矩阵

三.为何要引入SVD?

之前我也在想这个问题,既然方阵都能特征分解,而在PCA中我们分解的就是一个协方差方阵,那还使用SVD干嘛?用特征分解实现pca算法时我才知道,对于有的协方差矩阵特征值会是虚数,这种情况算法会失效,而SVD奇异值分解得到的奇异值一定是非负实数,可以解决之前的情况!矩阵的知识不是很扎实,没有详细学过svd,有时候总感觉不明不白的。。。

四.python实现

算法细心一点也挺好写的,但是我没写svd分解的版本,只是简单写了特征分解的版本。

# coding: utf-8

'''
特征向量分解实现PCA算法
如果特征值出现复数该算法会失效,这就引入了SVD,奇异值分解,
奇异值类比于特征值,但是奇异值一定是非负实数,不存在之前的情况
'''

import numpy as np
class PCA(object):
    m = 0
    n = 0
    #降维所需的基向量
    base_vectors = None
    #均值归一化
    def mean_normalization(self,X):
        for j in range(self.n):
            me = np.mean(X[:,j])
            X[:,j] = X[:,j] - me
        return X
    #r为降低到的维数
    def fit(self,X,r):
        self.m = X.shape[0]
        self.n = X.shape[1]
        #均值归一化
        X = self.mean_normalization(X)
        Xt = X.T
        #协方差矩阵
        c = (1/self.m) * Xt.dot(X)
        print(c)
        #求解协方差矩阵的特征向量和特征值
        eigenvalue,featurevector=np.linalg.eig(c)
        #对特征值索引排序 从大到小
        aso = np.argsort(eigenvalue)
        indexs = aso[::-1]
        print("特征值:",eigenvalue)
        print("特征向量:",featurevector)
        print("降为",r,"维")
        eigenvalue_sum = np.sum(eigenvalue)
        self.base_vectors = []
        for i in range(r):
            print("第",indexs[i],"特征的解释率为:",(eigenvalue[indexs[i]] / eigenvalue_sum))
            self.base_vectors.append(featurevector[:,indexs[i]])#取前r个特征值大的特征向量作为基向量
        self.base_vectors = np.array(self.base_vectors)
        return
    
    def transform(self,X):
        #r*n的P乘以n*m的矩阵转置后为m*r的矩阵
        return self.base_vectors.dot(X.T).T
    def fit_transform(self,X,r):
        self.fit(X, r)
        return self.transform(X)
    
#测试
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data.T
pca = PCA()
X_transformed = pca.fit_transform(X, r=1)

你可能感兴趣的:(机器学习)