主成分分析
优点:降低数据的复杂性,识别最重要的多个特征。缺点:不一定需要,且可能损失有用信息。适用数据类型:数值型数据。
首先我们讨论PCA背后的一些理论知识,然后介绍如何通过Python的NumPy来实现PCA。
13.2.1 移动坐标轴
考虑一下图13-1中的大量数据点。如果要求我们画出一条直线,这条线要尽可能覆盖这些点,那么最长的线可能是哪条?我做过多次尝试。在图13-1中,3条直线中B最长。在PCA中,我们对数据的坐标进行了旋转,该旋转的过程取决于数据的本身。第一条坐标轴旋转到覆盖数据的最大方差位置,即图中的直线B。数据的最大方差给出了数据的最重要的信息。
在选择了覆盖数据最大差异性的坐标轴之后,我们选择了第二条坐标轴。假如该坐标轴与第一条坐标轴垂直,它就是覆盖数据次大差异性的坐标轴。这里更严谨的说法就是正交(orthogonal)。当然,在二维平面下,垂直和正交是一回事。在图13-1中,直线C就是第二条坐标轴。利用PCA,我们将数据坐标轴旋转至数据角度上的那些最重要的方向。
图13-1 覆盖整个数据集的三条直线,其中直线B最长,并给出了数据集中差异化最大的方向
我们已经实现了坐标轴的旋转,接下来开始讨论降维。坐标轴的旋转并没有减少数据的维度。考虑图13-2,其中包含着3个不同的类别。要区分这3个类别,可以使用决策树。我们还记得决策树每次都是基于一个特征来做决策的。我们会发现,在x轴上可以找到一些值,这些值能够很好地将这3个类别分开。这样,我们就可能得到一些规则,比如当 (X<4)
时,数据属于类别0。如果使用SVM这样稍微复杂一点的分类器,我们就会得到更好的分类面和分类规则,比如当(w0*x + w1*y + b) > 0
时,数据也属于类别0。SVM可能比决策树得到更好的分类间隔,但是分类超平面却很难解释。
通过PCA进行降维处理,我们就可以同时获得SVM和决策树的优点:一方面,得到了和决策树一样简单的分类器,同时分类间隔和SVM一样好。考察图13-2中下面的图,其中的数据来自于上面的图并经PCA转换之后绘制而成的。如果仅使用原始数据,那么这里的间隔会比决策树的间隔更大。另外,由于只需要考虑一维信息,因此数据就可以通过比SVM简单得多的很容易采用的规则进行区分。
图13-2 二维空间的3个类别。当在该数据集上应用PCA时,就可以去掉一维,从而使得该分类问题变得更容易处理
在图13-2中,我们只需要一维信息即可,因为另一维信息只是对分类缺乏贡献的噪声数据。在二维平面下,这一点看上去微不足道,但是如果在高维空间下则意义重大。
我们已经对PCA的基本过程做出了简单的阐述,接下来就可以通过代码来实现PCA过程。前面我曾提到的第一个主成分就是从数据差异性最大(即方差最大)的方向提取出来的,第二个主成分则来自于数据差异性次大的方向,并且该方向与第一个主成分方向正交。通过数据集的协方差矩阵及其特征值分析,我们就可以求得这些主成分的值。
一旦得到了协方差矩阵的特征向量,我们就可以保留最大的N个值。这些特征向量也给出了N个最重要特征的真实结构。我们可以通过将数据乘上这N个特征向量而将它转换到新的空间。
特征值分析
特征值分析是线性代数中的一个领域,它能够通过数据的一般格式来揭示数据的“真实”结构,即我们常说的特征向量和特征值。在等式
Av = λv
中,v 是特征向量, λ是特征值。特征值都是简单的标量值,因此Av = λv
代表的是:如果特征向量v被某个矩阵A左乘,那么它就等于某个标量λ乘以v。幸运的是,NumPy中有寻找特征向量和特征值的模块linalg
,它有eig
方法,该方法用于求解特征向量和特征值。
13.2.2 在NumPy中实现PCA
将数据转换成前N个主成分的伪码大致如下:
去除平均值 计算协方差矩阵 计算协方差矩阵的特征值和特征向量 将特征值从大到小排序 保留最上面的N个特征向量 将数据转换到上述N个特征向量构建的新空间中
建立一个名为pca.py
的文件并将下列代码加入用于计算PCA。
程序清单13-1 PCA算法
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=9999999): meanVals = mean(dataMat, axis=0) #❶ 去平均值 meanRemoved = dataMat - meanVals covMat = cov(meanRemoved, rowvar=0) eigVals,eigVects = linalg.eig(mat(covMat)) eigValInd = argsort(eigVals) #❷ 从小到大对N个值排序 eigValInd = eigValInd[:-(topNfeat+1):-1] redEigVects = eigVects[:,eigValInd] #❸ 将数据转换到新空间 lowDDataMat = meanRemoved * redEigVects reconMat = (lowDDataMat * redEigVects.T) + meanVals return lowDDataMat, reconMat
程序清单13-1中的代码包含了通常的NumPy导入和loadDataSet
函数。这里的loadDataSet
函数和前面章节中的版本有所不同,因为这里使用了两个list comprehension来构建矩阵。
pca
函数有两个参数:第一个参数是用于进行PCA操作的数据集,第二个参数topNfeat
则是一个可选参数,即应用的N个特征。如果不指定topNfeat
的值,那么函数就会返回前9 999 999个特征,或者原始数据中全部的特征。
首先计算并减去原始数据集的平均值❶。然后,计算协方差矩阵及其特征值,接着利用argsort
函数对特征值进行从小到大的排序。根据特征值排序结果的逆序就可以得到topNfeat
个最大的特征向量❷。这些特征向量将构成后面对数据进行转换的矩阵,该矩阵则利用N个特征将原始数据转换到新空间中❸。最后,原始数据被重构后返回用于调试,同时降维之后的数据集也被返回了。
一切都看上去不错,是不是?在进入规模更大的例子之前,我们先看看上面代码的运行效果以确保其结果正确无误。
>>> import pca
我们在testSet.txt
文件中加入一个由1000个数据点组成的数据集,并通过如下命令将该数据集调入内存:
>>> dataMat = pca.loadDataSet(/'testSet.txt/')
于是,我们就可以在该数据集上进行PCA操作:
>>> lowDMat, reconMat = pca.pca(dataMat, 1)
lowDMat
包含了降维之后的矩阵,这里是个一维矩阵,我们通过如下命令进行检查:
>>> shape(lowDMat)(1000, 1)
我们可以通过如下命令将降维后的数据和原始数据一起绘制出来:
>>> import matplotlib>>>import matplotlib.pyplot as pltfig = plt.figureax = fig.add_subplot(111)>>> ax.scatter(dataMat[:,0].flatten.A[0], dataMat[:,1].flatten.A[0],marker=/'^/', s=90)<matplotlib.collections.PathCollection object at 0x029B5C50>>>> ax.scatter(reconMat[:,0].flatten.A[0], reconMat[:,1].flatten.A[0],marker=/'o/', s=50, c=/'red/')<matplotlib.collections.PathCollection object at 0x0372A210>plt.show
我们应该会看到和图13-3类似的结果。使用如下命令来替换原来的PCA调用,并重复上述过程:
>>> lowDMat, reconMat = pca.pca(dataMat, 2)
既然没有剔除任何特征,那么重构之后的数据会和原始的数据重合。我们也会看到和图13-3类似的结果(不包含图13-3中的直线)。
图13-3 原始数据集(三角形点表示)及第一主成分(圆形点表示)