Python의 주요 구성 요소 분석


112

차원 축소를 위해 주성분 분석 (PCA)을 사용하고 싶습니다. numpy 또는 scipy가 이미 가지고 numpy.linalg.eigh있습니까? 아니면을 사용하여 직접 굴려야 합니까?

입력 데이터가 매우 높은 차원 (~ 460 차원)이기 때문에 SVD (특이 값 분해)를 사용하고 싶지 않기 때문에 SVD가 공분산 행렬의 고유 벡터를 계산하는 것보다 느릴 것이라고 생각합니다.

나는 언제 어떤 방법을 사용할 지에 대해 이미 올바른 결정을 내리고 내가 모르는 다른 최적화를 수행하는 미리 만들어진 디버그 된 구현을 찾고 싶었습니다.

답변:


28

MDP를 살펴볼 수 있습니다 .

직접 테스트 할 기회가 없었지만 PCA 기능을 위해 정확히 북마크했습니다.


8
MDP는 2012 년 이후로 유지되지 않았고 최상의 솔루션처럼 보이지 않습니다.
Marc Garcia

최신 업데이트는 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.
Gabriel

65

몇 달 후, 여기에 소규모 학급 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

여기에 이미지 설명 입력


3
Fyinfo, 2011 년 1 월 C. Caramanis의 Robust PCA 에 대한 훌륭한 강연이 있습니다 .
denis

이 코드가 해당 이미지 (Iris PCA)를 출력합니까? 그렇지 않은 경우 해당 이미지가 될 대체 솔루션을 게시 할 수 있습니다. 나는 파이썬에서 처음이기 때문에이 코드를 C ++로 변환하는 데 약간의 어려움이있는 IM :)
Orvyl

44

PCA 사용 numpy.linalg.svd은 매우 쉽습니다. 다음은 간단한 데모입니다.

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()

2
나는 여기에 조금 늦었다는 것을 알고 있지만 OP 특이 값 분해 를 피하는 솔루션을 구체적으로 요청했습니다 .
Alex A.

1
@Alex 나는 그것을 알고 있지만 SVD가 여전히 올바른 접근 방식이라고 확신합니다. OP의 요구에 충분히 빠르며 (위의 예, 262144 치수는 일반 랩톱에서 7.5 초 밖에 걸리지 않음) 고유 분해 방법 (아래 dwf의 설명 참조)보다 훨씬 더 수치 적으로 안정적입니다. 또한 허용되는 답변은 SVD도 사용합니다!
ali_m

나는 SVD가 갈 길이라는 것에 동의하지 않는다. 나는 단지 질문이 언급 된대로 대답이 질문을 다루지 않는다고 말하고 있었다. 그래도 좋은 대답입니다.
Alex A.

5
@Alex 충분히. 나는 이것이 XY 문제 의 또 다른 변형이라고 생각 합니다. OP는 SVD가 너무 느릴 것이라고 생각 했기 때문에 SVD 기반 솔루션을 원하지 않는다고 말했습니다 . 이와 같은 경우에는 원래의 좁은 형식으로 질문에 정확하게 대답하는 것보다 더 넓은 문제를 어떻게 해결할 것인지 설명하는 것이 개인적으로 더 도움이된다고 생각합니다.
ali_m 2015

svds문서가 진행되는 한 이미 내림차순으로 정렬되어 반환 됩니다. (아마도 이것은 2012 년의 경우 아니었지만, 오늘은)
에티엔 느 Bruines

34

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_)))

이것이 나에게 잘 작동하기 때문에 Upvoted-460 차원 이상이 있으며 sklearn이 SVD를 사용하고 질문이 비 SVD를 요청했지만 460 차원이 괜찮을 것이라고 생각합니다.
Dan Stowell 2013 년

상수 값 (std = 0)이있는 열을 제거 할 수도 있습니다. 이를 위해 다음을 사용해야합니다. remove_cols = np.where (np.all (x == np.mean (x, 0), 0)) [0] 그리고 x = np.delete (x, remove_cols, 1)
Noam 펠 레드


14

SVD는 460 차원에서 잘 작동합니다. Atom 넷북에서는 약 7 초가 걸립니다. eig () 메서드는 더 많이 걸립니다 시간 (정확한 부동 소수점 연산을 더 많이 사용합니다) 거의 항상 정확도가 떨어집니다.

예제가 460 개 미만인 경우 데이터 포인트가 열이라고 가정하고 산포 행렬 (x-datamean) ^ T (x-mean)을 대각 화 한 다음 (x-datamean)을 왼쪽으로 곱합니다. 즉 수도 빠르게 데이터보다 크기를 가지고있는 경우에합니다.


데이터보다 차원이 더 많을 때이 트릭을 더 자세히 설명 할 수 있습니까?
mrgloom 2014 년

1
기본적으로 고유 벡터가 데이터 벡터의 선형 조합이라고 가정합니다. Sirovich (1987) 참조. "일관된 구조의 난류와 역학."
dwf

11

다음을 사용하여 쉽게 "롤링"할 수 있습니다 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)이며, 여기서 데이터는 중심 행렬이어야합니다.
mrgloom 2014 년

