Phân tích thành phần chính bằng Python


112

Tôi muốn sử dụng phân tích thành phần chính (PCA) để giảm kích thước. Đã có numpy hoặc scipy chưa, hay tôi phải tự cuộn bằng cách sử dụng numpy.linalg.eigh?

Tôi không chỉ muốn sử dụng phân rã giá trị đơn lẻ (SVD) vì dữ liệu đầu vào của tôi khá cao (~ 460 chiều), vì vậy tôi nghĩ SVD sẽ chậm hơn so với việc tính toán các ký hiệu riêng của ma trận hiệp phương sai.

Tôi đã hy vọng tìm thấy một triển khai đã được gỡ lỗi, được tạo sẵn đã đưa ra quyết định đúng đắn về thời điểm sử dụng phương pháp nào và phương pháp nào có thể thực hiện các tối ưu hóa khác mà tôi không biết.

Câu trả lời:


28

Bạn có thể xem MDP .

Tôi chưa có cơ hội tự mình kiểm tra nó, nhưng tôi đã đánh dấu chính xác nó cho chức năng PCA.


8
MDP đã không được duy trì kể từ năm 2012, có vẻ như không phải là giải pháp tốt nhất.
Marc Garcia

Bản cập nhật mới nhất là từ ngày 09.03.2016, nhưng lưu ý rằng ir chỉ là bản phát hành sửa lỗi: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

Nhiều tháng sau, đây là một PCA lớp nhỏ và một hình ảnh:

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

nhập mô tả hình ảnh ở đây


3
Fyinfo, có một bài nói chuyện tuyệt vời về Robust PCA của C. Caramanis, tháng 1 năm 2011.
denis

mã này sẽ xuất ra hình ảnh đó (Iris PCA)? Nếu không, bạn có thể đăng một giải pháp thay thế, trong đó đầu ra sẽ là hình ảnh đó. IM có một số khó khăn trong việc chuyển đổi mã này vào c ++ vì tôi mới trong python :)
Orvyl

44

PCA sử dụng numpy.linalg.svdrất dễ dàng. Đây là một bản demo đơn giản:

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
Tôi nhận ra rằng tôi hơi muộn ở đây, nhưng OP đã yêu cầu một giải pháp đặc biệt để tránh phân hủy giá trị đơn lẻ.
Alex A.

1
@Alex Tôi nhận ra điều đó, nhưng tôi tin rằng SVD vẫn là cách tiếp cận đúng. Nó phải dễ dàng đủ nhanh cho nhu cầu của OP (ví dụ của tôi ở trên, với kích thước 262144 chỉ mất ~ 7,5 giây trên một máy tính xách tay thông thường) và nó ổn định hơn nhiều so với phương pháp phân tích eigendecomposition (xem nhận xét của dwf bên dưới). Tôi cũng lưu ý rằng câu trả lời được chấp nhận cũng sử dụng SVD!
ali_m

Tôi không đồng ý rằng SVD là con đường để đi, tôi chỉ nói rằng câu trả lời không giải quyết câu hỏi như câu hỏi đã nêu. Đó là một câu trả lời tốt, mặc dù, công việc tốt.
Alex A.

5
@Alex Đủ công bằng. Tôi nghĩ đây là một biến thể khác của vấn đề XY - OP nói rằng ông ấy không muốn có một giải pháp dựa trên SVD vì ông ấy nghĩ rằng SVD sẽ quá chậm, có thể là chưa thử. Trong những trường hợp như thế này, cá nhân tôi nghĩ sẽ hữu ích hơn nếu giải thích cách bạn giải quyết vấn đề rộng hơn, thay vì trả lời chính xác câu hỏi ở dạng ban đầu, hẹp hơn.
ali_m

svdđã trả về snhư được sắp xếp theo thứ tự giảm dần, theo như tài liệu hướng dẫn. (Có lẽ đây không phải là trường hợp của năm 2012, nhưng ngày nay đã xảy ra)
Etienne Bruines

34

Bạn có thể sử dụng 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_)))

