第十三章 利用PCA来简化数据
降维就是指采用某种映射方法,将原高维空间中的数据点映射到低维度的空间中。在低维空间下,数据更容易进行处理,如果降到三维以下,还可以将数据通过图像显示出来,因为人处在一个三维的空间。
数据显示并非大规模特征下的唯一难题,对数据进行简化还有如下一系列的原因:使得数据集更易使用;降低很多算法的计算开销;去除噪声;使得结果易懂。
目前流行的降维方法有:主成分分析(Principal Component Analysis,PCA)、因子分析(Factor Analysis)、独立成分分析(Independent Component Analysis,ICA),其中PCA最为广泛。
13.1降维技术
第一种降维的方法称为主成分分析。在PCA中,数据从原来的坐标系转换到新的坐标系,新坐标系的选择是由数据本身决定的。第一个新坐标轴选择的是原始数据中方差最大的方向,第二个新坐标轴的选择和第一个坐标轴正交且具有最大方差的方向。该过程一直重复,重复次数为原始数据中特征的数目。我们会发现,大部分方差都包含在最前面的几个新坐标轴中。因此,我们可以忽略余下的坐标轴,即对数据进行了降维处理。
另外一种降维技术是因子分析。在因子分析中,我们假设在观察数据的生成中有一些观察不到的隐变量(latent variable)。假设观察数据是这些隐变量和某些噪声的线性组合。那么隐变量的数据可能比观察数据的数目少,也就是说通过找到隐变量就可以实现数据的降维。
还有一种降维技术就是独立成分分析。ICA假设数据是从N个数据源生成的,这一点和因子分析有些类似。假设数据为多个数据源的混合观察结果,这些数据源之间在统计上是相互独立的,而在PCA中只假设数据是不相关的。同因子分析一样,如果数据源的数目少于观察数据的数目,则可以实现降维。
13.2PCA
主成分分析的优缺点:
优点:降低数据的复杂性,识别最重要的多个特征。
缺点:不一定需要,且可能损失有用信息。
适用数据类型:数值型数据。
13.2.1移动坐标轴
考虑下图的数据点,我们画出一条直线,这条线尽可能覆盖这些点,即图中红线所示。在PCA中,我们对数据的坐标进行旋转,该旋转的过程取决于数据的本身。第一条坐标轴旋转到覆盖数据的最大方差位置,即图中的红线。接下来选择第二条坐标轴,这条坐标轴与第一条坐标轴正交,它就是覆盖数据次大差异的坐标轴,即图中的蓝线。利用PCA,我们将数据坐标轴旋转至数据角度上的那些最重要的方向。
考虑上图中的数据,其中包含着三个不同的类别。
如果采用决策树将其分类,我们会发现在x轴上可以找到一些值,这些值能够很好的将这三个类别分开,但是分类间隔不会很大。如图中绿色所示。
如果使用SVM分类器,我们就会得到更好的分类面和分类规则。SVM可能比决策树得到更好的分类间隔,但是分类超平面却很难解释。
通过PCA进行降维处理,如上图的蓝线所示。我们就可以同时获得SVM和决策树的优点:一方面,得到了和决策树一样简单的分类器,同时分类间隔和SVM一样好。
13.2.2在NumPy中实现PCA
将数据转换成前N个主成分的伪代码如下:
去除平均值
计算协方差矩阵
计算协方差矩阵的特征值和特征向量
将特征值从大到小排序
保留最上面的N个特征向量
将数据转换到上述N个特征向量构建的新空间中
创建一个Pca.py文件并添加下列代码:
from numpy import *
import matplotlib.pyplot as plt
'''
功能:将原始数据转换为矩阵型,为了后面的计算使用
'''
def loadDataSet(fileName, delim='\t'):
fr = open(fileName)
stringArr = [line.strip().split(delim) for line in fr.readlines()] #得到了是列表,每个元素是字符串
datArr = [list(map(float,line)) for line in stringArr] #把每行字符串映射为浮点型数字
## print 'datArr=',datArr
return mat(datArr) #返回的是矩阵型数据集
'''
功能:将数据降维
输入变量:
dataMat:矩阵型原始数据
topNfeat:保留的特征个数
输出变量:
lowDDataMat:降维之后的数据集
reconMat:重构原始数据集
'''
def pca(dataMat, topNfeat=9999999):
meanVals = mean(dataMat, axis=0)
meanRemoved = dataMat - meanVals #remove mean移除均值
## print 'meanRemoved=',meanRemoved
covMat = cov(meanRemoved, rowvar=0)
eigVals,eigVects = linalg.eig(mat(covMat))
## print 'eigVals=',eigVals
eigValInd = argsort(eigVals) #sort, sort goes smallest to largest
eigValInd = eigValInd[:-(topNfeat+1):-1] #cut off unwanted dimensions后面的-1代表的是将值倒序,原来特征值从小到大,现在从大到小
## print 'eigValInd=',eigValInd
redEigVects = eigVects[:,eigValInd] #reorganize eig vects largest to smallest
lowDDataMat = meanRemoved * redEigVects#transform data into new dimensions将数据转换到新空间中
print(lowDDataMat)
reconMat = (lowDDataMat * redEigVects.T) + meanVals #降维后的数据再次映射到原来空间中,用于与原始数据进行比较
return lowDDataMat, reconMat
'''
功能:绘图
'''
def fig(dataMat,reconMat):
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()
测试代码:
import Pca
from numpy import *
dataMat = Pca.loadDataSet('testSet.txt')
print('dataMat=',dataMat)
lowDMat, reconMat = Pca.pca(dataMat,1)
print(shape(lowDMat))
print('reconMat=',reconMat)
Pca.fig(dataMat,reconMat)
结果:
分析:
文件中的原始数据共有1000个样本,每个样本2个特征,保留1个特征,即降维到1维,其特征向量为方差最大的方向,就是如图红色直线的方向。
改变调用pca函数时的参数,改为 lowDMat, reconMat = Pca.pca(dataMat,2) ,即保量样本的2个特征,降维到2维那么得到的结果与原来的数据是重合的。这样没有剔除任何特征。如下图所示。
13.3示例:利用PCA对半导体制造数据降维
我们需要降维的数据集包含了590个特征。数据中包含很多缺失值,这些缺失值是以NaN(Not a Number的缩写)标识的。对于缺失值,我们使用特征的均值来填补缺失值。
在Pca.py中添加下列代码:
def replaceNanWithMean():
datMat = loadDataSet("secom.data",' ')
numFeat = shape(datMat)[1] # 获取特征维度
for i in range(numFeat):
meanVal = mean(datMat[nonzero(~isnan(datMat[:,i].A))[0],i]) # 利用该维度所有非NaN特征求取均值
#(找出第 i 列中是Nan的那些元素的位置(是为1,不是为0)再取反就找到了不是Nan的位置,
#然后再索引出来求均值。)
datMat[nonzero(isnan(datMat[:,i].A))[0],i] = meanVal # 将该维度中所有NaN特征全部用均值替换
return datMat
测试代码:
import Pca
from numpy import *
dataMat = Pca.replaceNanWithMean()
meanVals = mean(dataMat,axis = 0)
meanRemoved = dataMat - meanVals
covMat = cov(meanRemoved,rowvar=False)
eigVals,eigVects = linalg.eig(mat(covMat))
print(eigVals)
# 对前20个特征值画图
Pca.plt.plot(eigVals[:20])
Pca.plt.show()
#画占比
eigValsRate = []
for i in range(30):
temRate = float(sum(eigVals[0:i]))/ sum(eigVals)
eigValsRate.append(temRate)
Pca.plt.plot(eigValsRate)
Pca.plt.show()
结果:
前20个主成分占总方差的百分比:
前20个主成分累计方差百分比:
分析:
从结果中可以看到,其中有很多数据的特征值是0,意味着它们本身并没有提供额外的信息。
且大部分方差都包含在前面的几个主成分中,舍弃后面的主成分并不会损失太多的信息。如果保留前6个主成分,则数据集可以从590个特征约简成6个特征,大概实现了100:1的压缩。