¿Cómo realizar la validación cruzada para PCA para determinar el número de componentes principales?

13

Estoy tratando de escribir mi propia función para el análisis de componentes principales, PCA (por supuesto, ya hay mucho escrito pero solo estoy interesado en implementar cosas por mí mismo). El principal problema que encontré es el paso de validación cruzada y el cálculo de la suma de cuadrados pronosticada (PRENSA). No importa qué validación cruzada use, es una pregunta principalmente sobre la teoría detrás, pero considere la validación cruzada de dejar uno fuera (LOOCV). De la teoría descubrí que para realizar LOOCV necesitas:

  1. eliminar un objeto
  2. escalar el resto
  3. realizar PCA con cierto número de componentes
  4. escalar el objeto eliminado de acuerdo con los parámetros obtenidos en (2)
  5. predecir el objeto de acuerdo con el modelo de PCA
  6. calcular PRENSA para este objeto
  7. volver a realizar el mismo algoritmo a otros objetos
  8. resumir todos los valores de PRENSA
  9. lucro

Debido a que soy muy nuevo en el campo, para asegurarme de tener razón, comparo los resultados con la salida de algún software que tengo (también para escribir algún código sigo las instrucciones en el software). Obtengo los mismos resultados al calcular la suma residual de los cuadrados y R2 , pero calcular la PRENSA es un problema.

¿Podría decirme si lo que implemento en el paso de validación cruzada es correcto o no?

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);

En el software ( PLS_Toolbox ) funciona así:

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

Entonces, hacen una normalización adicional usando esta tempRepmatvariable: la única razón que encontré fue que aplican LOOCV para PCA robusto. Desafortunadamente, el equipo de soporte no quiso responder mi pregunta ya que solo tengo una versión demo de su software.

Kirill
fuente
Verificación adicional si entiendo el fragmento de normalización adicional: ¿cuál es el papel de la tempRepmat(kk,kk) = -1línea? ¿La línea anterior ya no garantiza que sea tempRepmat(kk,kk)igual a -1? Además, ¿por qué menos? El error se va a cuadrar de todos modos, ¿entiendo correctamente que si se eliminan las desventajas, nada cambiará?
ameba dice Reinstate Monica
Lo estaba revisando en el pasado y nada cambiará. Eso es correcto. Encontré solo algunos paralelos con implementaciones de PCA robustas porque cada valor de PRENSA calculado en dicha implementación (antes de resumir todo) tiene su propio peso.
Kirill
Estoy buscando el código R equivalente al código MATLAB proporcionado en la respuesta y he ofrecido una recompensa.
AIM_BLB
3
@CSA, pedir código está fuera de tema aquí (incluso, presumiblemente, a través de comentarios y recompensas). Debería poder preguntar eso en Stack Overflow : puede copiar el código, citar la fuente con un enlace aquí y solicitar una traducción. Creo que todo eso estaría en el tema allí.
gung - Restablece a Monica

Respuestas:

21

Lo que está haciendo está mal: ¡no tiene sentido calcular PRESS para PCA así! Específicamente, el problema radica en su paso # 5.


Enfoque ingenuo de PRENSA para PCA

ndx(i)Rd,i=1nx(i)X(i)kU(i)x(i)x^(i)2=x(i)U(i)[U(i)]x(i)2i

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

Por simplicidad, estoy ignorando los problemas de centrado y escalado aquí.

El enfoque ingenuo está mal

El problema anterior es que usamos para calcular la predicción , y eso es algo muy malo.x(i)x^(i)

Tenga en cuenta la diferencia crucial para un caso de regresión, donde la fórmula para el error de reconstrucción es básicamente la misma , pero la predicción se calcula usando las variables predictoras y no usando . Esto no es posible en PCA, porque en PCA no hay variables dependientes e independientes: todas las variables se tratan juntas.y(i)y^(i)2y^(i)y(i)

En la práctica, significa que PRESS, como se calculó anteriormente, puede disminuir al aumentar el número de componentes y nunca alcanzar un mínimo. Lo que llevaría a pensar que todos los componentes son significativos. O tal vez en algunos casos alcanza un mínimo, pero aún tiende a sobreajustar y sobreestimar la dimensionalidad óptima.kd