2
공분산 행렬 에서 사용하는 위험에 대한 이 답변에 대한 @dwf의 의견을 살펴보아야 eig()합니다.
Alex A.

8

방금 Machine Learning : An Algorithmic Perspective 책을 읽었습니다 . 이 책의 모든 코드 예제는 Python (그리고 거의 Numpy)에 의해 작성되었습니다. chatper10.2 Principal Components Analysis 의 코드 스 니펫은 읽을만한 가치가 있습니다. numpy.linalg.eig를 사용합니다.
그건 그렇고, SVD는 460 * 460 치수를 매우 잘 처리 할 수 ​​있다고 생각합니다. 아주 오래된 PC에서 numpy / scipy.linalg.svd로 6500 * 6500 SVD를 계산했습니다 : Pentium III 733mHz. 솔직히 스크립트는 SVD 결과를 얻기 위해 많은 메모리 (약 1.xG)와 많은 시간 (약 30 분)이 필요합니다. 하지만 현대 PC의 460 * 460은 SVD를 여러 번 필요로하지 않는 한 큰 문제가되지 않을 것이라고 생각합니다.


28
단순히 svd ()를 사용할 수있는 경우 공분산 행렬에서 eig ()를 사용해서는 안됩니다. 사용하려는 구성 요소의 수와 데이터 매트릭스의 크기에 따라 전자에서 발생하는 수치 오류 (더 많은 부동 소수점 연산 수행)가 중요해질 수 있습니다. 같은 이유로 당신이 정말로 관심이있는 것이 벡터 나 행렬의 역배라면 inv ()로 행렬을 명시 적으로 반전해서는 안됩니다. 대신 solve ()를 사용해야합니다.
dwf

5

모든 고유 값과 고유 벡터를 계산하므로 전체 SVD (특이 값 분해)가 필요하지 않으며 큰 행렬의 경우 금지 될 수 있습니다. scipy 및 희소 모듈은 희소 및 조밀 행렬 모두에서 작동하는 일반적인 선형 algrebra 함수를 제공하며, 그중 eig * 함수 계열이 있습니다.

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

Scikit-learn 은 현재 밀도가 높은 행렬 만 지원 하는 Python PCA 구현 을 제공합니다 .

타이밍 :

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

1
여전히 공분산 행렬을 계산해야하므로 공정한 비교는 아닙니다. 또한 밀도가 높은 행렬에서 희소 행렬을 구성하는 것이 매우 느리기 때문에 매우 큰 행렬에 대해 희소 선형 항목을 사용하는 것이 좋습니다. 예를 들어, eigsh실제로 eigh는 비 희소 행렬 보다 4 배 더 느립니다 . 동일은 마찬가지입니다 scipy.sparse.linalg.svdsnumpy.linalg.svd. @dwf가 언급 한 이유로 항상 고유 값 분해보다 SVD를 사용하고 행렬이 정말 커지면 SVD의 희소 버전을 사용합니다.
ali_m

2
조밀 한 행렬에서 희소 행렬을 계산할 필요가 없습니다. sparse.linalg 모듈에서 제공되는 알고리즘은 Operator 객체의 matvec 메서드를 통한 행렬 벡터 곱셈 연산에만 의존합니다. 조밀 한 행렬의 경우 이는 matvec = dot (A, x)와 같습니다. 같은 이유로, 당신은 공분산 행렬을 계산 할 필요가 없습니다 만 A의 동작 점 (AT, 점 (A, X))를 제공하기 위해
니콜라스 BARBEY

아, 이제 희소 방법과 비 희소 방법의 상대 속도가 행렬의 크기에 따라 달라진다는 것을 알 수 있습니다. 나는 당신의 예를 사용하는 경우 A는 1000 * 1000 매트릭스는 다음입니다 eigshsvds빠르게보다 eighsvd~ 3의 요인에 의해, 그러나이 작은 경우, 다음 100 * 100, 말 eighsvd각각 ~ 4의 요인과 ~ 1.5 빠르다 . T는 여전히 희소 고유 값 분해보다 희소 SVD를 사용합니다.
ali_m 2012 년

2
사실, 나는 큰 행렬에 편향되어 있다고 생각합니다. 나에게 큰 행렬은 10⁶ * 10⁶ 그 경우 * 1000 1000보다 자주도 ... 공분산 행렬을 저장할 수 없습니다 더처럼
니콜라스 BARBEY

4

다음 은 numpy, scipy 및 C-extensions를 사용하는 파이썬 용 PCA 모듈의 또 다른 구현입니다. 모듈은 C로 구현 된 SVD 또는 NIPALS (Nonlinear Iterative Partial Least Squares) 알고리즘을 사용하여 PCA를 수행합니다.


0

3D 벡터로 작업하는 경우 toolbelt vg를 사용하여 SVD를 간결하게 적용 할 수 있습니다 . numpy 위에 밝은 레이어입니다.

import numpy as np
import vg

vg.principal_components(data)

첫 번째 주요 구성 요소 만 원하는 경우 편리한 별칭도 있습니다.

vg.major_axis(data)

나는 마지막 스타트 업에서 라이브러리를 만들었는데, NumPy에서 장황하거나 불투명 한 단순한 아이디어와 같은 용도로 동기를 부여 받았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.