Análisis de componentes principales en Python
Pregunta
Me gustaría utilizar el análisis de componentes principales (PCA) para la reducción de dimensionalidad. No numpy o scipy lo tiene, o qué tengo que rodar mi propia usando numpy.linalg.eigh
?
Yo no sólo quiero utilizar la descomposición en valores singulares (SVD) porque mis datos de entrada son bastante alta dimensión (~ 460 dimensiones), por lo que creo SVD será más lento que el cálculo de los vectores propios de la matriz de covarianza.
Tenía la esperanza de encontrar un preparado de antemano, depurado aplicación que ya toma las decisiones correctas para cuándo usar el método y que tal vez hace otras optimizaciones que no me conocen.
Solución
Es posible echar un vistazo a MDP .
No he tenido la oportunidad de probarlo, pero me he marcado como favorito exactamente para la funcionalidad de PCA.
Otros consejos
Unos meses más tarde, he aquí una pequeña clase de PCA, y una imagen:
#!/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
PCA utilizando numpy.linalg.svd
es muy fácil. He aquí una demostración sencilla:
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()
Puede utilizar 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_)))
matplotlib.mlab tiene un aplicación del ACC .
SVD debería funcionar bien con 460 dimensiones. Se tarda unos 7 segundos en mi netbook Atom. El método EIG () toma más de tiempo (como debe ser, que utiliza más operaciones de punto flotante) y casi siempre será menos precisa.
Si usted tiene menos de 460 ejemplos a continuación, lo que quiere hacer es diagonalizar la matriz de dispersión (x - datamean) ^ T (x - media), suponiendo que los puntos de datos son columnas, y luego a la izquierda, multiplicando por (x - datamean). Que podría más rápida en el caso de que usted tiene más dimensiones que los datos.
Puede bastante facilidad "rodar" su propio uso de scipy.linalg
(suponiendo un pre-centrado data
conjunto de datos):
covmat = data.dot(data.T)
evs, evmat = scipy.linalg.eig(covmat)
Entonces evs
son sus valores propios, y evmat
es su matriz de proyección.
Si desea mantener las dimensiones d
, utilice los primeros valores propios y d
primeros vectores propios d
.
Dado que scipy.linalg
tiene la descomposición y numpy las multiplicaciones de matrices, lo que más se puede pedir?
acabo de terminar de leer el libro máquina de aprendizaje: Una perspectiva algorítmica . Todos los ejemplos de código en el libro fue escrito por Python (y casi con Numpy). El fragmento de código de chatper10.2 Análisis de Componentes Principales quizás vale la pena una lectura. Utiliza numpy.linalg.eig.
Por cierto, creo que puede manejar SVD 460 * 460 dimensiones muy bien. Tengo calcular un 6500 * 6500 SVD con numpy / scipy.linalg.svd en un muy viejo PC: Pentium III a 733 MHz. Para ser honesto, el guión necesita una gran cantidad de memoria (sobre 1.xG) y una gran cantidad de tiempo (unos 30 minutos) para obtener el resultado SVD.
Pero creo que 460 * 460 en un PC moderno no será un gran problema a menos que u necesidad de hacer SVD un gran número de veces.
No es necesario plena descomposición en valores singulares (SVD) en el que calcula todos los valores propios y los vectores propios y puede ser prohibitivo para grandes matrices. scipy y su módulo escasa proporcionan funciones algrebra lineal genéricos que trabajan en ambas matrices dispersas y densas, entre las que se encuentra la familia * eig de funciones:
http://docs.scipy.org/ doc / scipy / referencia / sparse.linalg.html # matriz-factorizaciones
scikit-learn proporciona un Python PCA aplicación que sólo admite matrices densas por ahora.
Tiempos:
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
Aquí es otra implementación de un módulo de PCA para Python usando numpy, scipy y C -extensiones. El módulo lleva a cabo PCA utilizando una SVD o los NIPALS (no lineal iterativos Mínimos Cuadrados Parciales) de algoritmo que se implementa en C.
Si se trabaja con vectores 3D, se puede aplicar usando SVD concisa el cinturón de herramientas vg . Es una ligera capa en la parte superior de numpy.
import numpy as np
import vg
vg.principal_components(data)
También hay un alias conveniente si sólo desea que el primer componente principal:
vg.major_axis(data)
He creado la biblioteca en mi último inicio, donde fue motivada por usos como esto:. Ideas simples que son verbosa u opaco en NumPy