Được ủng hộ vì điều này phù hợp với tôi - tôi có hơn 460 kích thước và mặc dù sklearn sử dụng SVD và câu hỏi được yêu cầu không phải SVD, tôi nghĩ rằng 460 thứ nguyên có thể là OK.
Dan Stowell

Bạn cũng có thể muốn xóa các cột có giá trị không đổi (std = 0). Đối với điều đó, bạn nên sử dụng: remove_cols = np.where (np.all (x == np.mean (x, 0), 0)) [0] Và sau đó x = np.delete (x, remove_cols, 1)
Noam Peled


14

SVD sẽ hoạt động tốt với 460 kích thước. Quá trình này mất khoảng 7 giây trên netbook Atom của tôi. Phương thức eig () tốn nhiều thời gian hơn (vì nó nên sử dụng nhiều phép toán dấu phẩy động hơn) và hầu như sẽ luôn kém chính xác hơn.

Nếu bạn có ít hơn 460 ví dụ thì điều bạn muốn làm là lập đường chéo ma trận phân tán (x - datamean) ^ T (x - mean), giả sử các điểm dữ liệu của bạn là cột, sau đó nhân trái với (x - datamean). Điều đó thể nhanh hơn trong trường hợp bạn có nhiều thứ nguyên hơn dữ liệu.


bạn có thể mô tả chi tiết hơn thủ thuật này khi bạn có nhiều thứ nguyên hơn dữ liệu không?
mrgloom

1
Về cơ bản, bạn giả định rằng các eigenvectors là sự kết hợp tuyến tính của các vectơ dữ liệu. Xem Sirovich (1987). "Sự hỗn loạn và động lực của cấu trúc mạch lạc."
dwf

11

Bạn có thể khá dễ dàng "cuộn" của riêng mình bằng cách sử dụng scipy.linalg(giả sử một tập dữ liệu được căn giữa data):

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

Sau đó evslà các giá trị riêng của bạn, và evmatlà ma trận chiếu của bạn.

Nếu bạn muốn giữ thứ dnguyên, hãy sử dụng các dgiá trị riêng đầu tiên và các giá trị riêng đầu tiên d.

Cho rằng scipy.linalgcó sự phân rã và tính toán các phép nhân ma trận, bạn cần gì nữa?


ma trận cov là np.dot (data.T, data, out = covmat), trong đó dữ liệu phải được căn giữa ma trận.
mrgloom

2
Bạn nên xem nhận xét của @ dwf về câu trả lời này để biết những nguy hiểm khi sử dụng eig()trên ma trận hiệp phương sai.
Alex A.

8

Tôi vừa đọc xong cuốn sách Machine Learning: An Algorithmic Perspective . Tất cả các ví dụ mã trong cuốn sách được viết bằng Python (và gần như bằng Numpy). Đoạn mã của Phân tích các thành phần chính của chatper10.2 có thể đáng để đọc. Nó sử dụng numpy.linalg.eig.
Nhân tiện, tôi nghĩ SVD có thể xử lý kích thước 460 * 460 rất tốt. Tôi đã tính toán một SVD 6500 * 6500 với numpy / scipy.linalg.svd trên một PC rất cũ: Pentium III 733mHz. Thành thật mà nói, script cần rất nhiều bộ nhớ (khoảng 1.xG) và rất nhiều thời gian (khoảng 30 phút) để có được kết quả SVD. Nhưng tôi nghĩ rằng 460 * 460 trên PC hiện đại sẽ không phải là một vấn đề lớn trừ khi bạn cần làm SVD một số lượng lớn.


28
Bạn không bao giờ nên sử dụng eig () trên ma trận hiệp phương sai khi bạn chỉ có thể sử dụng svd (). Tùy thuộc vào số lượng thành phần bạn định sử dụng và kích thước của ma trận dữ liệu của bạn, lỗi số được đưa ra bởi cái trước (nó thực hiện nhiều phép toán dấu phẩy động hơn) có thể trở nên đáng kể. Vì lý do tương tự, bạn không bao giờ nên đảo ngược rõ ràng ma trận với inv () nếu điều bạn thực sự quan tâm là số lần nghịch đảo của một vectơ hoặc ma trận; bạn nên sử dụng giải quyết () thay thế.
dwf

