Riduzione della dimensionalità (SVD o PCA) su una matrice ampia e sparsa


31

/ modifica: ulteriori follow-up ora è possibile utilizzare irlba :: prcomp_irlba


/ modifica: follow-up sul mio post. irlbaora ha argomenti "center" e "scale", che ti permettono di usarlo per calcolare i componenti principali, ad esempio:

pc <- M %*% irlba(M, nv=5, nu=0, center=colMeans(M), right_only=TRUE)$v


Ho una vasta gamma Matrixdi funzioni che vorrei utilizzare in un algoritmo di machine learning:

library(Matrix)
set.seed(42)
rows <- 500000
cols <- 10000
i <- unlist(lapply(1:rows, function(i) rep(i, sample(1:5,1))))
j <- sample(1:cols, length(i), replace=TRUE)
M <- sparseMatrix(i, j)

Poiché questa matrice ha molte colonne, vorrei ridurre la sua dimensionalità a qualcosa di più gestibile. Posso usare l'eccellente pacchetto irlba per eseguire SVD e restituire i primi n componenti principali (5 mostrati qui; probabilmente userò 100 o 500 sul mio set di dati effettivo):

library(irlba)
pc <- irlba(M, nu=5)$u

Tuttavia, ho letto che prima di eseguire la PCA, si dovrebbe centrare la matrice (sottrarre la media della colonna da ciascuna colonna). Questo è molto difficile da fare sul mio set di dati e inoltre distruggerebbe la scarsità della matrice.

Quanto è "cattivo" eseguire SVD sui dati non scalati e inserirli direttamente in un algoritmo di apprendimento automatico? Esistono modi efficaci per scalare questi dati, preservando la scarsità della matrice?


/ modifica: A portato alla mia attenzione da B_miner, i "PC" dovrebbero essere davvero:

pc <- M %*% irlba(M, nv=5, nu=0)$v 

Inoltre, penso che la risposta di Whuber dovrebbe essere abbastanza facile da implementare, tramite la crossprodfunzione, che è estremamente veloce su matrici sparse:

system.time(M_Mt <- crossprod(M)) # 0.463 seconds
system.time(means <- colMeans(M)) #0.003 seconds

Ora non sono sicuro di cosa fare al meansvettore prima di sottrarre M_Mt, ma pubblicherò non appena lo capirò.


/ edit3: ecco la versione modificata del codice di whuber, usando operazioni con matrici sparse per ogni fase del processo. Se riesci a memorizzare l'intera matrice sparsa in memoria, funziona molto rapidamente:

library('Matrix')
library('irlba')
set.seed(42)
m <- 500000
n <- 100
i <- unlist(lapply(1:m, function(i) rep(i, sample(25:50,1))))
j <- sample(1:n, length(i), replace=TRUE)
x <- sparseMatrix(i, j, x=runif(length(i)))

n_comp <- 50
system.time({
  xt.x <- crossprod(x)
  x.means <- colMeans(x)
  xt.x <- (xt.x - m * tcrossprod(x.means)) / (m-1)
  svd.0 <- irlba(xt.x, nu=0, nv=n_comp, tol=1e-10)
})
#user  system elapsed 
#0.148   0.030   2.923 

system.time(pca <- prcomp(x, center=TRUE))
#user  system elapsed 
#32.178   2.702  12.322

max(abs(pca$center - x.means))
max(abs(xt.x - cov(as.matrix(x))))
max(abs(abs(svd.0$v / pca$rotation[,1:n_comp]) - 1))

Se si imposta il numero di colonne su 10.000 e il numero di componenti principali su 25, il irlbaPCA basato su circa 17 minuti impiega circa 17 minuti per calcolare 50 componenti principali approssimativi e consuma circa 6 GB di RAM, il che non è poi così male.


Zach, curioso di averlo mai risolto.
B_Miner,

