[关闭]
@anboqing 2015-06-17T22:24:35.000000Z 字数 9622 阅读 4515

降维 PCA

PCA 降维



1. 维度灾难

1.1 The curse of dimensionality

bellman
Richard E.Bellman(发明动态规划的美国应用数学家) 在1961年提出了这个术语:

The "Curse of dimensionality", is a term coined by Bellman to describe the problem caused by the exponential increase in volume associated with adding extra dimensions to a (mathematical) space.

1.2 用3类模式识别问题举例

  1. 一个简单的方法是:

    • 将特征空间划分为小bins
    • 计算每个bins里边每个样本对于每一类的ratio
    • 对于一个新的样本,找到它所在的bin然后将它划分到该bin里最主要的类里
      这个方法类似于k-nn算法,和桶算法类似
  2. 比如我们用单特征来划分三个类的9个实例:
     pca1
    在一维我们会注意到类之间会有太多重叠,于是我们就决定引入第二个特征试图增加可分性.
    pca2
    这时,在二维空间,如果

    • 我们选择保留样本密度,那么样本点数量就从9激增至9*3=27
    • 我们选择保留样本数量,那么2维的散点图就十分稀疏
      该怎么抉择呢?再增加一个feature?
      pca3
      在3维空间,事情变得更糟糕:
    • bins的数量增加到27
    • 维持样本密度不变,样本点的数量就增加到了81
    • 维持样本点个数不变,那么3D散点图几乎就是空的

很明显,我们用相同的bins来划分样本空间的办法是十分低效率的

1.3 我们如何打败维度灾难?

在实践中,维度灾难意味着,给定一个样本大小,存在一个维数的上限,超过了这个上线,分类器的性能就会不升反降.
pca4
很多时候,在低维空间做更准确的映射比在高维空间直接映射有更好的分类性能.

1.4 维度灾难更加深刻的含义

fredman

定义在高维空间的函数比定义在低维空间的函数复杂的多,并且那些复杂性是难以辨明的复杂.这意味着,为了更好的学习它,越复杂的函数需要越密集的样本点 --by Friedman( famous for his friedman test)


2. 降维

2.1 特征选择 vs. 特征提取


2.1.1 特征抽取的定义


2.2 最优映射 y=f(x) 

通常,一个最优映射 y=f(x)是一个非线性函数,但是,没有一个系统和成熟的方法进行非线性变换,非线性变换的数学理论还很不成熟.(对特定特征子集的选择还要结合具体问题具体分析).因此,特征抽取常常被限定为了线性变换 y=Wx.
* 这就是说,y 是 x 的一个线性投影
* 注意:当映射一个非线性函数时,减少后的特征空间叫做manifold(流形).参见Nonlinear dimensionality reduction
pca6


2.3 信号表示 vs. 分类

选择特征抽取映射y=f(x)的办法是找到一个目标函数,对其进行最大化或者最小化.按照目标函数的条件,特征提取技术被分为了两类:

pca7
   
在线性特征提取的领域内,常用的技术有两个:


3. Principal Component Analysis(PCA)


3.1 PCA的目标

PCA的目标是在降维的同时尽可能多的保留高维空间的随机性(方差)

3.2 PCA 的推导

假设 x 是一个 N-维随机向量,表示为一组正交基向量[ϕ1|ϕ2|....|ϕN]的线性组合