Un enfoque correcto

Hay varios enfoques posibles, ver Bro et al. (2008) Validación cruzada de modelos de componentes: una mirada crítica a los métodos actuales para una visión general y comparación. Un enfoque es dejar de lado una dimensión de un punto de datos a la vez (es decir, lugar de ), para que los datos de entrenamiento se conviertan en una matriz con un valor faltante , y luego predecir ("imputar") este valor faltante con PCA. (Por supuesto, uno puede mantener al azar una fracción mayor de elementos de la matriz, por ejemplo, 10%). El problema es que computar PCA con valores faltantes puede ser computacionalmente bastante lento (depende del algoritmo EM), pero debe repetirse aquí muchas veces. Actualización: ver http://alexhwilliams.info/itsneuronalblog/2018/02/26/crossval/xj(i)x(i) para una discusión agradable y la implementación de Python (PCA con valores faltantes se implementa a través de mínimos cuadrados alternos).

Un enfoque que me pareció mucho más práctico es dejar de lado un punto de datos a la vez, calcular PCA en los datos de entrenamiento (exactamente como se indica arriba), pero luego recorrer las dimensiones de , déjelos fuera uno a la vez y calcule un error de reconstrucción usando el resto. Esto puede ser bastante confuso al principio y las fórmulas tienden a volverse bastante desordenadas, pero la implementación es bastante sencilla. Permítanme primero dar la fórmula (algo aterradora) y luego explicarla brevemente:x(i)x(i)

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

Considere el bucle interno aquí. Dejamos un punto y calculamos componentes principales en los datos de entrenamiento, . Ahora mantenemos cada valor como prueba y utilizamos las dimensiones restantes para realizar la predicción . La predicción es la coordenada de "la proyección" (en el sentido de los mínimos cuadrados) de en el subespacio por . Para calcularlo, encuentre un punto en el espacio de la PC que esté más cerca dex(i)kU(i)xj(i)xj(i)Rd1x^j(i)jxj(i)U(i)z^Rkxj(i) calculando donde es con la fila expulsado, y significa pseudoinverso. Ahora asigne al espacio original: y tome su coordenada . z^=[Uj(i)]+xj(i)RkUj(i)U(i)j[]+z^U(i)[Uj(i)]+xj(i)j[]j

Una aproximación al enfoque correcto

No entiendo la normalización adicional utilizada en PLS_Toolbox, pero aquí hay un enfoque que va en la misma dirección.

Hay otra forma de mapear en el espacio de componentes principales: , es decir, simplemente tome la transposición en lugar de pseudo-inversa. En otras palabras, la dimensión que queda fuera para la prueba no se cuenta en absoluto, y los pesos correspondientes también se eliminan simplemente. Creo que esto debería ser menos preciso, pero a menudo podría ser aceptable. Lo bueno es que la fórmula resultante ahora se puede vectorizar de la siguiente manera (omito el 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,

donde escribí como para compacidad, y significa poner todos los elementos no diagonales a cero. ¡Tenga en cuenta que esta fórmula se ve exactamente como la primera (PRENSA ingenua) con una pequeña corrección! Tenga en cuenta también que esta corrección solo depende de la diagonal de , como en el código PLS_Toolbox. Sin embargo, la fórmula sigue siendo diferente de lo que parece implementarse en PLS_Toolbox, y no puedo explicar esta diferencia.U(i)Udiag{}UU

Actualización (febrero de 2018): anteriormente llamé a un procedimiento "correcto" y otro "aproximado", pero ya no estoy tan seguro de que esto sea significativo. Ambos procedimientos tienen sentido y creo que ninguno es más correcto. Realmente me gusta que el procedimiento "aproximado" tenga una fórmula más simple. Además, recuerdo que tenía un conjunto de datos donde el procedimiento "aproximado" arrojó resultados que parecían más significativos. Desafortunadamente, ya no recuerdo los detalles.


Ejemplos

Así es como se comparan estos métodos para dos conjuntos de datos conocidos: el conjunto de datos Iris y el conjunto de datos de vino. Tenga en cuenta que el método ingenuo produce una curva decreciente monotónicamente, mientras que otros dos métodos producen una curva con un mínimo. Tenga en cuenta además que en el caso de Iris, el método aproximado sugiere 1 PC como el número óptimo, pero el método pseudoinverso sugiere 2 PC. (Y mirando cualquier diagrama de dispersión de PCA para el conjunto de datos de Iris, parece que ambas primeras PC tienen alguna señal). Y en el caso del vino, el método pseudoinverso apunta claramente a 3 PC, mientras que el método aproximado no puede decidir entre 3 y 5.

ingrese la descripción de la imagen aquí


Código de Matlab para realizar validación cruzada y trazar los 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 dice reinstalar Monica
fuente
¡Gracias por su respuesta! Conozco ese papel. Y apliqué la validación cruzada en filas descrita allí (parece que corresponde exactamente al código que proporcioné). Me comparo con el software PLS_Toolbox pero tienen una línea de código en LOOCV que realmente no entiendo (escribí en la pregunta original).
Kirill
Sí, lo llaman "validación cruzada por filas" y su implementación parece estar bien, pero tenga en cuenta que esta es una mala forma de realizar la validación cruzada (como se indicó y también se demostró empíricamente en Bro et al.) Y básicamente debería nunca lo use! Con respecto a la línea de código que no comprende, ¿va a actualizar su pregunta? No estoy seguro de a qué te refieres.
ameba dice Reinstate Monica
El caso es que esta implementación parece tener la capacidad de alcanzar el mínimo en CV.
Kirill
La PRENSA de está muy cerca de la varianza explicada del LOO%. No diría que esto es bueno o malo, pero definitivamente es algo de lo que uno debe estar al tanto. Y al igual que el% de varianza explicada se acercará a 1 a medida que el modelo de PCA se acerque al rango del conjunto de datos, esta X PRESS se acercará a 0.x^x
cbeleites descontento con SX
@ Kirill: Gracias, el fragmento de código tiene sentido ahora (tal vez pueda eliminar los comentarios anteriores que ahora están obsoletos). No tengo idea de lo que se supone que debe hacer, pero si dice que hace que el error de predicción calculado alcance el mínimo, entonces probablemente está introduciendo efectivamente alguna penalización para más grande (número de componentes; es decir, variable en su código). Honestamente, sería escéptico de cualquier método de este tipo (a menos que exista una justificación teórica), especialmente dado que hay mejores enfoques como el que describí en mi respuesta. ki
ameba dice Reinstate Monica
1

Para agregar un punto aún más general a la buena respuesta de @ amoeba:

Una diferencia práctica y crucial entre los modelos supervisados ​​y no supervisados ​​es que, para los modelos no supervisados, debe pensar mucho más en qué consideraría equivalente y qué no.

Los modelos supervisados ​​siempre tienen su salida final de una manera en la que no necesita preocuparse demasiado por esto: por definición y construcción, afirma tener el mismo significado que , por lo que puede compararlo directamente.y^y^y

Para construir medidas de rendimiento significativas, debe pensar qué tipos de libertad del modelo no tienen sentido para su aplicación y cuáles no. Eso llevaría a una PRENSA en los puntajes, posiblemente (¿generalmente?) Después de algún tipo de rotación / volteo similar a Procrustes.

PRESIONE en x Mi conjetura es (ahora no tengo tiempo para averiguar qué hacen sus 2 líneas de código, pero ¿tal vez podría pasar por las líneas y echar un vistazo?):

Para obtener una medida que sea útil para determinar una buena complejidad del modelo a partir de una medida que proporcione una bondad de ajuste que generalmente aumentará hasta que se alcance el modelo de rango completo, debe penalizar por modelos demasiado complejos. Lo que a su vez significa que esta penalización es a) crucial yb) el ajuste de la penalización ajustará la complejidad elegida.


Nota al margen: me gustaría agregar que sería muy cuidadoso con este tipo de optimización automatizada de la complejidad del modelo. En mi experiencia, muchos de estos algoritmos solo producen pseudo-objetividad, y a menudo tienen el costo de funcionar bien solo para ciertos tipos de datos.

cbeleites descontentos con SX
fuente