Como executar a validação cruzada para o PCA para determinar o número de componentes principais?

13

Estou tentando escrever minha própria função para análise de componentes principais, o PCA (é claro que já há muito escrito, mas estou interessado apenas em implementar coisas sozinho). O principal problema que encontrei é a etapa de validação cruzada e o cálculo da soma prevista dos quadrados (PRESS). Não importa qual validação cruzada eu uso, é uma questão principalmente sobre a teoria por trás, mas considere a validação cruzada de exclusão única (LOOCV). A partir da teoria, descobri que, para executar o LOOCV, você precisa:

  1. excluir um objeto
  2. escalar o resto
  3. executar PCA com algum número de componentes
  4. dimensione o objeto excluído de acordo com os parâmetros obtidos em (2)
  5. prever o objeto de acordo com o modelo PCA
  6. calcular PRESS para este objeto
  7. execute novamente o mesmo algoritmo para outros objetos
  8. resumir todos os valores PRESS
  9. lucro

Como sou muito novo em campo, para ter certeza de que estou certo, comparo os resultados com os resultados de alguns softwares que tenho (também para escrever um código, sigo as instruções do software). Recebo completamente os mesmos resultados calculando a soma residual dos quadrados e , mas calcular PRESS é um problema.R2

Você poderia me dizer se o que eu implemento na etapa de validação cruzada está certo ou não:

case 'loocv'

% # n - number of objects
% # p - number of variables
% # vComponents - the number of components used in CV
dataSets = divideData(n,n); 
         % # it is just a variable responsible for creating datasets for CV 
         % #  (for LOOCV datasets will be equal to [1, 2, 3, ... , n]);'
tempPRESS = zeros(n,vComponents);

for j = 1:n
  Xmodel1 = X; % # X - n x p original matrix
  Xmodel1(dataSets{j},:) = []; % # delete the object to be predicted
  [Xmodel1,Xmodel1shift,Xmodel1div] = skScale(Xmodel1, 'Center', vCenter, 
                                              'Scaling', vScaling); 
          % # scale the data and extract the shift and scaling factor
  Xmodel2 = X(dataSets{j},:); % # the object to be predicted
  Xmodel2 = bsxfun(@minus,Xmodel2,Xmodel1shift); % # shift and scale the object
  Xmodel2 = bsxfun(@rdivide,Xmodel2,Xmodel1div);
  [Xscores2,Xloadings2] = myNipals(Xmodel1,0.00000001,vComponents); 
          % # the way to calculate the scores and loadings
                % # Xscores2 - n x vComponents matrix
                % # Xloadings2 - vComponents x p matrix
  for i = 1:vComponents
    tempPRESS(j,i) = sum(sum((Xmodel2* ...
       (eye(p) - transpose(Xloadings2(1:i,:))*Xloadings2(1:i,:))).^2));
  end
end
PRESS = sum(tempPRESS,1);

No software ( PLS_Toolbox ), funciona assim:

for i = 1:vComponents
    tempPCA = eye(p) - transpose(Xloadings2(1:i,:))*Xloadings2(1:i,:);
    for kk = 1:p
        tempRepmat(:,kk) = -(1/tempPCA(kk,kk))*tempPCA(:,kk);
          % # this I do not understand
        tempRepmat(kk,kk) = -1; 
          % # here is some normalization that I do not get
    end 
    tempPRESS(j,i) = sum(sum((Xmodel2*tempRepmat).^2)); 
end

Portanto, eles fazem alguma normalização adicional usando essa tempRepmatvariável: a única razão pela qual descobri foi que eles aplicam LOOCV para PCA robusto. Infelizmente, a equipe de suporte não quis responder à minha pergunta, pois eu tenho apenas a versão demo de seu software.

