【问题标题】:Principal component analysis in PythonPython中的主成分分析
【发布时间】:2010-12-16 09:07:48
【问题描述】:

我想使用主成分分析 (PCA) 进行降维。 numpy 或 scipy 是否已经拥有它,还是我必须使用 numpy.linalg.eigh 自己推出?

我不只是想使用奇异值分解 (SVD),因为我的输入数据非常高维(约 460 维),所以我认为 SVD 会比计算协方差矩阵的特征向量要慢。

我希望找到一个预制的、经过调试的实现,它已经为何时使用哪种方法做出了正确的决定,并且可能会进行我不知道的其他优化。

【问题讨论】:

    标签: python numpy scipy pca


    【解决方案1】:

    如果您正在使用 3D 矢量,则可以使用工具带 vg 简洁地应用 SVD。它是 numpy 之上的一个轻层。

    import numpy as np
    import vg
    
    vg.principal_components(data)
    

    如果你只想要第一个主成分,还有一个方便的别名:

    vg.major_axis(data)
    

    我在上次创业时创建了这个库,它的动机是这样的:在 NumPy 中冗长或不透明的简单想法。

    【讨论】:

      【解决方案2】:

      几个月后,这是一个小类PCA,以及图片:

      #!/usr/bin/env python
      """ a small class for Principal Component Analysis
      Usage:
          p = PCA( A, fraction=0.90 )
      In:
          A: an array of e.g. 1000 observations x 20 variables, 1000 rows x 20 columns
          fraction: use principal components that account for e.g.
              90 % of the total variance
      
      Out:
          p.U, p.d, p.Vt: from numpy.linalg.svd, A = U . d . Vt
          p.dinv: 1/d or 0, see NR
          p.eigen: the eigenvalues of A*A, in decreasing order (p.d**2).
              eigen[j] / eigen.sum() is variable j's fraction of the total variance;
              look at the first few eigen[] to see how many PCs get to 90 %, 95 % ...
          p.npc: number of principal components,
              e.g. 2 if the top 2 eigenvalues are >= `fraction` of the total.
              It's ok to change this; methods use the current value.
      
      Methods:
          The methods of class PCA transform vectors or arrays of e.g.
          20 variables, 2 principal components and 1000 observations,
          using partial matrices U' d' Vt', parts of the full U d Vt:
          A ~ U' . d' . Vt' where e.g.
              U' is 1000 x 2
              d' is diag([ d0, d1 ]), the 2 largest singular values
              Vt' is 2 x 20.  Dropping the primes,
      
          d . Vt      2 principal vars = p.vars_pc( 20 vars )
          U           1000 obs = p.pc_obs( 2 principal vars )
          U . d . Vt  1000 obs, p.obs( 20 vars ) = pc_obs( vars_pc( vars ))
              fast approximate A . vars, using the `npc` principal components
      
          Ut              2 pcs = p.obs_pc( 1000 obs )
          V . dinv        20 vars = p.pc_vars( 2 principal vars )
          V . dinv . Ut   20 vars, p.vars( 1000 obs ) = pc_vars( obs_pc( obs )),
              fast approximate Ainverse . obs: vars that give ~ those obs.
      
      
      Notes:
          PCA does not center or scale A; you usually want to first
              A -= A.mean(A, axis=0)
              A /= A.std(A, axis=0)
          with the little class Center or the like, below.
      
      See also:
          http://en.wikipedia.org/wiki/Principal_component_analysis
          http://en.wikipedia.org/wiki/Singular_value_decomposition
          Press et al., Numerical Recipes (2 or 3 ed), SVD
          PCA micro-tutorial
          iris-pca .py .png
      
      """
      
      from __future__ import division
      import numpy as np
      dot = np.dot
          # import bz.numpyutil as nu
          # dot = nu.pdot
      
      __version__ = "2010-04-14 apr"
      __author_email__ = "denis-bz-py at t-online dot de"
      
      #...............................................................................
      class PCA:
          def __init__( self, A, fraction=0.90 ):
              assert 0 <= fraction <= 1
                  # A = U . diag(d) . Vt, O( m n^2 ), lapack_lite --
              self.U, self.d, self.Vt = np.linalg.svd( A, full_matrices=False )
              assert np.all( self.d[:-1] >= self.d[1:] )  # sorted
              self.eigen = self.d**2
              self.sumvariance = np.cumsum(self.eigen)
              self.sumvariance /= self.sumvariance[-1]
              self.npc = np.searchsorted( self.sumvariance, fraction ) + 1
              self.dinv = np.array([ 1/d if d > self.d[0] * 1e-6  else 0
                                      for d in self.d ])
      
          def pc( self ):
              """ e.g. 1000 x 2 U[:, :npc] * d[:npc], to plot etc. """
              n = self.npc
              return self.U[:, :n] * self.d[:n]
      
          # These 1-line methods may not be worth the bother;
          # then use U d Vt directly --
      
          def vars_pc( self, x ):
              n = self.npc
              return self.d[:n] * dot( self.Vt[:n], x.T ).T  # 20 vars -> 2 principal
      
          def pc_vars( self, p ):
              n = self.npc
              return dot( self.Vt[:n].T, (self.dinv[:n] * p).T ) .T  # 2 PC -> 20 vars
      
          def pc_obs( self, p ):
              n = self.npc
              return dot( self.U[:, :n], p.T )  # 2 principal -> 1000 obs
      
          def obs_pc( self, obs ):
              n = self.npc
              return dot( self.U[:, :n].T, obs ) .T  # 1000 obs -> 2 principal
      
          def obs( self, x ):
              return self.pc_obs( self.vars_pc(x) )  # 20 vars -> 2 principal -> 1000 obs
      
          def vars( self, obs ):
              return self.pc_vars( self.obs_pc(obs) )  # 1000 obs -> 2 principal -> 20 vars
      
      
      class Center:
          """ A -= A.mean() /= A.std(), inplace -- use A.copy() if need be
              uncenter(x) == original A . x
          """
              # mttiw
          def __init__( self, A, axis=0, scale=True, verbose=1 ):
              self.mean = A.mean(axis=axis)
              if verbose:
                  print "Center -= A.mean:", self.mean
              A -= self.mean
              if scale:
                  std = A.std(axis=axis)
                  self.std = np.where( std, std, 1. )
                  if verbose:
                      print "Center /= A.std:", self.std
                  A /= self.std
              else:
                  self.std = np.ones( A.shape[-1] )
              self.A = A
      
          def uncenter( self, x ):
              return np.dot( self.A, x * self.std ) + np.dot( x, self.mean )
      
      
      #...............................................................................
      if __name__ == "__main__":
          import sys
      
          csv = "iris4.csv"  # wikipedia Iris_flower_data_set
              # 5.1,3.5,1.4,0.2  # ,Iris-setosa ...
          N = 1000
          K = 20
          fraction = .90
          seed = 1
          exec "\n".join( sys.argv[1:] )  # N= ...
          np.random.seed(seed)
          np.set_printoptions( 1, threshold=100, suppress=True )  # .1f
          try:
              A = np.genfromtxt( csv, delimiter="," )
              N, K = A.shape
          except IOError:
              A = np.random.normal( size=(N, K) )  # gen correlated ?
      
          print "csv: %s  N: %d  K: %d  fraction: %.2g" % (csv, N, K, fraction)
          Center(A)
          print "A:", A
      
          print "PCA ..." ,
          p = PCA( A, fraction=fraction )
          print "npc:", p.npc
          print "% variance:", p.sumvariance * 100
      
          print "Vt[0], weights that give PC 0:", p.Vt[0]
          print "A . Vt[0]:", dot( A, p.Vt[0] )
          print "pc:", p.pc()
      
          print "\nobs <-> pc <-> x: with fraction=1, diffs should be ~ 0"
          x = np.ones(K)
          # x = np.ones(( 3, K ))
          print "x:", x
          pc = p.vars_pc(x)  # d' Vt' x
          print "vars_pc(x):", pc
          print "back to ~ x:", p.pc_vars(pc)
      
          Ax = dot( A, x.T )
          pcx = p.obs(x)  # U' d' Vt' x
          print "Ax:", Ax
          print "A'x:", pcx
          print "max |Ax - A'x|: %.2g" % np.linalg.norm( Ax - pcx, np.inf )
      
          b = Ax  # ~ back to original x, Ainv A x
          back = p.vars(b)
          print "~ back again:", back
          print "max |back - x|: %.2g" % np.linalg.norm( back - x, np.inf )
      
      # end pca.py
      

      【讨论】:

      • fyinfo,在2011年1月C. Caramanis Robust PCA 987654322有一个很好的谈话。 span>
      • 这个代码将输出该图像(虹膜pca)?如果没有,你能发布一个替代解决方案,在那里它是那个图像。我在将此代码转换为C ++时,我有一些困难,因为我是Python的新内容:) span>
      【解决方案3】:

      使用numpy.linalg.svd 的 PCA 非常简单。这是一个简单的演示:

      import numpy as np
      import matplotlib.pyplot as plt
      from scipy.misc import lena
      
      # the underlying signal is a sinusoidally modulated image
      img = lena()
      t = np.arange(100)
      time = np.sin(0.1*t)
      real = time[:,np.newaxis,np.newaxis] * img[np.newaxis,...]
      
      # we add some noise
      noisy = real + np.random.randn(*real.shape)*255
      
      # (observations, features) matrix
      M = noisy.reshape(noisy.shape[0],-1)
      
      # singular value decomposition factorises your data matrix such that:
      # 
      #   M = U*S*V.T     (where '*' is matrix multiplication)
      # 
      # * U and V are the singular matrices, containing orthogonal vectors of
      #   unit length in their rows and columns respectively.
      #
      # * S is a diagonal matrix containing the singular values of M - these 
      #   values squared divided by the number of observations will give the 
      #   variance explained by each PC.
      #
      # * if M is considered to be an (observations, features) matrix, the PCs
      #   themselves would correspond to the rows of S^(1/2)*V.T. if M is 
      #   (features, observations) then the PCs would be the columns of
      #   U*S^(1/2).
      #
      # * since U and V both contain orthonormal vectors, U*V.T is equivalent 
      #   to a whitened version of M.
      
      U, s, Vt = np.linalg.svd(M, full_matrices=False)
      V = Vt.T
      
      # PCs are already sorted by descending order 
      # of the singular values (i.e. by the
      # proportion of total variance they explain)
      
      # if we use all of the PCs we can reconstruct the noisy signal perfectly
      S = np.diag(s)
      Mhat = np.dot(U, np.dot(S, V.T))
      print "Using all PCs, MSE = %.6G" %(np.mean((M - Mhat)**2))
      
      # if we use only the first 20 PCs the reconstruction is less accurate
      Mhat2 = np.dot(U[:, :20], np.dot(S[:20, :20], V[:,:20].T))
      print "Using first 20 PCs, MSE = %.6G" %(np.mean((M - Mhat2)**2))
      
      fig, [ax1, ax2, ax3] = plt.subplots(1, 3)
      ax1.imshow(img)
      ax1.set_title('true image')
      ax2.imshow(noisy.mean(0))
      ax2.set_title('mean of noisy images')
      ax3.imshow((s[0]**(1./2) * V[:,0]).reshape(img.shape))
      ax3.set_title('first spatial PC')
      plt.show()
      

      【讨论】:

      • 我意识到我在这里有点晚了,但是 OP 特别要求提供一个避免奇异值分解的解决方案。
      • @Alex 我意识到这一点,但我相信 SVD 仍然是正确的方法。它应该足够快以满足 OP 的需求(我上面的示例,262144 维度在普通笔记本电脑上仅需约 7.5 秒),并且它比特征分解方法在数值上更稳定(请参阅下面的 dwf 评论)。我还注意到,接受的答案也使用 SVD!
      • 我不同意 SVD 是要走的路,我只是说答案并没有解决问题,因为问题已经说明了。不过,这是一个很好的答案,干得好。
      • @Alex 够公平的。我认为这是XY problem 的另一个变体 - OP 说他不想要基于 SVD 的解决方案,因为他认为 SVD 会太慢,可能还没有尝试过。在这种情况下,我个人认为解释你将如何解决更广泛的问题更有帮助,而不是准确地以原始、狭窄的形式回答问题。
      • svd 已经返回s,按照文档的降序排列。 (也许 2012 年不是这样,但今天是)
      【解决方案4】:

      你可以很容易地使用scipy.linalg“滚动”你自己的(假设一个预先居中的数据集data):

      covmat = data.dot(data.T)
      evs, evmat = scipy.linalg.eig(covmat)
      

      那么evs是你的特征值,evmat是你的投影矩阵。

      如果要保留d 维度,请使用第一个d 特征值和第一个d 特征向量。

      鉴于scipy.linalg 有分解和numpy 矩阵乘法,你还需要什么?

      【讨论】:

      • cov 矩阵为 np.dot(data.T,data,out=covmat),其中数据必须为中心矩阵。
      • 您应该查看@dwf 对this answer 的评论,了解在协方差矩阵上使用eig() 的危险。
      【解决方案5】:

      【讨论】:

      【解决方案6】:

      你可以使用sklearn:

      import sklearn.decomposition as deco
      import numpy as np
      
      x = (x - np.mean(x, 0)) / np.std(x, 0) # You need to normalize your data first
      pca = deco.PCA(n_components) # n_components is the components number after reduction
      x_r = pca.fit(x).transform(x)
      print ('explained variance (first %d components): %.2f'%(n_components, sum(pca.explained_variance_ratio_)))
      

      【讨论】:

      • 赞成,因为这对我很有效 - 我有超过 460 个维度,即使 sklearn 使用 SVD 并且问题要求非 SVD,我认为 460 个维度可能是可以的。
      • 您可能还想删除具有恒定值 (std=0) 的列。为此,您应该使用: remove_cols = np.where(np.all(x == np.mean(x, 0), 0))[0] 然后 x = np.delete(x, remove_cols, 1)跨度>
      【解决方案7】:

      您不需要全奇异值分解(SVD)计算所有特征值和特征向量,并且可以对大矩阵禁止。 scipy及其稀疏模块提供了在稀疏和密集矩阵上工作的通用线性Algrebra功能,其中有Eig *家族功能:

      http://docs.scipy.org/doc/scipy/reference/sparse.linalg.html#matrix-factorizations

      Scikit-learn提供Python PCA implementation,目前只支持密集的矩阵。

      时间:

      In [1]: A = np.random.randn(1000, 1000)
      
      In [2]: %timeit scipy.sparse.linalg.eigsh(A)
      1 loops, best of 3: 802 ms per loop
      
      In [3]: %timeit np.linalg.svd(A)
      1 loops, best of 3: 5.91 s per loop
      

      【讨论】:

      • 不是真正的比较,因为你仍然需要计算协方差矩阵。此外,它可能只值得使用稀疏的LINALG用于非常大的矩阵的东西,因为它似乎非常慢,以构建密集矩阵的稀疏矩阵。例如,eigsh实际上比Nonsparse矩阵为987654327 = 987654327。 scipy.sparse.linalg.svdsnumpy.linalg.svd相同是真的。如果矩阵变得真正巨大,我总是将SVD与特征值分解过来的@DWF提到的原因。 span>
      • 您不需要计算密集矩阵的稀疏矩阵。在SPARSE.LINALG模块中提供的算法仅依赖于通过操作员对象的MATVEC方法依赖于矩阵向量乘法操作。对于密集的矩阵,这只是matvec = dot(a,x)的东西。出于同样的原因,您不需要计算协方差矩阵,但仅提供用于a的操作点(a.t,dot(a,x))。 span>
      • 啊,现在我看到稀疏与nonsparse方法的相对速度取决于矩阵的大小。如果我使用你的例子是a为1000 * 1000矩阵,那么eigshsvdseighsvd @×3倍〜3,但如果一个较小,请说100 * 100,那么eighsvd分别以〜4和〜1.5的因素更快。虽然,t仍然会在稀疏的特征值分解上使用稀疏的SVD。 span>
      • 实际上,我认为我偏向大矩阵。对我来说,大矩阵更像是10¼*10¼比1000 * 1000。在这些情况下,您常常无法存储协方差矩阵... span>
      【解决方案8】:

      你可能看看MDP

      我没有机会自己测试它,但我已经完全购买了PCA功能。

      【讨论】:

      • MDP,看起来不像最好的解决方案。 span>
      • 最新更新是从09.03.2016,但请注意,IR只是错误修复版本:Note that from this release MDP is in maintenance mode. 13 years after its first public release, MDP has reached full maturity and no new features are planned in the future. span>
      【解决方案9】:

      SVD 应该适用于 460 维。在我的 Atom 上网本上大约需要 7 秒。 eig() 方法需要 更多 时间(它应该使用更多的浮点运算)并且几乎总是不太准确。

      如果您的示例少于 460 个,那么您要做的是将散布矩阵对角化 (x - datamean)^T(x - mean),假设您的数据点是列,然后左乘以 (x -数据平均值)。在维度多于数据的情况下,可能会更快。

      【讨论】:

      • 当你的维度多于数据时,你能更详细地描述这个技巧吗?
      • 基本上你假设特征向量是数据向量的线性组合。参见 Sirovich (1987)。 “湍流和连贯结构的动力学。”
      【解决方案10】:

      我只是读完了Machine Learning: An Algorithmic Perspective。本书中的所有代码示例由Python(几乎使用numpy)编写。 chatper10.2 Principal Components Analysis的代码SN-P可能值得一读。它使用numpy.linalg.eig。
      顺便说一下,我认为SVD可以很好地处理460 * 460维度。在一个非常旧的PC上使用Numpy / Scipy.linalg.svd计算了6500 * 6500 SVD:Pentium III 733MHz。要诚实地,脚本需要很多内存(约1.xg)和大量时间(大约30分钟)来获得SVD结果。 但我认为在现代PC上的460 * 460不会是一个大问题,除非你需要SVD大量的次数。

      【讨论】:

      • 当您只需使用SVD()时,您应该永远不会在协方差矩阵上使用EIG()。根据您计划使用多少组件和数据矩阵的大小,前者引入的数值错误(它做得更多浮点操作)可能变得显着。出于同样的原因,如果您真正感兴趣的是矢量或矩阵,则不应该明确地用inv()明确地反转矩阵;你应该使用解决()。 span>
      【解决方案11】:

      Here 是另一个使用 numpy、scipy 和 C 扩展的 Python PCA 模块实现。该模块使用 C 语言实现的 SVD 或 NIPALS(非线性迭代部分最小二乘)算法执行 PCA。

      【讨论】:

        猜你喜欢
        • 2012-10-24
        • 2018-12-06
        • 2013-03-31
        • 1970-01-01
        • 2013-08-24
        • 2014-04-08
        • 2019-09-29
        相关资源
        最近更新 更多