机器学习实战—利用PCA来简化数据
一、降维技术
在低维下,数据更容易处理。
对数据简化有如下原因:
1、使得数据集更易使用。
2、降低很多算法的计算开销。
3.去除噪声。
4.使得结果更易懂。
第一种降维的方法是主成分分析(PCA),在PCA中,数据从原来的坐标系中转换到了新的坐标系,新坐标系的选择由数据本身决定。第一个新坐标轴选择的是原始数据中方差最大的方向,第二个新坐标轴的选择和第一个坐标轴正交且具有最大方差的方向。该过程一直重复,大部分方差都包含在最前面的几个新坐标轴中,因此实现了数据降维。
第二种降维技术是因子分析,假设观察数据是由隐变量和某些噪声的线性组合,那么只查找隐变量就可以实现数据降维。
第三种降维技术是独立成分分析(ICA),ICA假设数据是从N个数据源生成的,假设数据为多个数据源的混合观察结果,这些数据源之间在统计上是相互独立的,而在PCA只假设数据是不相关的,不相关和独立是两个概念,所以PCA和ICA是不同的。
二、PCA
优点:降低数据的复杂性,识别最重要的多个特征。
缺点:不一定需要,且可能损失有用信息。
1、移动坐标轴
在PCA中,数据从原来的坐标系中转换到了新的坐标系,新坐标系的选择由数据本身决定。
而描述线性变换也很简单,通常一组线性无关向量即可,称为基。基可以理解为构建向量世界的基础,任何向量都可以利用它线性组合而成。
我们利用最多的就是i帽和j帽(即正交基(1,0),(0,1)注:一般描述为列向量,为了方便,本章的向量都为列向量)
如下图所示,α=(3,2)向量其实具体应该描述为α=3i+2j
既然如此,假如我们不再使用常规的i帽和j帽最为基,例如小明同学非要换一组其他的(例如将i,j旋转90°,其实这就是一种线性变换)基,那么在他看来,原来的α该怎么描述呢?
可能有人会问,α并未变化,产生差别的原因是?在这里,我们的视角和小明的视角并不一样,更进一步说,是因为我们和小明选取的坐标(参考)系不一样。我们看到的α是在xy坐标系下3倍i帽与2倍j帽的线性组合,而小明看到的是旋转后的坐标系,这时我们需要利用小明选取的基来描述α。
具体说来,可以有两种方法求解“小明眼中α的坐标”:
1、将α投影到新基上,得出投影长度(有方向,即可正负)即为坐标;
2、通过坐标变换公式求得
先说方法一,先说明一下投影的含义,一方面,从几何意义上,如下图所示,即将向量向另一向量所在直线做垂线,则投影长度即为蓝色向量与垂线交点的向量长度;另一方面,内积与投影关系密切,有A·B=|A|cos(a)|B|,这里设A为投影向量,则B为被投影向量,则|A|cos(a)为投影矢量长度,|B|为被投影向量的模。再进一步,如果我们假设B的模为1,即让|B|=1,那么就变成了:A·B=|A|cos(a)
简而言之,如果设向量B的模为1,则A与B的内积值等于A向B所在直线投影的矢量长度!
继续说下面这幅图,图中基由(1,0),(0,1)转换为(1/√2,1/√2),(-1/√2,1/√2)
注:这里基的坐标都是以我们视角的坐标系来看的
那么,新基坐标系下,α的坐标计算为:
即下图所示:
一般说来,如果我们有M个N维向量,想将其变换为由R个N维向量表示的新空间中,那么首先将R个基按行组成矩阵A(上例中我们把基(1/√2,1/√2),(-1/√2,1/√2)按行排列),然后将向量按列组成矩阵B,那么两矩阵的乘积AB就是变换结果,其中AB的第m列为A中第m列变换后的结果。
最后,上述分析同时给矩阵相乘找到了一种物理解释:两个矩阵相乘的意义是将右边矩阵中的每一列列向量变换到左边矩阵中每一行行向量为基所表示的空间中去。
2、目的
根据上述的经验,我们认为让数据进行合理的降维,一是尽量减少信息的缺失,即保全信息的所有内容;二是减少数据之间的相关性(例子中有所体现)。
2.1、从统计上来说(另一层面是从概率上来说),方差体现着离散程度,直观而言,二维数据降为一维,这个问题实际上是要在二维平面中选择一个方向,将所有数据都投影到这个方向所在直线上,用投影值表示原始记录。
那么如何选择这个方向(或者说基)才能尽量保留最多的原始信息呢?我们希望投影后的投影值尽可能分散。也就是方差尽可能的大。
2.2、统计中另一指标协方差,其用于衡量两个变量的总体误差,直接理解为相关性即可(由其得到的相关系数可能更为直接)。
我们希望的是协方差尽可能的小,如果为0,则可以得出目标变量之间不相关(或者成为线性独立,不相关是指两个随机变量没有近似的线性关系,而独立是指两个变量没有任何关系。)
由上述可以得到我们的目的:数据之间的1.方差尽可能的大,2.协方差逼近0
3.策略
这里我们假设数据每一维度特征的均值为0(我们有能力达到这样的效果,可以称之为0均值化,当然也可以不这样做),上述的方差和协方差公式为:
假设我们只有a和b两个字段,那么我们将它们按行组成矩阵X(这里和上面第1节内容定义不一样,每一列代表一个样本或者一条记录):
然后我们用X乘以X的转置,并乘上系数1/m:
这个矩阵对角线上的两个元素分别是两个字段的方差,而其它元素是a和b的协方差。两者被统一到了一个矩阵的。
根据矩阵相乘的运算法则,这个结论很容易被推广到一般情况:
设我们有m个n维数据记录,将其按列排成n乘m的矩阵X,设C=X乘以X的转置,并乘上系数1/m,则C是一个对称矩阵,其对角线分别个各个字段的方差,而第i行j列和j行i列元素相同,表示i和j两个字段的协方差。
从上面知我们的目的:数据之间的1.方差尽可能的大,2.协方差逼近0,在协方差矩阵C体现就为对角元尽可能大,非对角元为0这样的数值体现。
4、方法
上节我们得知,我们需要让协方差矩阵C体现就为对角元尽可能大,非对角元为0,有什么方法能做到这点?
4.1、特征值法
对角元尽可能大,非对角元为0 这样的描述直接的想法就是对角矩阵,那么我们能不能联系特征向量、特征值这些知识?当然是肯定的,还记得C必为对称矩阵的这一特点吗?
在线性代数上,实对称矩阵有一系列非常好的性质:
1)对称矩阵特征值为实数。
2)实对称矩阵不同特征值对应的特征向量必然正交。
3)设特征向量λλ重数为r,则必然存在r个线性无关的特征向量对应于λλ,因此可以将这r个特征向量单位正交化。
···
那么我们的想法就很自然了,原数据矩阵X,由它得到协方差矩阵C,我们希望X经过变换矩阵P得到降维矩阵Y,而Y协方差矩阵为对角矩阵,这样满足了上述目的。
拆分上面的语言,
“X经过变换矩阵P得到降维矩阵Y”,数学上表达为:PX=Y(XP=Y也可以,只不过二者的P不一样);
“Y协方差矩阵为对角矩阵”,设Y的协方差矩阵为D,我们推导一下D与C的关系:
目标进一步明确,我们要求变换矩阵P,P是体现我们整个线性变化的关键。我们可以通过对它的修改来达到降维这一目的。
问题是P怎么求得?
我们从特征变换考虑D与C之间的关系:
假设C求得特征向量,为e1,e2,⋯,en,我们将其按列组成矩阵:
将C对角化:
很自然,我们希望Λ =D,则P =E转置
P是协方差矩阵的特征向量单位化后按行排列出的矩阵,其中每一行都是C的一个特征向量。如果设P按照Λ中特征值的从大到小,将特征向量从上到下排列,则用P的前K行组成的矩阵乘以原始数据矩阵X,就得到了我们需要的降维后的数据矩阵Y。
至于K的选择,一般说来采用一定的比例作为标准(参见西瓜书p231,其中的说法是重构阈值。简单而言,就是选取的K个特征值之和占总体特征值之和的比例必须达到一定的比例)
总结一下,特征值法的算法为( 设有m条n维数据):
1)将原始数据按列组成n行m列矩阵X
2)将X的每一行(代表一个属性字段)进行零均值化,即减去这一行的均值
3)求出协方差矩阵C
4)求出协方差矩阵的特征值及对应的特征向量
5)将特征向量按对应特征值大小从上到下按行排列成矩阵,取前k行组成矩阵P
6)Y=PX即为降维到k维后的数据
即PCA算法就是用这种方法,求解特征向量的过程就是求解正交向量基的过程,选取特征值较大的前n个特征,从而组成特征向量矩阵,这样是在寻找数据在低维空间中新坐标系的基向量,有了基向量之后,将原数据坐标映射到新坐标系中就是该数据在新坐标系中的坐标。
PCA算法:
from numpy import *
import numpy as np
#数据加载函数
def loadDataSet(filename,delim='\t'):
fp = open(filename)
stringArr = [line.strip().split(delim) for line in fp.readlines()]
#将每个元素映射 为float型
dataMat = [list(map(float,line)) for line in stringArr]
return mat(dataMat)
#输入参数:数据集、维度要求
def pca(dataSet,topNfeat=9999999):
#PCA中是对去均值后的数据求解协方差矩阵的,协方差均值正对角元素代表该维度变量的方差,其他元素代表了对应两个维度变量的协方差
#在对数据降维时,PCA将数据映射到N低维坐标系中就要保证坐标系基向量正交(协方差为0),并且数据在单个维度上的分类间隔大(方差大)
#去均值是为了计算方便
#参数axis=0表示对矩阵的每一列求均值
meanVals = mean(dataSet,axis=0)
#去均值
meanRemoved = dataSet - meanVals
#计算协方差矩阵
covMat = cov(meanRemoved,rowvar=0)
#计算协方差矩阵的特征值和特征向量
eigVals,eigVects = np.linalg.eig(mat(covMat))
#对特征进行排序(升序),函数返回的是索引值
eigValsInd = argsort(eigVals)
# print("eigValsInd:",eigValsInd)
#从右开始选择所要求的维度数量,返回的由大到小
eigValsInd = eigValsInd[:-(topNfeat+1):-1]
#将选出的特征向量组成特征矩阵
redEigVects = eigVects[:,eigValsInd]
#将去均值的数据映射到地位空间
lowDDataMat = meanRemoved * redEigVects
#再根据低维向量基矩阵将原始数据重构
reconMat = (lowDDataMat * redEigVects.T) + meanVals
return lowDDataMat,reconMat
绘制散点图:
#数据集中有NAN,对所有NAN数据更换为特征列的均值
def repalceNanWithMean():
dataMat = loadDataSet('secom.data',' ')
numFeat = shape(dataMat)[1]
#遍历每一列数据
for i in range(numFeat):
#计算每一列均值
meanVal = mean(dataMat[nonzero(~isnan(dataMat[:,i].A))[0],i])
#将均值填充至NAN处
dataMat[nonzero(isnan(dataMat[:, i].A))[0], i] = meanVal
return dataMat
结果:
示例:利用PCA对半导*造数据降维
该数据集有590个特征,对其进行降维,该数据包含很多缺失值,这些缺失值是以NaN标识的,对于这些值用特征平均值来代替
将NaN替换为平均值:
#数据集中有NAN,对所有NAN数据更换为特征列的均值
def repalceNanWithMean():
dataMat = loadDataSet('secom.data',' ')
numFeat = shape(dataMat)[1]
#遍历每一列数据
for i in range(numFeat):
#计算每一列均值
meanVal = mean(dataMat[nonzero(~isnan(dataMat[:,i].A))[0],i])
#将均值填充至NAN处
dataMat[nonzero(isnan(dataMat[:, i].A))[0], i] = meanVal
return dataMat
测试函数:
def exis():
dataMat = repalceNanWithMean()
meanVals = mean(dataMat,axis=0)
meanRemoved = dataMat - meanVals
covMat = cov(meanRemoved,rowvar=0)
eigVals,eigVects = np.linalg.eig(mat(covMat))
print("eigVals:",eigVals)
注:以上函数均在main函数进行测试,main函数如下:
if __name__ == "__main__":
dataMat = loadDataSet('testSet.txt')
print("shape(dataMat)",shape(dataMat))
lowDDataMat, reconMat = pca(dataMat,1)
print("shape(lowDDataMat):",shape(lowDDataMat))
print("shape(reconMat):",shape(reconMat))
poltScatter(dataMat,reconMat)
# exis()
下图给出了总的方差百分比:
我们观察到,在开始的几个主成分后,方差就会迅速下降。
下标给出了这些主成分的方差百分比和累计方差百分比:
这样我们可以知道在数据集的前面多个主成分所包含的信息量很大,上面的分析能够得到所用到的主成分数目,然后得到约简后数据就可以在分类器中使用了。
总结:降维技术使得数据变得更易使用,并且它们往往能够去除数据中的噪声,使得其他机器学习任务更加精确。降维往往作为预处理步骤,在数据应用到其他算法之前清洗数据。有很多的技术可以用于数据降维,在这些技术中,独立成分分析、因子分析和成分分析比较流行,其中又以主成分分析应用最广泛。