Python'da temel bileşen analizi


112

Boyut azaltma için temel bileşen analizini (PCA) kullanmak istiyorum. Numpy veya scipy'de zaten var mı, yoksa kendimi numpy.linalg.eighmi kullanmalıyım ?

Girdi verilerim oldukça yüksek boyutlu olduğundan (~ 460 boyut) sadece tekil değer ayrıştırmasını (SVD) kullanmak istemiyorum, bu yüzden SVD'nin kovaryans matrisinin özvektörlerini hesaplamaktan daha yavaş olacağını düşünüyorum.

Hangi yöntemin ne zaman kullanılacağına dair doğru kararları zaten veren ve belki de bilmediğim diğer optimizasyonları yapan önceden hazırlanmış, hata ayıklanmış bir uygulama bulmayı umuyordum.

Yanıtlar:


28

MDP'ye bir göz atabilirsiniz .

Kendim test etme şansım olmadı, ancak tam olarak PCA işlevselliği için işaretledim.


8
MDP, 2012'den beri korunmuyor, en iyi çözüm gibi görünmüyor.
Marc Garcia

En son güncelleme 09.03.2016 tarihinde yapılmıştır, ancak ir'in yalnızca bir hata düzeltme sürümü olduğunu unutmayın: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

Aylar sonra, işte küçük bir sınıf PCA ve bir resim:

#!/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

görüntü açıklamasını buraya girin


3
Fyinfo, Robust PCA hakkında mükemmel bir konuşma var, C. Caramanis, Ocak 2011.
denis

bu kod o görüntüyü çıkaracak mı (Iris PCA)? Değilse, o görüntünün çıktığı alternatif bir çözüm gönderebilir misiniz? IM
python'da

44

PCA numpy.linalg.svdkullanımı çok kolaydır. İşte basit bir demo:

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
Burada biraz geciktiğimin farkındayım, ancak OP özellikle tekil değer ayrışımından kaçınan bir çözüm talep etti .
Alex A.

