Melhor algoritmo PCA para um grande número de recursos (> 10K)?

54

Eu perguntei isso anteriormente no StackOverflow, mas parece que pode ser mais apropriado aqui, já que não obteve respostas no SO. É uma espécie de cruzamento entre estatística e programação.

Preciso escrever algum código para fazer o PCA (Principal Component Analysis). Naveguei pelos algoritmos conhecidos e implementei este , que, até onde sei, é equivalente ao algoritmo NIPALS. Ele funciona bem para encontrar os dois ou três primeiros componentes principais, mas depois fica muito lento para convergir (da ordem de centenas a milhares de iterações). Aqui estão os detalhes do que eu preciso:

  1. O algoritmo deve ser eficiente ao lidar com um grande número de recursos (ordem de 10.000 a 20.000) e tamanhos de amostra da ordem de algumas centenas.

  2. Ele deve ser razoavelmente implementável sem uma biblioteca de álgebra / matriz linear decente, pois a linguagem de destino é D, que ainda não possui uma, e mesmo que tivesse, eu preferiria não adicioná-la como uma dependência do projeto em questão. .

Como uma observação lateral, no mesmo conjunto de dados R parece encontrar todos os componentes principais muito rapidamente, mas ele usa decomposição de valor singular, o que não é algo que eu queira codificar sozinho.

dsimcha
fonte
2
Existem muitos algoritmos SVD públicos. Veja en.wikipedia.org/wiki/… . Você não pode usar ou adaptar um deles? Além disso, o R é de código aberto e está sob uma licença GPL. Por que não emprestar seu algoritmo se ele fizer o trabalho?
Rob Hyndman
@ Rob: Eu gostaria de evitar praticamente escrever uma biblioteca de álgebra linear, e também quero evitar o copyleft da GPL. Além disso, eu já observei os trechos do código-fonte R antes e geralmente não é muito legível.
dsimcha
4
Estou esquecendo de algo? Você tem mais de 10 mil recursos, mas <1 mil amostras? Isso significa que os últimos 9 mil componentes são arbitrários. Deseja todos os 1K dos primeiros componentes?
shabbychef
2
De qualquer forma, você não pode deixar de ter que implementar SVD, embora, graças a muita pesquisa numérica em álgebra linear, agora existam muitos métodos para escolher, dependendo de quão grande / pequeno, esparso / denso é sua matriz ou se você deseja apenas os valores singulares ou o conjunto completo de valores singulares e vetores singulares esquerda / direita. Os algoritmos não são terrivelmente difíceis de entender IMHO.
JM não é um estatístico
Você pode nos dizer por que deseja fazer o PCA?
robin Girard

Respostas:

27

Eu implementei o SVD aleatório, conforme indicado em "Halko, N., Martinsson, PG, Shkolnisky, Y. e Tygert, M. (2010). Um algoritmo para a análise de componentes principais de grandes conjuntos de dados. Arxiv preprint arXiv: 1007.5510, 0526. Recuperado em 1 de abril de 2011, em http://arxiv.org/abs/1007.5510 . ". Se você deseja obter SVD truncado, ele realmente funciona muito mais rapidamente do que as variações de DVD no MATLAB. Você pode obtê-lo aqui:

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

Para testá-lo, basta criar uma imagem na mesma pasta (assim como uma grande matriz, você mesmo pode criar a matriz)

% 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)')

Fast SVD

Quando o executo na área de trabalho para obter uma imagem de tamanho 635 * 483, recebo

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

Como você pode ver, para valores baixos de k, é mais de 10 vezes mais rápido que o uso do Matlab SVD. A propósito, você pode precisar da seguinte função simples para a função de teste:

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

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

Não adicionei o método PCA, pois é simples de implementar usando SVD. Você pode verificar este link para ver o relacionamento deles.

petrichor
fonte
12

você pode tentar usar algumas opções.

1- Decomposição da matriz penalizada . Você aplica algumas restrições de penalidade nos u e nos v para obter alguma escarsidade. Algoritmo rápido usado em dados genômicos

