【发布时间】:2010-12-16 09:07:48
【问题描述】:
我想使用主成分分析 (PCA) 进行降维。 numpy 或 scipy 是否已经拥有它,还是我必须使用 numpy.linalg.eigh 自己推出?
我不只是想使用奇异值分解 (SVD),因为我的输入数据非常高维(约 460 维),所以我认为 SVD 会比计算协方差矩阵的特征向量要慢。
我希望找到一个预制的、经过调试的实现,它已经为何时使用哪种方法做出了正确的决定,并且可能会进行我不知道的其他优化。
【问题讨论】:
我想使用主成分分析 (PCA) 进行降维。 numpy 或 scipy 是否已经拥有它,还是我必须使用 numpy.linalg.eigh 自己推出?
我不只是想使用奇异值分解 (SVD),因为我的输入数据非常高维(约 460 维),所以我认为 SVD 会比计算协方差矩阵的特征向量要慢。
我希望找到一个预制的、经过调试的实现,它已经为何时使用哪种方法做出了正确的决定,并且可能会进行我不知道的其他优化。
【问题讨论】:
如果您正在使用 3D 矢量,则可以使用工具带 vg 简洁地应用 SVD。它是 numpy 之上的一个轻层。
import numpy as np
import vg
vg.principal_components(data)
如果你只想要第一个主成分,还有一个方便的别名:
vg.major_axis(data)
我在上次创业时创建了这个库,它的动机是这样的:在 NumPy 中冗长或不透明的简单想法。
【讨论】:
几个月后,这是一个小类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
【讨论】:
使用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()
【讨论】:
svd 已经返回s,按照文档的降序排列。 (也许 2012 年不是这样,但今天是)
你可以很容易地使用scipy.linalg“滚动”你自己的(假设一个预先居中的数据集data):
covmat = data.dot(data.T)
evs, evmat = scipy.linalg.eig(covmat)
那么evs是你的特征值,evmat是你的投影矩阵。
如果要保留d 维度,请使用第一个d 特征值和第一个d 特征向量。
鉴于scipy.linalg 有分解和numpy 矩阵乘法,你还需要什么?
【讨论】:
eig() 的危险。
【讨论】:
你可以使用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_)))
【讨论】:
您不需要全奇异值分解(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
【讨论】:
eigsh实际上比Nonsparse矩阵为987654327 = 987654327。 scipy.sparse.linalg.svds与numpy.linalg.svd相同是真的。如果矩阵变得真正巨大,我总是将SVD与特征值分解过来的@DWF提到的原因。 span>
eigsh和svds和eigh和svd @×3倍〜3,但如果一个较小,请说100 * 100,那么eigh和svd分别以〜4和〜1.5的因素更快。虽然,t仍然会在稀疏的特征值分解上使用稀疏的SVD。 span>
你可能看看MDP。
我没有机会自己测试它,但我已经完全购买了PCA功能。
【讨论】:
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>
SVD 应该适用于 460 维。在我的 Atom 上网本上大约需要 7 秒。 eig() 方法需要 更多 时间(它应该使用更多的浮点运算)并且几乎总是不太准确。
如果您的示例少于 460 个,那么您要做的是将散布矩阵对角化 (x - datamean)^T(x - mean),假设您的数据点是列,然后左乘以 (x -数据平均值)。在维度多于数据的情况下,可能会更快。
【讨论】:
我只是读完了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大量的次数。
【讨论】:
Here 是另一个使用 numpy、scipy 和 C 扩展的 Python PCA 模块实现。该模块使用 C 语言实现的 SVD 或 NIPALS(非线性迭代部分最小二乘)算法执行 PCA。
【讨论】: