Аналіз основних компонентів у Python


112

Я б хотів використовувати аналіз основних компонентів (PCA) для зменшення розмірності. Чи є у неї нумепі або саупі, або мені доведеться самостійно катати numpy.linalg.eigh?

Я не просто хочу використовувати розклад сингулярного значення (SVD), оскільки мої вхідні дані досить великі (~ 460 розмірів), тому я думаю, що SVD буде повільнішим, ніж обчислення власних векторів матриці коваріації.

Я сподівався знайти попередньо налагоджену реалізацію, яка вже приймає правильні рішення щодо того, коли використовувати який метод, а який, можливо, робить інші оптимізації, про які я не знаю.

Відповіді:


28

Ви можете подивитися на MDP .

У мене не було можливості протестувати його, але я зробив закладки саме для функціональності PCA.


8
MDP не підтримується з 2012 року, не виглядає як найкраще рішення.
Марк Гарсія

Останнє оновлення - з 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.
Габріель

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, є відмінний розмову на Robust PCA К. Caramanis, січень 2011
ДЕНИС

цей код видасть це зображення (Iris PCA)? Якщо ні, чи можете ви розмістити альтернативне рішення, у якому вийшло б це зображення. У ІМ виникають певні труднощі в перетворенні цього коду на c ++, оскільки я новачок у python :)
Орвіль,

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
Я усвідомлюю, що тут я трохи запізнююся, але ОП спеціально вимагає рішення, що дозволяє уникнути сингулярного розкладання значення.
Олексій А.

1
@ Алекс я це розумію, але переконаний, що SVD все ще є правильним підходом. Він повинен бути досить швидким для потреб ОП (мій приклад вище, з розмірами 262144 на звичайному ноутбуці займає лише ~ 7,5 сек), і він набагато більш стабільний, ніж метод ейгендекомпозиції (див. Коментар dwf нижче). Я також зазначу, що прийнята відповідь використовує і SVD!
ali_m

Я не погоджуюсь з тим, що SVD - це шлях, я просто говорив, що відповідь не стосується питання, оскільки питання задано. Хоча це гарна відповідь, приємна робота.
Олексій А.

5
@ Алекс досить справедливо. Я думаю, що це ще один варіант проблеми XY - ОП заявив, що не хоче рішення на базі SVD, оскільки вважав, що SVD буде надто повільним, ймовірно, ще не намагаючись його. У таких випадках я особисто вважаю, що корисніше пояснити, як би ви вирішили більш широку проблему, а не відповідати на питання саме в оригінальному, вужчому вигляді.
ali_m

svdвже повертається sяк відсортовано у порядку зменшення, наскільки йде документація. (Можливо, це було не так у 2012 році, але сьогодні це так)
Етьєн Брюйнз

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

Запропоновано, оскільки це добре працює для мене - у мене більше 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)
Noam Peled


14

SVD повинен чудово працювати з 460 розмірами. На мою нетбук Atom це займає близько 7 секунд. Метод eig () займає більше часу (як і слід, він використовує більше операцій з плаваючою точкою) і майже завжди буде менш точним.

Якщо у вас менше 460 прикладів, то те, що ви хочете зробити, - це діагоналізація матриці розсіювання (x - datamean) ^ T (x - середнє значення), припускаючи, що ваші точки даних є стовпцями, а потім лівою множенням на (x - datamean). Це може бути швидше у випадку, коли у вас більше вимірів, ніж даних.


чи можете ви детальніше описати цей трюк, коли у вас більше вимірів, ніж даних?
mrgloom

1
В основному ви припускаєте, що власні вектори є лінійними комбінаціями векторів даних. Див. Сірович (1987). "Турбулентність та динаміка когерентних структур".
dwf

11

Ви можете досить легко "скочувати" свої власні, використовуючи scipy.linalg(за умови попереднього центру даних data):

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

Тоді evsваші власні значення іevmat ваша матриця проекцій.

Якщо ви хочете зберегти dпараметри, скористайтеся першими dвласними значеннями та першимиd власні вектори.

З огляду на те, що scipy.linalgє розкладання та нумерування множин матриці, що ще потрібно?


cov матриця - np.dot (data.T, data, out = covmat), де дані повинні бути центровані матрицею.
mrgloom

2
Слід поглянути на коментар @ dwf щодо цієї відповіді щодо небезпеки використання eig()на коваріаційній матриці.
Олексій А.

8

Я просто закінчую читання книги " Машинне навчання: алгоритмічна перспектива" . Усі приклади коду в книзі були написані Python (і майже з Numpy). Фрагмент коду chatper10.2 Аналіз основних компонентів, можливо, варто прочитати. Він використовує numpy.linalg.eig.
До речі, я думаю, що SVD дуже добре справляється з розмірами 460 * 460. Я розраховував 6500 * 6500 SVD з numpy / scipy.linalg.svd на дуже старому ПК: Pentium III 733mHz. Якщо чесно, скрипту потрібно багато пам'яті (близько 1.xG) і багато часу (близько 30 хвилин), щоб отримати результат SVD. Але я думаю, що 460 * 460 на сучасному ПК не буде великою проблемою, якщо вам не доведеться робити SVD величезну кількість разів.


28
Ніколи не слід використовувати eig () на матриці коваріації, коли можна просто використовувати svd (). Залежно від кількості компонентів, які ви плануєте використовувати та розміру вашої матриці даних, числова помилка, введена першою (вона робить більше операцій з плаваючою комою), може стати значною. З тієї ж причини ви ніколи не повинні явно інвертувати матрицю з inv (), якщо те, що вас насправді цікавить, - це зворотні часи вектор або матриця; вам слід скористатися натомість Solution ().
dwf

5

Вам не потрібна повна сингулярна величина декомпозиції (SVD), вона обчислює всі власні значення та власні вектори, і це може бути непосильним для великих матриць. вуха та його розріджений модуль забезпечують загальні функції лінійної алгребри, що працюють як на рідких, так і на щільних матрицях, серед яких є сімейство функцій eig *:

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

Scikit-learn пропонує реалізацію PCA Python, яка наразі підтримує лише щільні матриці.

Терміни:

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насправді на ~ 4 рази повільніше, ніж eighдля матриць без розбору. Те саме стосується і scipy.sparse.linalg.svdsпроти numpy.linalg.svd. Я завжди ходив би з SVD над власним значенням розкладання з причин, про які згадував @dwf, і, можливо, використовувати рідкісну версію SVD, якщо матриці дійсно величезні.
ali_m

2
Вам не потрібно обчислювати рідкісні матриці з щільних матриць. Алгоритми, надані в модулі sparse.linalg, покладаються лише на операцію множення матричного вектора через метод matvec об'єкта Operator. Для щільних матриць це просто щось на зразок matvec = крапка (A, x). З цієї ж причини вам не потрібно обчислювати матрицю коваріації, а лише надати операційну крапку (AT, крапка (A, x)) для А.
Ніколяс Барбі

Ах, тепер я бачу, що відносна швидкість методів розрідженого проти нерозбірного залежить від розміру матриці. Якщо я використовую ваш приклад , де А являє собою матрицю 1000 * 1000 , то eigshі svdsшвидше , ніж eighта svdна коефіцієнт ~ 3, але якщо А менше, скажімо , 100 * 100, тоeigh і svdбільш швидкі факторами ~ 4 і ~ 1,5 відповідно . Тим не менш, все-таки використовуватиме розріджений SVD над розрідженим власним значенням розкладання.
ali_m

2
Дійсно, я думаю, що я упереджений до великих матриць. Для мене великі матриці більше схожі на 10⁶ * 10⁶, ніж 1000 * 1000. У такому випадку ви часто навіть не можете зберігати коваріаційні матриці ...
Nicolas Barbey

4

Ось ще одна реалізація модуля PCA для python з використанням numpy, scipy та C-розширень. Модуль здійснює PCA, використовуючи алгоритм SVD або NIPALS (нелінійні ітеративні часткові найменші квадрати), реалізований у C.


0

Якщо ви працюєте з 3D-векторами, ви можете застосовувати SVD стисло за допомогою інструментальної стрічки vg . Це легкий шар зверху нуме.

import numpy as np
import vg

vg.principal_components(data)

Також є зручний псевдонім, якщо ви хочете лише перший основний компонент:

vg.major_axis(data)

Я створив бібліотеку під час мого останнього запуску, де її мотивували такі використання: прості ідеї, які є багатослівними або непрозорими в NumPy.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.