主成分分析(PCA)是一种基于变量协方差矩阵对数据进行压缩降维、去噪的有效方法,PCA的思想是将n维特征映射到k维上(k<n),这k维特征称为主元,是旧特征的线性组合,这些线性组合最大化样本方差,尽量使新的k个特征互不相关。给定 n 个变量的 m 个观察值,形成一个 n *m 的数据矩阵,n通常比较大。对于一个由多个变量描述的复杂事物,认识难度会很大,于是我们可以抓住事物主要方面进行重点分析,如果事物的主要方面刚好体现在几个主要变量上,那么我们只需要将体现事物主要方面的较少的几个主要变量分离出来,对此进行详细分析。但是,在一般情况下,并不能直接找出这样的关键变量。这时我们可以用原有变量的线性组合来表示事物的主要方面, PCA 就是这样一种分析方法。PCA主要用于数据降维,对于由一系列特征组成的多维向量,其中某些元素本身没有区分性,比如某个元素在所有的样本中都相等,或者彼此差距不大,那么这个元素本身就没有区分性,如果用它做特征来区分,贡献会非常小。所以我们的目的是找那些变化大的元素,即方差大的那些维,而去除掉那些变化不大的维,从而使特征留下的都是“精品”,使得计算量也相应变小。 对于一个k维的特征来说,相当于它的每一维特征与其他维都是正交的(相当于在多维坐标系中,坐标轴都是垂直的),那么我们可以变化这些维的坐标系,从而使这个特征在某些维上方差大,而在某些维上方差很小。例如,一个45度倾斜的椭圆,在第一坐标系,如果按照x,y坐标来投影,这些点的x和y的属性很难用于区分他们,因为他们在x,y轴上坐标变化的方差都差不多,我们无法根据这个点的某个x属性或y属性来判断这个点是哪个,而如果将坐标轴旋转,以椭圆长轴为x轴,则椭圆在长轴上的分布比较长,方差大,而在短轴上的分布短,方差小,所以可以考虑只保留这些点的长轴属性,来区分椭圆上的点,这样,区分性比x,y轴的方法要好!
所以我们的做法就是求得一个k维特征的投影矩阵,这个投影矩阵可以将特征从高维降到低维。投影矩阵也可以叫做变换矩阵。新的低维特征必须每个维都正交,特征向量都是正交的。通过求样本矩阵的协方差矩阵,然后求出协方差矩阵的特征向量,这些特征向量就可以构成这个投影矩阵了。特征向量的选择取决于协方差矩阵的特征值的大小。经过 PCA 分析,一个多变量的复杂问题被简化为低维空间的简单问题。可以利用这种简化方法进行作图,形象地表示和分析复杂问题。
PCA过程
第一步,获取数据,下图中的Data为原始数据,一共有两个维度,可看出二维平面上的点。
第二步,减去平均值,对于Data中的每一维数据分别求平均值,并减去平均值,得到DataAdjust数据。
第三步,计算DataAdjust的协方差矩阵
第四步,计算协方差矩阵的特征向量和特征值,选取特征向量
第五步,将样本点投影到选取的特征向量上,得到新的数据集
伪代码如下
减去平均数
计算协方差矩阵
计算协方差矩阵的特征值和特征向量
将特征值从大到小排序
保留最大的K个特征向量
将数据转换到上述K各特征向量构建的新空间中
做法是根据特征值的贡献率选取前p个最大特征向量及其对应的特征向量,贡献率是指选取的特征值的和与占所有特征值的和比,即:
一般取a=99%,即使训练样本在前p个特征向量集上的投影有99%的能量,这前p个特征值对应的特征向量就构成了投影矩阵
python实现
# -*- coding: cp936 -*- import matplotlib import matplotlib.pyplot as plt from numpy import * def loadDataSet(fileName, delim='\t'): fr = open(fileName) stringArr = [line.strip().split(delim) for line in fr.readlines()] datArr = [map(float,line) for line in stringArr] return mat(datArr) def pca(dataMat, topNfeat=999999): meanVals = mean(dataMat, axis=0) DataAdjust = dataMat - meanVals #减去平均值 covMat = cov(DataAdjust, rowvar=0) eigVals,eigVects = linalg.eig(mat(covMat)) #计算特征值和特征向量 #print eigVals #计算协方差矩阵 eigValInd = argsort(eigVals) #对特征值排序 eigValInd = eigValInd[:-(topNfeat+1):-1] #保留最大的前K个特征值 redEigVects = eigVects[:,eigValInd] #对应的特征向量 lowDDataMat = DataAdjust * redEigVects #将数据转换到低维新空间 reconMat = (lowDDataMat * redEigVects.T) + meanVals #重构数据,用于调试 return lowDDataMat, reconMat dataMat = loadDataSet('testSet.txt') lowDMat, reconMat = pca(dataMat,1) print "shape(lowDMat): ",shape(lowDMat) fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(dataMat[:,0].flatten().A[0],dataMat[:,1].flatten().A[0],marker='^',s=90) ax.scatter(reconMat[:,0].flatten().A[0],reconMat[:,1].flatten().A[0],marker='o',s=50,c='red') plt.show()