数据降维——PCA(主成分分析)算法原理

引言

数据降维就是指采用某种映射方法,将高维空间中的数据点映射到低维度的空间中。不同于回归、分类和聚类,降维方法并不是用来做模型预测的,其本身是一种数据预处理方法。

在降维方法中,尤为重要的是:被抽取出的维度表示应该仍能捕捉大部分的原始数据的变化和结构。换句话说,降维的目的就是为了排除数据中的噪音并保留数据原有的隐含结构。有时候,原始数据的维度太高,若此时直接使用分类、回归等方法进行机器学习建模将非常困难,因为需要拟合的参数数目远大于训练样本的数目,这时就需要先将原始数据进行降维处理。其中PCA是最为常见的线性降维方法,下面就一起来学习PCA如何实现降维吧。


一、PCA概述

1.1 基本思想

主成分分析PCA(Principal Component Analysis),是最常用的一种降维方法。基本思想:通过某种线性投影,将高维数据映射到低维的空间中表示。降维过程主要在于抽取高维矩阵中的k个主向量(主成分),这k个主向量代表了输入数据可能的最大变化,最终使用映射的方式做了一次降维,同时也保证最大程度保留了原数据的结构和信息

换句话说,PCA算法目的是为了保留那k个最有代表性的特征,剔除了噪声和那些不重要的特征,实现对原数据的降维。

1.2 算法步骤

先对算法步骤总体阐述一下,后面为每一步都进行讲解说明:

(1)输入n样本矩阵X

(2)对样本矩阵去中心化,即每一位数据减去对应列的均值。

(3)计算得到矩阵X协方差矩阵C

(4)用特征值分解方法求协方差矩阵C特征值与特征向量

(5)对特征值从大到小排序,选择其中最大的k个。将其对应的k个特征向量分别作为行向量,组成特征向量矩阵P

(6)将样本矩阵X投影到以P为基的新空间中,即Y=PX,便可以将n维数据降低成k维数据。

二、算法详解

2.1 样本矩阵

输入的样本矩阵X就是我们需要降维处理的m\times n维矩阵,表示为:

                                                                       X_{m\times n}=\begin{bmatrix} x_{11} &x_{12} & \cdots &x_{1n} \\ x_{21}& x_{22} & \cdots & x_{2n}\\ \vdots &\vdots& \ddots &\vdots\\ x_{m1}& x_{m2} & \cdots & x_{mn} \end{bmatrix}

m行代表第m个样本,第n列代表第n维成分。

 2.2 去中心化

假设\bar x_{i}是第i维的均值,即\mu_{i}=\frac{1}{m}\sum_{j=1}^{m}x_{ji},例如第1维的均值为\mu _{1}=\frac{1}{m}(x_{11}+x_{21}+\cdots +x_{m1})去中心化是让每一个数据都减去所在维度的均值,因此去中心化的样本矩阵X可表示为:

                                                X=\begin{bmatrix} x_{11}- \mu_{1} &x_{12}- \mu_{2} & \cdots &x_{1n} - \mu_{n}\\ x_{21}- \mu_{1}& x_{22}- \mu_{2} & \cdots & x_{2n} - \mu_{n}\\ \vdots &\vdots& \ddots &\vdots\\ x_{m1}- \mu_{1}& x_{m2}- \mu_{2} & \cdots & x_{mn} - \mu_{n} \end{bmatrix} =\begin{bmatrix} x_{1} &x_{2} & \cdots &x_n \end{bmatrix}

问:为何要去中心化?