@B_Miner: Fondamentalmente, ho fatto SVD senza preoccuparmi di centrare o scalare prima, perché non ho mai trovato un buon modo per farlo senza convertire la mia matrice sparsa in una matrice densa. La matrice originale% *% del componente V di svd fornisce i "componenti principali". A volte, ottengo risultati migliori se "piego" i valori degli automi, ad esempio v% *% diag (d), dove d è il vettore degli autovalori dall'SVD.
Zach,

Trattate v% *% diag (d) da solo o ancora moltiplicato per la matrice originale X (ovvero X% *% v% *% diag (d)). Sembra sopra che stai usando la matrice u come punteggio del componente principale?
B_Miner,

Io uso X %*% v %*% diag(d, ncol=length(d)). V matrice nella SVD è equivalente all'elemento "rotazione" di un prcompoggetto, e X %*% voppure X %*% v %*% diag(d, ncol=length(d))rappresenta l' xelemento di un prcompoggetto. Dai un'occhiata a stats:::prcomp.default.
Zach

Sì, X% *% v è l'elemento x di prcomp. Sembra che quando usi la matrice u come nella tua domanda, stai effettivamente usando X% *% v% *% diag (1 / d).
B_Miner,

Risposte:


37

Prima di tutto, vuoi davvero centrare i dati . In caso contrario, l' interpretazione geometrica del PCA mostra che il primo componente principale sarà vicino al vettore dei mezzi e tutti i PC successivi saranno ortogonali ad esso, il che impedirà loro di avvicinarsi a qualsiasi PC che si trova vicino a quel primo vettore. Possiamo sperare che la maggior parte dei PC successivi sia approssimativamente corretta, ma il valore di ciò è discutibile quando è probabile che i primi diversi PC - i più importanti - siano abbastanza sbagliati.

XXX1000010000

YZ500000nmYmZ1n1

(YmY1)(ZmZ1)=YZmZ1YmY1.Z+mZmY11=YZn(mYmZ),

mY=1Y/nmZ=1Z/n

XXYZ10000XX


Esempio

Rget.colXprcomp

m <- 500000 # Will be 500,000
n <- 100    # will be 10,000
library("Matrix")
x <- as(matrix(pmax(0,rnorm(m*n, mean=-2)), nrow=m), "sparseMatrix")
#
# Compute centered version of x'x by having at most two columns
# of x in memory at any time.
#
get.col <- function(i) x[,i] # Emulates reading a column
system.time({
  xt.x <- matrix(numeric(), n, n)
  x.means <- rep(numeric(), n)
  for (i in 1:n) {
    i.col <- get.col(i)
    x.means[i] <- mean(i.col)
    xt.x[i,i] <- sum(i.col * i.col)
    if (i < n) {
      for (j in (i+1):n) {
        j.col <- get.col(j)
        xt.x[i,j] <- xt.x[j,i] <- sum(j.col * i.col)
      }    
    }
  }
  xt.x <- (xt.x - m * outer(x.means, x.means, `*`)) / (m-1)
  svd.0 <- svd(xt.x / m)
}
)
system.time(pca <- prcomp(x, center=TRUE))
#
# Checks: all should be essentially zero.
#
max(abs(pca$center - x.means))
max(abs(xt.x - cov(x)))
max(abs(abs(svd.0$v / pca$rotation) - 1)) # (This is an unstable calculation.)

Grazie per la risposta dettagliata Uno dei vantaggi di irlbaè che puoi specificare nudi limitare l'algoritmo ai primi n componenti principali, il che aumenta notevolmente la sua efficacia e (penso) ignora il calcolo della matrice XX '.
Zach,

1
100005000005×1091000010000108irlba

Suppongo che quest'ultimo. =). Quindi ho bisogno di calcolare il prodotto punto per ogni coppia di colonne nella mia matrice sparsa, sottrarre la colMeansmatrice sparsa dalla matrice del prodotto punto, quindi eseguire irlba sul risultato?
Zach,

XXRX

5
Ho aggiunto il codice per illustrare.
whuber
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.