x=i=1Nyiϕiwhereϕiϕj={0,1, j i = j 

假设我们只选择了基向量中的M(M<N)维来表示x,我们可以用预先选择的常量bj来替换[yM+1,...,yN]T

x^(M)=i=1Myiϕi+i=M+1Nbiϕi

两种表示形式的方差为:
Δx(M)=xx^(M)=i=1Nyiϕii=1Myiϕi+i=M+1Nbiϕi=i=M+1N(yibi)ϕi

我们可以用均方差来表示这个误差的大小.   
   
我们的目标是找到使均方误差最小的参数:基向量ϕi和常量bi
ϵ¯2(M)=E[|Δx(M)|2]=Ei=M+1Nj=M+1N(yibi)(yjbj)ϕTiϕj=i=M+1NE[(yibi)2]

这里之所以能抵消是因为,当 ij 时, ϕiϕj=0
i=j 时,ϕiϕj=1

问题现在转化成了求 bjϕi,我们可以用对目标函数求偏导数并且令偏导数为0的方法来求bi.
8

现在,把bi=E[yi] 代入上边的公式,均方误差可以变换成方差的正交化形式:

9
其中 X 是 x 的协方差矩阵.

现在,引入拉格朗日因子:
10
对每个 ϕi 求偏导:
11
于是,ϕiλi就是协方差矩阵 X特征值和特征向量

这样,我们就能把均方差的和表示为:
12

为了最小化这个式子,λi 必须是最小的特征值


3.3 总结

把随机向量 X 投影到 其协方差矩阵X的最大特征值λi对应的特征向量ϕi上,我们就可以得到N维随机向量 xRN 的一个最优(有误差的最小方差和)近似: M 个独立向量的线型组合.

注意:
- 由于PCA使用了协方差矩阵的特征向量,它能够找到单峰正态分布下数据的独立分布
- 对于非高斯分布或者多峰正态分布数据,PCA只是简单的去相关
- PCA的主要限制是它没有考虑类别的可分性,因为它没有考虑类标签
- PCA只是简单的将坐标转向了有最大方差的方向
- 而有最大方差的方向并不保证有良好的可分类特征
例如:
13
用PCA的话,会降维到Y轴上,因为Y轴有最大的方差.然而降维到y轴之后,样本几乎不可分,因为都混杂在一起了.


3.4 举例


4.实现PCA算法


4.1 数据预处理

在应用PCA算法之前,我们常常要对数据进行预处理,因为PCA算法对原始数据的可靠性,一致性,以及规范性十分敏感,如果不做相应的处理,算法的效果就会大打折扣.
通常我们希望所有的特征 x1,x2,...,xn都有相似的取值范围(并且均值接近0).在部分应用中,我们都有必要对每个特征做预处理,即通过估算每个特征xj的均值和方差,而后将其取值范围规整化为零均值单位方差.

通常,预处理主要有2步:


4.1.1 均值规整化

假设我们有训练集: x(1),x(2),...x(m)
均值规整化过程:
1. 求每个特征的均值 μi=1mmi=1x(i)j
2. 把每个特征的值x(i)j 替换为 xjμj
3. 如果不同的特征之间的取值范围不一样(例如: x1 = size of house(大多取值 1000多) x2 = number of bedrooms(取值不超过10) ), 就需要把每个特征的刻度转换成和其他特征差不多的取值范围:

x(i)j=x(i)j=μjSj

这里 Sj是某种对特征j的取值范围的度量.(常常是最大值减最小值 或者是标准差)


4.1.2 白化


4.2 PCA 算法

  1. 首先,计算 协方差矩阵
    Σ=1mi=1n(x(i))(x(i))T

    其中 Σ 是 n x n 大小的协方差矩阵 , 因为 x(i) 是 n x 1 (一个样本), (x(i))T 是 1 x n .
  2. 计算协方差矩阵 Σ特征向量:
  1. [U,S,V] = svd(Sigma);
  2. //或者
  3. [U,S,V] = eig(Sigma);

关于这里为什么要用 svd函数,因为 PCA 根据应用领域的不同,还有很多别名:
线性代数中叫 :SVD(singular value decomposition of X ) EVD(eigenvlaue decomposition of XTX) ,KLT(信号处理),POD(机械工程).

这样,我们得到了 [U,S,V],其中 U 就是 把特征向量按照对应的特征值大小从大到小排列所得到的一个 n x n 特征向量矩阵,这就是一个变换后的正交基

U=|u1||u2||un|(1501)

我们将要使用这个矩阵来降维,要将原来的数据 xRn 映射到 zRk(k<n),需要使用前 K 个最大的特征值对应的特征向量(因特征值大的更能代表数据特征)组生成一组新的正交基:

Ureduce=|u1||u2||uk|(1502)

  1. Ureduce = U(:,1:k);

这样以来,我们就可以在新基上用投影的方式表示原来的数据了:

Z=|u1||u2||uk|TX=(u1)T(u2)T(uk)TX=z1z2zk(1503)

这样,
x1x2xnz1z2zk(k<n),(1504)

  1. z = Ureduce'*x;

4.3 python 代码实现

数据下载地址

代码github地址

  1. #coding=utf-8
  2. import numpy as np
  3. __author__ = 'anboqing'
  4. """
  5. feature scaling and mean normalization
  6. 零均值化
  7. """
  8. def zeroMean(dataMat):
  9. print("feature scaling ... ")
  10. meanVal = np.mean(dataMat,axis=0) # axis = 0 means that calculate mean value by column(every feature)
  11. print("mean normalization ...")
  12. newVal = dataMat - meanVal
  13. return newVal,meanVal
  14. """
  15. 求预处理之后的样本矩阵的协方差矩阵
  16. """
  17. def covarianceMat(normal_data):
  18. '''
  19. 计算协方差矩阵
  20. :param normal_data: 零均值规范化过的数据
  21. :return: 协方差矩阵
  22. '''
  23. print('calculate the covariance matrix ... ')
  24. covMat = np.cov(normal_data,rowvar=0)
  25. """
  26. numpy中的cov函数用于求协方差矩阵,
  27. 参数rowvar很重要,
  28. 若rowvar=0,说明传入的数据一行代表一个样本,
  29. 若rowvar!=0,说明一列代表一个样本
  30. """
  31. return covMat
  32. def eigenValAndVector(covMat):
  33. """
  34. 求协方差矩阵的特征值和特征向
  35. :param covMat: 协方差矩阵
  36. :return: 协方差矩阵的特征值,特征向量
  37. """
  38. print('calculate eigen value and eigen vector of covariance matrix...')
  39. eigVals,eigVects = np.linalg.eig(np.mat(covMat))
  40. return eigVals,eigVects
  41. def reduceDimensionality(eigVals,eigVects,dataMat,k=1):
  42. """
  43. 降维:保留主要成分
  44. 用前k个特征向量组成新的正交基,把原始数据映射到新的正交基上
  45. :param eigVals: 原始数据的协方差矩阵的特征值
  46. :param eigVects: 原始数据的协方差矩阵的特诊向量
  47. :param dataMat: 原始数据
  48. :param k: 要降维到多少维
  49. :return: 降维后的数据,投影的正交基
  50. """
  51. print('reduce the dimension ... ')
  52. eigValIndices = np.argsort(eigVals) # 对特征值从小到大排序
  53. n_eigValIndices = eigValIndices[-1:-(k+1):-1] # 最大的k个特征值的下标
  54. data_mat_trans = eigVects[:,n_eigValIndices] # 取出前k个最大的特征向量
  55. low_dimensional_data = dataMat * data_mat_trans # 将原始数据投影到新的向量空间的正交基上
  56. return low_dimensional_data,data_mat_trans
  57. def load_libsvm_data(file_path):
  58. fin = open(file_path)
  59. print('load data : '+file_path)
  60. data_mat=[]
  61. label_vec = []
  62. for strline in fin.readlines():
  63. line_arr = strline.strip().split()
  64. label_vec.append(int(line_arr[0]))
  65. line_arr = line_arr[1:]
  66. row_arr = []
  67. idx = 1
  68. for pair in line_arr:
  69. str_indice,str_val=pair.split(":")
  70. indice = int(str_indice)
  71. fval = float(str_val)
  72. #print indice,fval
  73. if indice == idx:
  74. row_arr.append(fval)
  75. else:
  76. row_arr.append(0.0)
  77. idx=idx+1
  78. data_mat.append(row_arr)
  79. return data_mat,label_vec
  80. def choosePCASize(eig_vals,percentage=0.99):
  81. '''
  82. 计算主成分个数的函数
  83. :param eig_vals: 特征值数组
  84. :param percentage: 要保留的方差的百分比
  85. :return:应该降到的维数
  86. '''
  87. asc_eigs = np.sort(eig_vals) # 升序
  88. desc_eigs = asc_eigs[-1::-1] # 翻转成从大到小
  89. eig_sum = sum(desc_eigs)
  90. tmpSum =0
  91. num = 0
  92. for i in desc_eigs:
  93. tmpSum += i
  94. num+=1
  95. if tmpSum >= eig_sum * percentage:
  96. return num
  97. if __name__ == "__main__":
  98. file_path = 'madelon'
  99. # 读取livsvm格式的数据
  100. data_mat,label_vec = load_libsvm_data(file_path)
  101. # 转换成 numpy matrix 结构
  102. dataMat = np.mat(data_mat)
  103. # 数据零均值化
  104. new_dat,mean_dat = zeroMean(dataMat)
  105. # 计算协方差矩阵
  106. cov_mat = covarianceMat(new_dat)
  107. #print np.shape(cov_mat)
  108. # 计算协方差矩阵的特征值和特征向量
  109. eigen_values,eigen_vectors = eigenValAndVector(cov_mat)
  110. # 选择要降到的维数(保存99的方差)
  111. num_ = choosePCASize(eigen_values)
  112. # 试着把这个数据集降维为num_维
  113. low_dim_mat,orth_base = reduceDimensionality(eigen_values,eigen_vectors,new_dat,num_)
  114. print np.shape(low_dim_mat)
  115. print np.shape(orth_base)

4.4 选择主成分的个数

在应用PCA的时候,对于一个1000维的数据,我们怎么知道要降维到多少维才是合理的?也就是要在保留最多信息的同时取出最多的噪声.Andrew Ng的机器学习课讲的方法是:

  1. 算出 Average squared projection error:
    1mi=1mx(i)x(i)approx2
  2. 算出 Total variation in the data:
    1mi=1mx(i)2
  3. 然后,选择满足下列条件的 k :
    1mmi=1x(i)x(i)approx21mmi=1x(i)20.01(1%)

    也就是说: 保留了 99 % 的方差,其中 0.01可以根据应用的不同来自己设定
    上式也可以改成:
    1ki=1λini=1λi0.01

    其中λi 是协方差矩阵的特征值

选择主成分大小的算法:

  1. 计算出协方差矩阵的特征值,特征向量等
    2.然后算出特征值的和
    3.迭代 k=1尝试计算分子,看满足不满足不等式,直到满足为止
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注