答:我们知道计算两个维度XY之间的协方差为:Cov(X,Y)=E[(X-E[X])(Y-E(Y)],而去中心化x-\mu其实是对应计算了公式中的X-E(X)(数学期望E(X)就是均值),这样做的好处是能够使得后面求解协方差矩阵变得更容易。

2.3 协方差矩阵

关于协方差矩阵详解可参见本人的另一篇博客:PCA(主成分分析)原理涉及到的线性代数理论(二):协方差矩阵

协方差在概率论和统计学中用于衡量两个维度偏离其均值的程度。如果协方差为正值,则说明两者是正相关的,如果为负值就说明是负相关的,如果为0,也就是统计上说的“相互独立”。对于多维矩阵X,就用到协方差矩阵来衡量X内部每每两个维度之间的相关性。也就是说计算矩阵X各个维度两两之间的协方差,得到的这些协方差值构建了一个矩阵,则为协方差矩阵。

对已去中心化的矩阵X,求其对应的协方差矩阵C,可表示为:                        C=\begin{bmatrix} Cov(x_{1},x_{1}) &Cov(x_{1},x_{2}) & \cdots &Cov(x_{1},x_{n})\\Cov(x_{2},x_{1})& Cov(x_{2},x_{2}) & \cdots &Cov(x_{2},x_{n})\\ \vdots &\vdots& \ddots &\vdots\\ Cov(x_{n},x_{1})& Cov(x_{n},x_{2}) & \cdots & Cov(x_{n},x_{n}) \end{bmatrix}_{n\times n} = \begin{bmatrix} \frac{1}{m}\sum_{1}^{m}x_{1}x_{1}& \frac{1}{m}\sum_{1}^{m}x_{1}x_{2}& \cdots&\frac{1}{m}\sum_{1}^{m}x_{1}x_{n} \\ \frac{1}{m}\sum_{1}^{m}x_{2}x_{1}& \frac{1}{m}\sum_{1}^{m}x_{2}x_{2}& \cdots&\frac{1}{m}\sum_{1}^{m}x_{2}x_{n} \\ \vdots &\vdots& \ddots &\vdots \\\frac{1}{m}\sum_{1}^{m}x_{n}x_{1}& \frac{1}{m}\sum_{1}^{m}x_{n}x_{2}& \cdots&\frac{1}{m}\sum_{1}^{m}x_{n}x_{n} \end{bmatrix}

最终可简写为                                                               C=\frac{1}{m}XX^T

公式说明:(1)X已经去中心化,即X的每一个元素已经减去了均值,所以协方差计算直接表示为Cov(x_{i},x_{j})=\frac{1}{m}\sum_{1}^{m}x_{i}x_{j}

                  (2)从上可以看出,协方差矩阵是一个对称矩阵,即以对角线对称的元素两两相等,由此可得XX^T,再乘                                         以\frac{1}{m}即得到了最终的简写式。

协方差矩阵C的主对角线元素为各个维度的方差,除此之外的元素都是协方差(方差可理解为是两个维度一致时的协方差,也就是一维数据内部的协方差。)。

除此之外,因为协方差矩阵是一个实对称矩阵,所以它也有实对称矩阵所拥有的性质,而关于实对称矩阵的性质在后面推算中会用到,这里先埋个伏笔,后面会提及。

至此,协方差矩阵求出来了,但是又有什么意义吗?不急,先让我们来探究一下算法优化目标,看完你就知道为什么要求协方差矩阵了……

2.4 算法优化目标

首先,重申一下算法的最终目的:使n维数据降到了k维数据(n>k),并让降维后的数据最大程度保留原始数据的信息。实现降维无非就是让n维空间的数据投影到以k个特征向量为基(可以理解为常见的x轴、y轴、z轴等)的空间中;而需要最大程度保留原始数据信息的话,关键在于正确地选取这k个基,也就是选择最具代表性的那些特征,剔除没必要的特征,减少噪声。

为此我们需要探究一下如何选择这k个主成分作为新空间的基,将其视为算法接下来的优化目标。

  • 第一个优化目标

先来了解一下何为信息?信息来源于未知,也就是说如果不同样本的同一维度的值差异特别大,那该维度带给我们的信息量就是极大的。转换成数学语言,就是说某维度的方差越大,它的信息量越大

(在信号处理中认为信号具有较大的方差,噪声有较小的方差,而信噪比就是信号与噪声的方差比)

举个例子:假如有3个人的人脸特征数据,他们的3个维度分别为:眼睛、鼻子、嘴巴。如果他们鼻子这一维度的数据都是一样的,如下图中,三个人都是大鼻子(方差=0)。那么我们从鼻子这一维度获得的信息量就是为零,因为无法从该维度得知该人脸图像到底属于谁的。

那如果他们鼻子这一维度的数据各不相同,如下图中,三个人分别是大、中、小鼻子(方差很大)。那么我们从鼻子这一维度获得的信息量就很大了,甚至直接用这一个维度就可以识别出是谁的人脸图像。

用坐标系来说明这个问题:假设二维空间有A、B、C、D、E、F六个数据点,现要求我们将这些数据点降到一维空间中表示,又希望最大程度保留最大信息,要怎么做?正确的做法就是在这个二维平面上选取一个方向(基),让这些数据点投影到这个方向上,最后使得这些投影点尽可能的分散。

在如下左图中:选择了以x轴为方向进行投影,可以看到投影后得到了G、H、I、J四个点。其中B、C两点投影后重叠于H点上,E、F两点投影后重叠于J点上。可以这么理解:投影点过于集中,导致有些点已经重叠了,造成了信息的损失。

而在右图中:选择了y轴与x轴之间的对称线作为投影的方向,可以看到投影后得到了G、H、I、J、K、L六个点,并没有发现重叠的情况,也就是说明投影点比较分散,此时就不造成信息的损失了。

我们知道方差是用来衡量数据的离散程度,方差越大,数据越分散,反之方差越小,数据则越集中。所以右图中投影后的数据点的方差会比左图中的大,保留了更多的信息。那么右图中的投影方向就是我们要选择的那个基,以此来构建降维后的新空间。

   

综合上述,我们可以得到第一个优化目标:①降维后各维度的方差尽可能大。

  • 第二个优化目标

如果某2个维度间存在相关性,那么就可以从一个维度的值推测出另一个维度的值。说明该维度中有一个是多余的,对我们识别特征帮助不大,那自然可以把其中一个维度舍去。

比如说:假如上面例子中嘴巴与鼻子这两个维度之间存在线性关系(相关性为1,完全相关):对于格式:(y)嘴巴、(x)鼻子,都有y=x,x∈[大,中,小]。那么嘴巴这一维度在这里是不是就是冗余的?有它没它都一个样,完全可以通过鼻子的大小推测出嘴巴的大小,进而判断出人的身份。

所以第二个优化目标便是:②保证不同维度相关性为0,也就是使得不同维度间的协方差为0。

假设Cov(x_{1},x_{2})=0,那么x_{1}x_{2}正交。所以为了使得协方差为0,那么每次选择一个新方向时,都要跟上一次选择的方向正交。也就是说选定了第一个基后,选择第二个基时只能在与第一个基正交的方向上选择,选第三个基则需要同时正交于第一个基与第二个基,依次类推得到最后的k个基都相互正交。(三维的正交基还能想象,k维空间很难想象哈哈)

到这里,我们得到了综合的优化目标:将一组n维向量降为k维(n>k),其目标是选择k个单位(模为1)正交基,使得原始数据变换到这组基上后,各维度两两间协方差为0,而各维度的方差则尽可能大(在正交的约束下,取最大的前k个方差)

在上面介绍协方差矩阵时,最后说了:协方差矩阵的主对角线元素为各个维度的方差,除此之外的元素都是维度两两之间的协方差。有没有惊奇的发现,最终优化目标里涉及到的方差和协方差都在协方差矩阵里,所以接下来就是要对协方差矩阵动刀了……

2.5 对角化、特征值分解

对上述最终优化目标,我们可以将其等价为:将协方差矩阵对角化,也就是让协方差矩阵对角线以外的元素化为0。另外一点要满足方差尽可能大,那么对角线元素要从大到小排序 。

有一点可能会容易混淆,所以要特别注意:在优化目标里提到的“使协方差为0”以及“方差尽可能大”,这两点是针对降维后得到的矩阵所对应的协方差矩阵而言的,而不是上面已求的样本矩阵X对应的协方差矩阵C。为此我们接下来要探究的问题,就是如何让这两个协方差矩阵产生联系。

先假设降维后的矩阵为Y,其对应的协方差矩阵为D,根据协方差矩阵C=\frac{1}{m}XX^T,同理可得:D=\frac{1}{m}YY^T

P为选取的那组基按行组成的矩阵(按行来组是为了可以乘以列矩阵),那么Y为样本矩阵X经过P的基变换后得到的矩阵,则Y=PX

结合这两个式子,可得到以下的推导:

                                                                    \begin{align*} D &=\frac{1}{m}YY^T\\ &= \frac{1}{m}PX(PX)^T \\ &= \frac{1}{m}PXX^TP^T\\ &= P\frac{1}{m}XX^TP^T\\ &= PCP^T \end{align*}                                                               

至此,我们找到了两个协方差矩阵CD的关系:D=PCP^T。可以看出,现在我们目标已经变成了求这个变换矩阵P了,并要满足PCP^T是一个对称矩阵(对应上面说的:把优化目标等价为将D对角化),并且其对角边元素从大到小排序。最终抽取P的前k行作为我们要寻找的k个基,由这k个基组成的矩阵去乘以样本矩阵X,就能使得Xn维降到k维了。

那如何去算出这个P呢?还记得介绍协方差矩阵的时候埋了一个伏笔吗?这里就可以运用到了,见下面框内的内容:

上面说了,协方差矩阵是一个实对称矩阵,因此也均有实对称矩阵的性质,如下:

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

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

由这两条性质可知,一个nn列的实对称矩阵一定可以找到n个单位正交特征向量,设这n个特征向量为e_{1},e_{2},\cdots ,e_{n},我们将其按列组成正交矩阵E

                                                                             E=\left \{ e_{1},e_{2},\cdots ,e_{n}\right \}

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

                                                                           E^TCE=\Lambda =\begin{bmatrix} \lambda_{1} &0 &\cdots &0 \\ 0&\lambda_{2} &\cdots & 0\\ \vdots & \vdots & \ddots &\vdots \\ 0& 0 & \cdots &\lambda_{n} \end{bmatrix}

其中\Lambda为对角矩阵,其对角元素为各特征向量对应的特征值。

(关于这部分的证明不赘述,可参考相关线性代数的“实对称矩阵对角化”的内容哦)

OK,对比一下D=PCP^TE^TCE=\Lambda,就可得到我们要求的变换矩阵P

                                                                                    P=E^T

结合上述对E的描述和P=E^T,可知P是协方差矩阵C的特征向量单位化(长度化为1)后按行组成的矩阵,其中每一行都是C的一个特征向量。如果设P按照\Lambda中特征值的从大到小,将特征向量从上到下排列,则用P的前k行组成的矩阵乘以样本矩阵X,就得到了我们需要的降维矩阵Y

既然P都是C的特征向量单位化后组成的矩阵,那么就必要事先对C进行特征值分解求出其特征值和特征向量,这里不作赘述,关于特征值分解的详情如有兴趣请参看本人的另一篇博客:PCA(主成分分析)原理涉及到的线性代数理论(一):特征值与特征向量、特征值分解


三、代码样例(基于Spark MLlib)

随便输点数据:

2 4 6 8 10
5 3 4 1 4
2 8 9 10 7
4 5 7 1 4
1 2 3 4 5

代码如下:

package sparkMllib.dimensionalityReduction
import org.apache.spark.mllib.linalg.{Matrix, Vectors}
import org.apache.spark.mllib.linalg.distributed.RowMatrix
import org.apache.spark.{SparkConf, SparkContext}

object PCADemo {
  def main(args: Array[String]): Unit = {
    val conf=new SparkConf().setMaster("local").setAppName("PCADemo")
    val sc = new SparkContext(conf)

    //将数据构建成矩阵
    val data = sc.textFile("E:\\test\\pca.txt")
    val parsedData = data.map(line => {
      Vectors.dense(line.split(" ").map(_.toDouble))
    })
    val rowMatrix = new RowMatrix(parsedData)

    //提取3个主成分,构建降维后的矩阵
    val pc: Matrix = rowMatrix.computePrincipalComponents(3)
    val matrix = rowMatrix.multiply(pc)

    //打印降维后的数据
    matrix.rows.foreach(println)
  }
}

输出降维后的数据:

可见每一行都从5维数据降到了3维数据。

源码分析:主成分分析的实现代码在RowMatrix中实现。源码如下:

def computePrincipalComponents(k: Int): Matrix = {
    val n = numCols().toInt
    //计算协方差矩阵
    val Cov = computeCovariance().toBreeze.asInstanceOf[BDM[Double]]
    //特征值分解
    val brzSvd.SVD(u: BDM[Double], _, _) = brzSvd(Cov)
    if (k == n) {
      Matrices.dense(n, k, u.data)
    } else {
      Matrices.dense(n, k, Arrays.copyOfRange(u.data, 0, n * k))
    }
  }

这段代码首先会计算样本的协方差矩阵,然后在通过breezesvd方法进行奇异值分解。这里由于协方差矩阵是方阵,所以奇异值分解等价于特征值分解。下面是计算协方差的代码:

 def computeCovariance(): Matrix = {
    val n = numCols().toInt
    checkNumColumns(n)
    val (m, mean) = rows.treeAggregate[(Long, BDV[Double])]((0L, BDV.zeros[Double](n)))(
      seqOp = (s: (Long, BDV[Double]), v: Vector) => (s._1 + 1L, s._2 += v.toBreeze),
      combOp = (s1: (Long, BDV[Double]), s2: (Long, BDV[Double])) =>
        (s1._1 + s2._1, s1._2 += s2._2)
    )
    updateNumRows(m)
    mean :/= m.toDouble
    // We use the formula Cov(X, Y) = E[X * Y] - E[X] E[Y], which is not accurate if E[X * Y] is
    // large but Cov(X, Y) is small, but it is good for sparse computation.
    // TODO: find a fast and stable way for sparse data.
    val G = computeGramianMatrix().toBreeze.asInstanceOf[BDM[Double]]
    var i = 0
    var j = 0
    val m1 = m - 1.0
    var alpha = 0.0
    while (i < n) {
      alpha = m / m1 * mean(i)
      j = i
      while (j < n) {
        val Gij = G(i, j) / m1 - alpha * mean(j)
        G(i, j) = Gij
        G(j, i) = Gij
        j += 1
      }
      i += 1
    }
    Matrices.fromBreeze(G)
  }

如有兴趣,可见本人的另一个博客:Spark PCA降维模型提取人脸特征(scala+python)


引用及参考:

[1] 《工程数学线性代数第六版》

[2] 《Spark机器学习》 [南非]Nick Pentreath著

[3] PCA 原理:为什么用协方差矩阵

[4] PCA的数学原理

(欢迎转载,转载请注明出处)  

猜你喜欢

转载自blog.csdn.net/qq_42267603/article/details/88789793