Veja Tibshirani Whitten. Eles também têm um R-pkg. "Uma decomposição de matriz penalizada, com aplicações para componentes principais esparsos e análise de correlação canônica".

2- SVD randomizado . Como o SVD é um algoritmo mestre, pode ser desejável uma aproximação muito rápida, especialmente para análises exploratórias. Usando SVD aleatório, você pode executar o PCA em grandes conjuntos de dados.

Veja Martinsson, Rokhlin e Tygert "Um algoritmo aleatório para a decomposição de matrizes". Tygert possui código para uma implementação muito rápida do PCA.

Abaixo está uma implementação simples de SVD aleatório em 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)
}
pslice
fonte
+1 para decomposição de matriz penalizada. Esse pacote é incrível. Eu provavelmente devo mencionar que está escrito "Witten", no entanto, caso as pessoas tenham problemas para encontrar a citação. Por fim, o OP disse que não queria nada escrito em R, mas essencialmente qualquer pacote SVD grande por aí terá um back-end C, C ++ ou Fortran para velocidade.
David J. Harris
4

Parece que talvez você queira usar o algoritmo Lanczos . Caso contrário, convém consultar a Golub & Van Loan. Certa vez, codifiquei um algoritmo SVD (em SML, de todas as línguas) a partir de seu texto, e funcionou razoavelmente bem.

shabbychef
fonte
3

Sugiro tentar o PCA do kernel, que tem uma complexidade de tempo / espaço dependente do número de exemplos (N) em vez do número de recursos (P), que eu acho que seriam mais adequados para a sua configuração (P >> N)). O Kernel PCA basicamente trabalha com a matriz NxN do kernel (matriz de semelhanças entre os pontos de dados), em vez da matriz de covariância PxP, que pode ser difícil de lidar com P. grande. Outra coisa boa do kernel PCA é que ele pode aprender projeções não lineares também se você o usar com um kernel adequado. Consulte este documento no PCA do kernel .

ebony1
fonte
2

Parece-me que me lembro que é possível executar o PCA calculando a decomposição autônoma de X ^ TX em vez de XX ^ T e depois transformar para obter os PCs. No entanto, não me lembro dos detalhes imediatamente, mas está no (excelente) livro de Jolliffe e procurarei quando for o próximo no trabalho. Eu transliteraria as rotinas de álgebra linear de, por exemplo, Métodos Numéricos em C, em vez de usar qualquer outro algoritmo.

Dikran Marsupial
fonte
5
Boa sorte ... construir a matriz de covariância nunca é o melhor caminho para SVD. Eu mostrei um exemplo de por que formar explicitamente a matriz de covariância não é uma boa ideia em math.SE: math.stackexchange.com/questions/3869/3871#3871 .
JM não é um estatístico
1

Existe também o método de inicialização de Fisher et al , projetado para várias centenas de amostras de alta dimensão.

A idéia principal do método é formulada como "a reamostragem é uma transformação de baixa dimensão". Portanto, se você tiver um número pequeno (várias centenas) de amostras de alta dimensão, não poderá obter mais componentes principais do que o número de suas amostras. Portanto, faz sentido considerar as amostras como uma base parcimoniosa, projetar os dados no subespaço linear estendido por esses vetores e calcular o PCA dentro desse subespaço menor. Eles também fornecem mais detalhes sobre como lidar com o caso, quando nem todas as amostras podem ser armazenadas na memória.

Kolya Ivankov
fonte
0

Veja o artigo de Sam Roweis, EM Algorithms for PCA e SPCA .

ars
fonte
O algoritmo da Wikipedia cita isso e é equivalente a isso no caso de encontrar um componente principal de cada vez.
dsimcha
OK, vejo o link agora. Essa é uma abordagem bastante simples e, como a Wikipedia menciona, há avanços nessa idéia básica. Porém, refletindo, você terá que lidar com algum tipo de compensação (convergência neste caso). Gostaria de saber se você está fazendo a pergunta certa aqui. Realmente não há boas ligações às bibliotecas linalg para D?
ars