Kirill
fonte
Verificação adicional se eu entendo o snippet de normalização adicional: qual é o papel da tempRepmat(kk,kk) = -1linha? A linha anterior já não está garantindo que tempRepmat(kk,kk)seja igual a -1? Além disso, por que menos? O erro será quadrado de qualquer maneira, então eu entendi corretamente que se os menos foram removidos, nada mudará?
Ameba diz Reinstate Monica
Eu estava checando isso no passado e nada vai mudar. Está correto. Encontrei apenas alguns paralelos com implementações robustas de PCA porque cada valor PRESS calculado nessa implementação (antes de resumir tudo) tem seu próprio peso.
Kirill
Estou procurando o código R equivalente ao código MATLAB fornecido na resposta e paguei uma recompensa.
AIM_BLB
3
@CSA, pedir código está fora do tópico aqui (até, presumivelmente, por meio de comentários e recompensas). Você deve poder perguntar isso no Stack Overflow : você pode copiar o código, citar a fonte com um link aqui e solicitar uma tradução. Eu acredito que tudo o que estaria no tópico lá.
gung - Restabelece Monica

Respostas:

21

O que você está fazendo está errado: não faz sentido calcular o PRESS para PCA dessa maneira! Especificamente, o problema está na etapa 5.


Abordagem ingênua do PRESS for PCA

Deixe o conjunto de dados constituído por pontos em espaço -dimensional: . Para calcular o erro de reconstrução para um único ponto de dados de teste , você executa o PCA no conjunto de treinamento com este ponto excluído, faça um determinado número de eixos principais como colunas de e localize o erro de reconstrução como . PRESS é então igual à soma de todas as amostras de tested x ( i )R d ,ndx(i)Rd,i=1nx(i)X(i)kU(i) i Px(i)x^(i)2=x(i)U(i)[U(i)]x(i)2i, portanto, a equação razoável parece ser:

PRESS=?i=1nx(i)U(i)[U(i)]x(i)2.

Por simplicidade, estou ignorando os problemas de centralização e dimensionamento aqui.

A abordagem ingênua está errada

O problema acima é que usamos para calcular a previsão , e isso é uma coisa muito ruim.x(i)x^(i)

Observe a diferença crucial em um caso de regressão, onde a fórmula do erro de reconstrução é basicamente a mesma , mas a previsão é calculada usando as variáveis ​​preditoras e não usando . Isso não é possível no PCA, porque no PCA não há variáveis ​​dependentes e independentes: todas as variáveis ​​são tratadas juntas.y(i)y^(i)2y^(i)y(i)

Na prática, isso significa que o PRESS, conforme calculado acima, pode diminuir com o aumento do número de componentes e nunca atingir um mínimo. O que levaria a pensar que todos os componentes são significativos. Ou talvez, em alguns casos, atinja um mínimo, mas ainda tende a superestimar e superestimar a dimensionalidade ideal.kd

Uma abordagem correta

Existem várias abordagens possíveis, veja Bro et al. (2008) Validação cruzada de modelos de componentes: uma visão crítica dos métodos atuais para uma visão geral e comparação. Uma abordagem é deixar de fora uma dimensão de um ponto de dados por vez (ou seja, vez de ), para que os dados de treinamento se tornem uma matriz com um valor ausente e, em seguida, prever ("imputar") esse valor ausente no PCA. (É claro que é possível armazenar aleatoriamente uma fração maior de elementos da matriz, por exemplo, 10%). O problema é que a computação do PCA com valores ausentes pode ser computacionalmente bastante lenta (depende do algoritmo EM), mas precisa ser iterada várias vezes aqui. Atualização: consulte http://alexhwilliams.info/itsneuronalblog/2018/02/26/crossval/xj(i)x(i) para uma boa discussão e implementação do Python (o PCA com valores ausentes é implementado por meio de mínimos quadrados alternados).

Uma abordagem que achei muito mais prática é deixar de fora um ponto de dados cada vez, computar o PCA nos dados de treinamento (exatamente como acima), mas depois percorrer as dimensões de , deixe-os de fora um de cada vez e calcule um erro de reconstrução usando o resto. Isso pode ser bastante confuso no começo e as fórmulas tendem a se tornar bastante confusas, mas a implementação é bastante direta. Deixe-me primeiro dar a fórmula (um pouco assustadora) e depois explicá-la brevemente:x(i)x(i)

PRESSPCA=i=1nj=1d|xj(i)[U(i)[Uj(i)]+xj(i)]j|2.