5

Bạn không cần phải phân hủy giá trị số ít (SVD) đầy đủ vì nó tính toán tất cả các giá trị riêng và thiết bị định vị và có thể bị cấm đối với ma trận lớn. scipy và mô-đun thưa thớt của nó cung cấp các hàm đại số tuyến tính chung hoạt động trên cả ma trận thưa và ma trận dày đặc, trong đó có họ hàm eig *:

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

Scikit-learning cung cấp triển khai Python PCA hiện chỉ hỗ trợ các ma trận dày đặc.

Thời gian:

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
Không hẳn là một so sánh công bằng, vì bạn vẫn cần tính toán ma trận hiệp phương sai. Ngoài ra, nó có lẽ chỉ đáng sử dụng công cụ linalg thưa thớt cho các ma trận rất lớn, vì nó có vẻ khá chậm để xây dựng các ma trận thưa thớt từ các ma trận dày đặc. ví dụ, eigshthực sự chậm hơn ~ 4 lần so eighvới ma trận không phân tích. Điều này cũng đúng cho scipy.sparse.linalg.svdsso với numpy.linalg.svd. Tôi sẽ luôn sử dụng SVD qua phân rã eigenvalue vì những lý do mà @dwf đã đề cập và có lẽ sử dụng phiên bản SVD thưa thớt nếu ma trận thực sự lớn.
ali_m

2
Bạn không cần phải tính toán ma trận thưa thớt từ ma trận dày đặc. Các thuật toán được cung cấp trong mô-đun precision.linalg chỉ dựa vào phép toán nhân vectơ matrice thông qua phương thức matvec của đối tượng Toán tử. Đối với ma trận dày đặc, đây chỉ là một cái gì đó giống như matvec = dot (A, x). Đối với lý do tương tự, bạn không cần phải tính toán ma trận hiệp phương sai nhưng chỉ để cung cấp các dot hoạt động (AT, rải rác (A, x)) cho A.
Nicolas Barbey

Ah, bây giờ tôi thấy rằng tốc độ tương đối của các phương pháp thưa và không phân tích phụ thuộc vào kích thước của ma trận. Nếu tôi sử dụng ví dụ của bạn trong đó A là một ma trận 1000 * 1000 sau đó eigshsvdsđược nhanh hơn eighsvdbởi một yếu tố của ~ 3, nhưng nếu A là nhỏ hơn, nói 100 * 100, sau đó eighsvdlà nhanh hơn bởi các yếu tố của ~ 4 và ~ 1,5 tương ứng . Tuy nhiên, T vẫn sẽ sử dụng SVD thưa thớt để phân hủy eigenvalue thưa thớt.
ali_m

2
Thật vậy, tôi nghĩ rằng tôi thiên về các ma trận lớn. Đối với tôi ma trận lớn đều giống như 10⁶ * 10⁶ hơn 1000 * 1000. Trong những trường hợp này, bạn thường có thể thậm chí không lưu trữ ma trận hiệp phương sai ...
Nicolas Barbey

4

Đây là một triển khai khác của mô-đun PCA dành cho python bằng cách sử dụng phần mở rộng numpy, scipy và C. Mô-đun thực hiện PCA bằng cách sử dụng SVD hoặc thuật toán NIPALS (Nonlinear Iterative Partial Least Squares) được triển khai trong C.


0

Nếu bạn đang làm việc với vectơ 3D, bạn có thể áp dụng SVD một cách chính xác bằng cách sử dụng vg đai công cụ . Nó là một lớp nhẹ trên đầu trang của numpy.

import numpy as np
import vg

vg.principal_components(data)

Ngoài ra còn có một bí danh thuận tiện nếu bạn chỉ muốn thành phần chính đầu tiên:

vg.major_axis(data)

Tôi đã tạo thư viện ở lần khởi động cuối cùng của mình, nơi nó được thúc đẩy bởi những cách sử dụng như thế này: những ý tưởng đơn giản nhưng dài dòng hoặc không rõ ràng trong NumPy.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.