Il miglior algoritmo PCA per un numero enorme di funzionalità (> 10 K)?


54

In precedenza l'ho chiesto su StackOverflow, ma sembra che qui potrebbe essere più appropriato, dato che non ha ricevuto risposte su SO. È una specie di incrocio tra statistica e programmazione.

Ho bisogno di scrivere un po 'di codice per fare PCA (Analisi dei componenti principali). Ho sfogliato i noti algoritmi e implementato questo , che per quanto ne so è equivalente all'algoritmo NIPALS. Funziona bene per trovare i primi 2-3 componenti principali, ma poi sembra diventare molto lento a convergere (nell'ordine di centinaia o migliaia di iterazioni). Ecco i dettagli di ciò di cui ho bisogno:

  1. L'algoritmo deve essere efficiente quando si ha a che fare con un numero enorme di funzioni (da 10.000 a 20.000) e dimensioni del campione dell'ordine di alcune centinaia.

  2. Deve essere ragionevolmente implementabile senza una libreria di algebra / matrice lineare decente, poiché la lingua di destinazione è D, che non ne ha ancora una, e anche se lo facesse, preferirei non aggiungerla come dipendenza al progetto in questione .

Come nota a margine, sullo stesso set di dati R sembra trovare tutti i componenti principali molto velocemente, ma utilizza una scomposizione di valori singolari, che non è qualcosa che voglio codificare.


2
Esistono molti algoritmi SVD pubblici. Vedi en.wikipedia.org/wiki/… . Non puoi usare o adattare uno di loro? Inoltre, R è open-source e con licenza GPL, quindi perché non prendere in prestito il suo algoritmo se fa il lavoro?
Rob Hyndman,

@Rob: vorrei evitare praticamente di scrivere una libreria di algebra lineare e voglio anche evitare il copyleft della GPL. Inoltre, ho già visto frammenti del codice sorgente R in precedenza e generalmente non è molto leggibile.
dsimcha,

4
Mi sto perdendo qualcosa? Hai> 10K caratteristiche ma <1K campioni? Ciò significa che gli ultimi componenti 9K sono arbitrari. Vuoi tutti i 1K dei primi componenti?
Shabbychef,

2
In ogni caso, non puoi sfuggire alla necessità di implementare SVD, anche se grazie a molte ricerche numeriche sull'algebra lineare, ora ci sono molti metodi tra cui scegliere, a seconda di quanto sia grande / piccola, sparsa / densa la tua matrice, o se vuoi solo i valori singolari o l'insieme completo di valori singolari e vettori singolari sinistro / destro. Gli algoritmi non sono tremendamente difficili da capire IMHO.
JM non è uno statistico il

Puoi dirci perché vuoi fare PCA?
Robin Girard,

Risposte:


27

Ho implementato l'SVD randomizzato come indicato in "Halko, N., Martinsson, PG, Shkolnisky, Y. e Tygert, M. (2010). Un algoritmo per l'analisi dei componenti principali di grandi set di dati. Arxiv preprint arXiv: 1007.5510, 0526. Estratto il 1 aprile 2011 da http://arxiv.org/abs/1007.5510 . ". Se vuoi ottenere SVD troncato, funziona davvero molto più velocemente delle variazioni svd in MATLAB. Puoi ottenerlo qui:

function [U,S,V] = fsvd(A, k, i, usePowerMethod)
% FSVD Fast Singular Value Decomposition 
% 
%   [U,S,V] = FSVD(A,k,i,usePowerMethod) computes the truncated singular
%   value decomposition of the input matrix A upto rank k using i levels of
%   Krylov method as given in [1], p. 3.
% 
%   If usePowerMethod is given as true, then only exponent i is used (i.e.
%   as power method). See [2] p.9, Randomized PCA algorithm for details.
% 
%   [1] Halko, N., Martinsson, P. G., Shkolnisky, Y., & Tygert, M. (2010).
%   An algorithm for the principal component analysis of large data sets.
%   Arxiv preprint arXiv:1007.5510, 0526. Retrieved April 1, 2011, from
%   http://arxiv.org/abs/1007.5510. 
%   
%   [2] Halko, N., Martinsson, P. G., & Tropp, J. A. (2009). Finding
%   structure with randomness: Probabilistic algorithms for constructing
%   approximate matrix decompositions. Arxiv preprint arXiv:0909.4061.
%   Retrieved April 1, 2011, from http://arxiv.org/abs/0909.4061.
% 
%   See also SVD.
% 
%   Copyright 2011 Ismail Ari, http://ismailari.com.

    if nargin < 3
        i = 1;
    end

    % Take (conjugate) transpose if necessary. It makes H smaller thus
    % leading the computations to be faster
    if size(A,1) < size(A,2)
        A = A';
        isTransposed = true;
    else
        isTransposed = false;
    end

    n = size(A,2);
    l = k + 2;

    % Form a real n×l matrix G whose entries are iid Gaussian r.v.s of zero
    % mean and unit variance
    G = randn(n,l);


    if nargin >= 4 && usePowerMethod
        % Use only the given exponent
        H = A*G;
        for j = 2:i+1
            H = A * (A'*H);
        end
    else
        % Compute the m×l matrices H^{(0)}, ..., H^{(i)}
        % Note that this is done implicitly in each iteration below.
        H = cell(1,i+1);
        H{1} = A*G;
        for j = 2:i+1
            H{j} = A * (A'*H{j-1});
        end

        % Form the m×((i+1)l) matrix H
        H = cell2mat(H);
    end

    % Using the pivoted QR-decomposiion, form a real m×((i+1)l) matrix Q
    % whose columns are orthonormal, s.t. there exists a real
    % ((i+1)l)×((i+1)l) matrix R for which H = QR.  
    % XXX: Buradaki column pivoting ile yapılmayan hali.
    [Q,~] = qr(H,0);

    % Compute the n×((i+1)l) product matrix T = A^T Q
    T = A'*Q;

    % Form an SVD of T
    [Vt, St, W] = svd(T,'econ');

    % Compute the m×((i+1)l) product matrix
    Ut = Q*W;

    % Retrieve the leftmost m×k block U of Ut, the leftmost n×k block V of
    % Vt, and the leftmost uppermost k×k block S of St. The product U S V^T
    % then approxiamtes A. 

    if isTransposed
        V = Ut(:,1:k);
        U = Vt(:,1:k);     
    else
        U = Ut(:,1:k);
        V = Vt(:,1:k);
    end
    S = St(1:k,1:k);
end

Per provarlo, basta creare un'immagine nella stessa cartella (proprio come una matrice grande, puoi creare tu stesso la matrice)

% Example code for fast SVD.

clc, clear

%% TRY ME
k = 10; % # dims
i = 2;  % # power
COMPUTE_SVD0 = true; % Comment out if you do not want to spend time with builtin SVD.

% A is the m×n matrix we want to decompose
A = im2double(rgb2gray(imread('test_image.jpg')))';

%% DO NOT MODIFY
if COMPUTE_SVD0
    tic
    % Compute SVD of A directly
    [U0, S0, V0] = svd(A,'econ');
    A0 = U0(:,1:k) * S0(1:k,1:k) * V0(:,1:k)';
    toc
    display(['SVD Error: ' num2str(compute_error(A,A0))])
    clear U0 S0 V0
end

% FSVD without power method
tic
[U1, S1, V1] = fsvd(A, k, i);
toc
A1 = U1 * S1 * V1';
display(['FSVD HYBRID Error: ' num2str(compute_error(A,A1))])
clear U1 S1 V1

% FSVD with power method
tic
[U2, S2, V2] = fsvd(A, k, i, true);
toc
A2 = U2 * S2 * V2';
display(['FSVD POWER Error: ' num2str(compute_error(A,A2))])
clear U2 S2 V2

subplot(2,2,1), imshow(A'), title('A (orig)')
if COMPUTE_SVD0, subplot(2,2,2), imshow(A0'), title('A0 (svd)'), end
subplot(2,2,3), imshow(A1'), title('A1 (fsvd hybrid)')
subplot(2,2,4), imshow(A2'), title('A2 (fsvd power)')

SVD veloce

Quando lo eseguo sul desktop per un'immagine di dimensioni 635 * 483, ottengo

Elapsed time is 0.110510 seconds.
SVD Error: 0.19132
Elapsed time is 0.017286 seconds.
FSVD HYBRID Error: 0.19142
Elapsed time is 0.006496 seconds.
FSVD POWER Error: 0.19206

Come puoi vedere, per valori bassi di k, è oltre 10 volte più veloce rispetto all'utilizzo di Matlab SVD. A proposito, potresti aver bisogno della seguente semplice funzione per la funzione test:

function e = compute_error(A, B)
% COMPUTE_ERROR Compute relative error between two arrays

    e = norm(A(:)-B(:)) / norm(A(:));
end

Non ho aggiunto il metodo PCA poiché è semplice da implementare usando SVD. Puoi controllare questo link per vedere la loro relazione.


12

potresti provare a usare un paio di opzioni.

1- Decomposizione matrice maturata . Si applicano alcuni vincoli di penalità su ue v per ottenere qualche scarsità. Algoritmo rapido che è stato utilizzato sui dati di genomica

Vedi Whitten Tibshirani. Hanno anche un R-pkg. "Una decomposizione matriciale penalizzata, con applicazioni a componenti principali sparsi e analisi di correlazione canonica."

2- SVD randomizzato . Poiché SVD è un algoritmo master, è preferibile trovare un'approssimazione molto rapida, in particolare per l'analisi esplorativa. Usando SVD randomizzato, puoi fare PCA su enormi set di dati.

Vedi Martinsson, Rokhlin e Tygert "Un algoritmo randomizzato per la decomposizione delle matrici". Tygert ha un codice per un'implementazione molto rapida di PCA.

Di seguito è una semplice implementazione di SVD randomizzato in R.

ransvd = function(A, k=10, p=5) {
  n = nrow(A)
  y = A %*% matrix(rnorm(n * (k+p)), nrow=n)
  q = qr.Q(qr(y))
  b = t(q) %*% A
  svd = svd(b)
  list(u=q %*% svd$u, d=svd$d, v=svd$v)
}

+1 per la decomposizione della matrice penalizzata. Quel pacchetto è piuttosto sorprendente. Dovrei probabilmente dire che è scritto "Witten", tuttavia, nel caso in cui le persone abbiano difficoltà a trovare la citazione. Infine, l'OP ha affermato di non voler scrivere nulla in R, ma essenzialmente qualsiasi pacchetto SVD di grandi dimensioni avrà un backend C, C ++ o Fortran per la velocità.
David J. Harris,


3

Suggerirei di provare il kernel PCA che ha una complessità tempo / spazio dipendente dal numero di esempi (N) piuttosto che dal numero di funzioni (P), che ritengo più adatto alla tua impostazione (P >> N)). Kernel PCA funziona fondamentalmente con la matrice del kernel NxN (matrice di somiglianze tra i punti dati), piuttosto che con la matrice di covarianza PxP che può essere difficile da gestire per P. di grandi dimensioni Un altro aspetto positivo del kernel PCA è che può apprendere proiezioni non lineari anche se lo usi con un kernel adatto. Vedi questo documento sul kernel PCA .


2

Mi sembra di ricordare che è possibile eseguire il PCA calcolando la decomposizione degli automi di X ^ TX anziché XX ^ T e quindi trasformarsi per ottenere i PC. Tuttavia non riesco a ricordare i dettagli fuori mano, ma è nel libro (eccellente) di Jolliffe e lo cercherò quando sarò il prossimo a lavoro. Vorrei traslitterare le routine di algebra lineare da, ad esempio, i metodi numerici in C, piuttosto che usare qualsiasi altro algoritmo.


5
Buon dolore ... costruire la matrice di covarianza non è mai il modo migliore per SVD. Ho mostrato un esempio del perché formare esplicitamente la matrice di covarianza non è una buona idea su math.SE: math.stackexchange.com/questions/3869/3871#3871 .
JM non è uno statistico il

1

Esiste anche il metodo bootstrap di Fisher et al , progettato per diverse centinaia di campioni di alta dimensione.

L'idea principale del metodo è formulata come "il ricampionamento è una trasformazione a bassa dimensione". Pertanto, se si dispone di un numero (diverse centinaia) di campioni ad alta dimensione, non è possibile ottenere più componenti principali rispetto al numero dei campioni. Ha quindi senso considerare i campioni come una base parsimoniosa, proiettare i dati sul sottospazio lineare distribuito da questi vettori e calcolare il PCA all'interno di questo sottospazio più piccolo. Forniscono inoltre maggiori dettagli su come gestire il caso in cui non tutti i campioni possono essere memorizzati.


0

Vedi l'articolo di Sam Roweis, EM Algorithms per PCA e SPCA .


L'algoritmo di Wikipedia lo cita ed è equivalente a questo nel caso di trovare un componente principale alla volta.
dsimcha,

OK, ora vedo il link. Questo è un approccio abbastanza semplice e, come cita Wikipedia, ci sono progressi su questa idea di base. Tuttavia, riflettendo, dovrai fare i conti con un qualche tipo di compromesso (convergenza in questo caso). Mi chiedo se stai facendo la domanda giusta qui. Non ci sono davvero buoni collegamenti alle librerie linalg per D?
ARS
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.