Considere o loop interno aqui. Deixamos de lado um ponto e computamos componentes principais nos dados de treinamento, . Agora mantemos cada valor como teste e usamos as dimensões restantes para realizar a previsão . A previsão é a ésima coordenada da "projeção" (no sentido dos mínimos quadrados) de no subespaço estendido por . Para calcular, encontre um ponto no espaço do PC mais próximo de k U ( - i )x(i)kU(i)xj(i)xj(i)Rd1x^j(i)jxj(i)U(i)z^Rkxj(i) calculando onde é com -ésima linha expulso e significa pseudoinverso. Agora, mapeie volta ao espaço original: e assume a ésima coordenada . z^=[Uj(i)]+xj(i)RkUj(i)U(i)j[]+z^U(i)[Uj(i)]+xj(i)j[]j

Uma aproximação à abordagem correta

Não entendo bem a normalização adicional usada no PLS_Toolbox, mas aqui está uma abordagem que segue na mesma direção.

Existe outra maneira de mapear no espaço dos componentes principais: , ou seja, simplesmente faça a transposição em vez de pseudo-inversa. Em outras palavras, a dimensão que é deixada de fora para o teste não é contada, e os pesos correspondentes também são simplesmente expulsos. Eu acho que isso deve ser menos preciso, mas muitas vezes pode ser aceitável. O bom é que a fórmula resultante agora pode ser vetorizada da seguinte forma (eu omito o cálculo):xj(i)z^approx=[Uj(i)]xj(i)

PRESSPCA,approx=i=1nj=1d|xj(i)[U(i)[Uj(i)]xj(i)]j|2=i=1n(IUU+diag{UU})x(i)2,

onde eu escrevi como para compacidade e significa definir todos os elementos não diagonais como zero. Observe que esta fórmula se parece exatamente com a primeira (IMPRENSA ingênua) com uma pequena correção! Observe também que essa correção depende apenas da diagonal de , como no código PLS_Toolbox. No entanto, a fórmula ainda é diferente do que parece ser implementado no PLS_Toolbox, e essa diferença não posso explicar. U d i a g {} U UU(i)Udiag{}UU

Atualização (fev 2018): Acima, chamei um procedimento de "correto" e outro de "aproximado", mas não tenho mais tanta certeza de que isso seja significativo. Ambos os procedimentos fazem sentido e acho que nenhum dos dois é mais correto. Eu realmente gosto que o procedimento "aproximado" tenha uma fórmula mais simples. Além disso, lembro que tinha um conjunto de dados em que o procedimento "aproximado" produzia resultados que pareciam mais significativos. Infelizmente, não me lembro mais dos detalhes.


Exemplos

Aqui está como esses métodos se comparam para dois conjuntos de dados conhecidos: conjunto de dados Iris e conjunto de dados wine. Observe que o método ingênuo produz uma curva monotonicamente decrescente, enquanto outros dois métodos produzem uma curva com um mínimo. Observe ainda que, no caso Iris, o método aproximado sugere 1 PC como o número ideal, mas o método pseudo-inverso sugere 2 PCs. (E olhando para qualquer gráfico de dispersão de PCA para o conjunto de dados Iris, parece que os dois primeiros PCs transmitem algum sinal.) E no caso do wine, o método pseudoinverso aponta claramente para 3 PCs, enquanto o método aproximado não pode decidir entre 3 e 5.

insira a descrição da imagem aqui


Código Matlab para executar validação cruzada e plotar os resultados

function pca_loocv(X)

%// loop over data points 
for n=1:size(X,1)
    Xtrain = X([1:n-1 n+1:end],:);
    mu = mean(Xtrain);
    Xtrain = bsxfun(@minus, Xtrain, mu);
    [~,~,V] = svd(Xtrain, 'econ');
    Xtest = X(n,:);
    Xtest = bsxfun(@minus, Xtest, mu);

    %// loop over the number of PCs
    for j=1:min(size(V,2),25)
        P = V(:,1:j)*V(:,1:j)';        %//'
        err1 = Xtest * (eye(size(P)) - P);
        err2 = Xtest * (eye(size(P)) - P + diag(diag(P)));
        for k=1:size(Xtest,2)
            proj = Xtest(:,[1:k-1 k+1:end])*pinv(V([1:k-1 k+1:end],1:j))'*V(:,1:j)'; 
            err3(k) = Xtest(k) - proj(k);
        end

        error1(n,j) = sum(err1(:).^2);
        error2(n,j) = sum(err2(:).^2);
        error3(n,j) = sum(err3(:).^2);
    end    