1
@Alex Bunun farkındayım, ancak SVD'nin hala doğru yaklaşım olduğuna ikna oldum. OP'nin ihtiyaçları için yeterince hızlı olması gerekir (yukarıdaki örneğim, 262144 boyutuyla normal bir dizüstü bilgisayarda yalnızca ~ 7,5 saniye sürer) ve sayısal olarak eigendecomposition yönteminden çok daha kararlıdır (aşağıdaki dwf'nin açıklamasına bakın). Ayrıca kabul edilen cevabın SVD kullandığını da not ediyorum!
ali_m

SVD'nin gidilecek yol olduğuna katılmıyorum, sadece cevabın soru belirtildiği gibi soruyu ele almadığını söylüyordum. Yine de iyi bir cevap, güzel iş.
Alex A.

5
@Alex Yeterince adil. Bence bu, XY probleminin başka bir çeşidi - OP, SVD'nin muhtemelen henüz denemeden çok yavaş olacağını düşündüğü için SVD tabanlı bir çözüm istemediğini söyledi . Bu gibi durumlarda, soruyu tam olarak orijinal, daha dar biçiminde yanıtlamaktansa, daha geniş bir sorunu nasıl çözeceğinizi açıklamanın kişisel olarak daha yararlı olacağını düşünüyorum.
ali_m

svdzaten sdokümantasyona göre azalan sırada sıralı olarak geri döner . (Belki de 2012'de durum böyle değildi, ama bugün öyle)
Etienne Bruines

34

Sklearn'ı kullanabilirsiniz:

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

Bu benim için iyi çalıştığı için olumlu oy verildi - 460'tan fazla boyutum var ve sklearn SVD kullanıyor ve soru SVD olmayan bir soru talep etse de 460 boyutun uygun olacağını düşünüyorum.
Dan Stowell

Sabit değerli sütunları da kaldırmak isteyebilirsiniz (std = 0). Bunun için şunu kullanmalısınız: remove_cols = np.where (np.all (x == np.mean (x, 0), 0)) [0] Ve sonra x = np.delete (x, remove_cols, 1)
Noam Peled


14

SVD, 460 boyutta düzgün çalışmalıdır. Atom netbook'umda yaklaşık 7 saniye sürüyor. Eig () yöntemi daha fazlasını alır zaman (olması gerektiği gibi, daha fazla kayan nokta işlemi kullanır) ve neredeyse her zaman daha az doğru olacaktır.

460'tan daha az örneğiniz varsa, yapmak istediğiniz şey dağılım matrisini köşegenleştirmektir (x - veri adı) ^ T (x - ortalama), veri noktalarınızın sütun olduğunu varsayarak ve ardından (x - veri adı) ile sola çarparak. Veriden daha fazla boyuta sahip olduğunuz durumda bu daha hızlı olabilir .


Veriden çok boyutunuz olduğunda bu numarayı daha ayrıntılı olarak açıklayabilir misiniz?
mrgloom

1
Temel olarak, özvektörlerin veri vektörlerinin doğrusal kombinasyonları olduğunu varsayarsınız. Bkz. Sirovich (1987). "Türbülans ve uyumlu yapıların dinamikleri."
dwf

11

Aşağıdakileri kullanarak scipy.linalg(önceden merkezlenmiş bir veri kümesi varsayılarak) kendi isteğinizi kolayca "yuvarlayabilirsiniz" data:

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

Öyleyse evsözdeğerleriniz ve evmatprojeksiyon matrisinizdir.

dBoyutları korumak istiyorsanız , ilk dözdeğerleri ve ilk dözvektörleri kullanın.

scipy.linalgMatris çarpımlarının ayrışması ve uyuşması göz önüne alındığında, başka neye ihtiyacınız var?


cov matrisi np.dot'dur (data.T, data, out = covmat), burada veriler ortalanmış matris olmalıdır.
mrgloom

2
Bir kovaryans matrisi üzerinde kullanmanın tehlikeleri için @ dwf'nin bu cevapla ilgili yorumuna bir göz atmalısınız eig().
Alex A.

8

Makine Öğrenimi: Algoritmik Bir Perspektif kitabını okumayı yeni bitirdim . Kitaptaki tüm kod örnekleri Python (ve neredeyse Numpy ile) tarafından yazılmıştır. Chatper10.2 Principal Components Analysis kod parçacığı okumaya değer olabilir. Numpy.linalg.eig kullanır.
Bu arada, SVD'nin 460 * 460 boyutları çok iyi işleyebileceğini düşünüyorum. Çok eski bir bilgisayarda numpy / scipy.linalg.svd ile 6500 * 6500 SVD hesapladım: Pentium III 733mHz. Dürüst olmak gerekirse, komut dosyasının SVD sonucunu almak için çok fazla belleğe (yaklaşık 1.xG) ve çok zamana (yaklaşık 30 dakika) ihtiyacı vardır. Ancak bence modern bir PC'de çok sayıda SVD yapmanız gerekmedikçe büyük bir sorun olmayacak.


28
Svd () 'yi basitçe kullanabiliyorsanız asla kovaryans matrisinde eig () kullanmamalısınız. Kullanmayı planladığınız bileşen sayısına ve veri matrisinizin boyutuna bağlı olarak, birincisinin getirdiği sayısal hata (daha fazla kayan nokta işlemi yapar) önemli hale gelebilir. Aynı nedenden ötürü, gerçekten ilgilendiğiniz şey bir vektörün veya matrisin ters zamanlarıysa, inv () ile bir matrisi asla açıkça ters çevirmemelisiniz; bunun yerine çözme () kullanmalısınız.
dwf

5

Tüm özdeğerleri ve özvektörleri hesapladığı ve büyük matrisler için engelleyici olabileceği için tam Tekil Değer Ayrıştırmasına (SVD) ihtiyacınız yoktur. scipy ve onun seyrek modülü, hem seyrek hem de yoğun matrisler üzerinde çalışan jenerik doğrusal algoritma fonksiyonları sağlar; bunların arasında eig * fonksiyon ailesi bulunur:

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

Scikit-learn , şimdilik yalnızca yoğun matrisleri destekleyen bir Python PCA uygulaması sağlar .

Zamanlamalar:

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
Hala kovaryans matrisini hesaplamanız gerektiğinden, pek adil bir karşılaştırma değil. Ayrıca, yoğun matrislerden seyrek matrisler oluşturmak oldukça yavaş göründüğünden, muhtemelen çok büyük matrisler için seyrek linalg maddesini kullanmaya değer. örneğin, eigshaslında eighayrık olmayan matrislerden ~ 4 kat daha yavaştır . Aynı şey scipy.sparse.linalg.svdskarşı için de geçerlidir numpy.linalg.svd. @Dwf'nin bahsettiği nedenlerden ötürü özdeğer ayrıştırması üzerinde her zaman SVD ile giderdim ve belki de matrisler çok büyük olursa SVD'nin seyrek versiyonunu kullanırdım.
ali_m

2
Yoğun matrislerden seyrek matrisleri hesaplamanıza gerek yoktur. Sparse.linalg modülünde sağlanan algoritmalar, Operator nesnesinin matvec yöntemi aracılığıyla yalnızca matris vektörü çarpma işlemine dayanır. Yoğun matrisler için bu, matvec = nokta (A, x) gibi bir şeydir. Aynı nedenle, kovaryans matrisini hesaplamanız gerekmez, yalnızca A için işlem noktasını (AT, nokta (A, x)) sağlamanız gerekir.
Nicolas Barbey

Ah, şimdi görüyorum ki seyrek ve ayrık olmayan yöntemlerin göreceli hızı matrisin boyutuna bağlı. Ben senin örneğini kullanırsanız A 1000 x 1000 matris sonra nerede eigshve svdsdaha hızlı olan eighve svd~ 3 kat, ama daha küçük bir ise, o zaman 100 * 100, söylemek eighve svdsırasıyla ~ 4 faktörler ve ~ 1.5 ile daha hızlı olan . Yine de T, seyrek özdeğer ayrışmasına göre seyrek SVD kullanacaktır.
ali_m

2
Doğrusu, büyük matrislere eğilim gösterdiğimi düşünüyorum. Bana göre büyük matrisler, 1000 * 1000'den 10⁶ * 10 like gibidir. Bu durumda, kovaryans matrislerini bile saklayamazsınız ...
Nicolas Barbey

4

İşte numpy, scipy ve C-uzantılarını kullanan python için bir PCA modülünün başka bir uygulaması. Modül, bir SVD veya C'de uygulanan NIPALS (Doğrusal Olmayan Yinelemeli Kısmi En Küçük Kareler) algoritmasını kullanarak PCA'yı gerçekleştirir.


0

3D vektörler ile çalışıyorsanız, size kısaca toolbelt kullanarak SVD uygulayabilirsiniz VG . Uyuşukluğun üstünde hafif bir tabakadır.

import numpy as np
import vg

vg.principal_components(data)

Yalnızca ilk ana bileşeni istiyorsanız, uygun bir takma ad da vardır:

vg.major_axis(data)

Kütüphaneyi son girişimde oluşturdum, bu tür kullanımlarla motive edildi: NumPy'de ayrıntılı veya opak olan basit fikirler.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.