end

error1 = sum(error1);
error2 = sum(error2);
error3 = sum(error3);
%// plotting code
figure
hold on
plot(error1, 'k.--')
plot(error2, 'r.-')
plot(error3, 'b.-')
legend({'Naive method', 'Approximate method', 'Pseudoinverse method'}, ...
    'Location', 'NorthWest')
legend boxoff
set(gca, 'XTick', 1:length(error1))
set(gca, 'YTick', [])
xlabel('Number of PCs')
ylabel('Cross-validation error')
ameba diz Restabelecer Monica
fonte
Obrigado pela sua resposta! Eu conheço esse papel. E apliquei a validação cruzada em linhas descrita lá (parece que corresponde exatamente ao código que forneci). Eu comparo com o software PLS_Toolbox, mas eles têm uma linha de código no LOOCV que eu realmente não entendo (escrevi na pergunta original).
Kirill
Sim, eles chamam de "validação cruzada em linhas" e sua implementação parece boa, mas observe que essa é uma maneira ruim de realizar a validação cruzada (conforme declarado e também demonstrado empiricamente em Bro et al.) E basicamente você deve nunca use! Em relação à linha de código que você não entende - você atualizará sua pergunta? Não sei ao que você está se referindo.
Ameba diz Reinstate Monica
O fato é que essa implementação parece ter a capacidade de atingir o mínimo em CV.
Kirill
A IMPRENSA de está muito próxima da variação explicada LOO% - eu não diria que isso é bom ou ruim, mas é definitivamente algo que você precisa estar ciente. E como% variância explicada vai abordar 1 como o modelo PCA se aproxima da classificação do conjunto de dados, este X PRESS vai abordar 0.x^x
cbeleites descontentes com SX
@ Kirill: Obrigado, o trecho de código faz sentido agora (talvez você possa remover os comentários acima que agora estão obsoletos). Não tenho idéia do que deve fazer, mas se você disser que faz com que o erro de previsão computado atinja o mínimo, provavelmente está introduzindo efetivamente alguma penalidade para maior (número de componentes; ou seja, variável no seu código). Honestamente, eu seria cético em relação a qualquer método desse tipo (a menos que haja uma justificativa teórica), especialmente considerando que existem abordagens melhores como a que descrevi em minha resposta. ki
Ameba diz Reinstate Monica
1

Para adicionar um ponto ainda mais geral à boa resposta de @ amoeba:

Uma diferença prática e crucial entre os modelos supervisionados e não supervisionados é que, para os modelos não supervisionados, você precisa pensar muito mais sobre o que você consideraria equivalente e o que não.

Modelos supervisionadas sempre têm a sua última saída de uma forma onde você não precisa se preocupar muito com isso: por definição e construção, afirma ter o mesmo significado que , para que você possa compará-lo diretamente. y yy^y^y

Para construir medidas de desempenho significativas, você precisa pensar que tipos de liberdade do modelo são sem sentido para o seu aplicativo e quais não são. Isso levaria a uma IMPRENSA nas pontuações, possivelmente (geralmente?) Após algum tipo de rotação / inversão do tipo Procrustes.

IMPRENSA no x Meu palpite é (não tenho tempo agora para descobrir o que as duas linhas de código fazem - mas talvez você possa percorrer as linhas e dar uma olhada?):

Para obter uma medida que seja útil para determinar uma boa complexidade do modelo a partir de uma medida que ofereça uma qualidade de ajuste que normalmente aumentará até que o modelo de classificação completo seja atingido, você deve penalizar modelos muito complexos. O que, por sua vez, significa que essa penalização é a) crucial eb) o ajuste da penalidade ajustará a complexidade escolhida.


Nota lateral: gostaria de acrescentar que seria muito cuidadoso com esse tipo de otimização automatizada da complexidade do modelo. Na minha experiência, muitos desses algoritmos produzem apenas pseudo-objetividade e muitas vezes têm o custo de funcionar bem apenas para certos tipos de dados.

cbeleites descontentes com o SX
fonte