큰 희소 행렬의 차원 축소 (SVD 또는 PCA)


31

/ 편집 : 추가 후속 조치 irlba :: prcomp_irlba를 사용할 수 있습니다


/ 편집 : 내 자신의 게시물에 후속. irlba이제 "center"및 "scale"인수를 사용하여이를 사용하여 기본 구성 요소를 계산할 수 있습니다. 예 :

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


Matrix기계 학습 알고리즘에 사용하려는 크고 작은 기능이 있습니다.

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)

이 행렬에는 많은 열이 있으므로 차원을 관리하기 쉬운 것으로 줄이려고합니다. 우수한 irlba 패키지 를 사용하여 SVD를 수행하고 첫 번째 n 주요 구성 요소를 반환 할 수 있습니다 (여기서는 5, 실제 데이터 세트에는 100 또는 500을 사용할 것입니다).

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

그러나 PCA를 수행하기 전에 행렬을 중앙에 배치해야한다는 것을 읽었습니다 (각 열에서 열 평균을 뺍니다). 이것은 내 데이터 세트에서 수행하기가 매우 어렵고 행렬의 희소성을 파괴합니다.

스케일링되지 않은 데이터에 대해 SVD를 수행하고 머신 러닝 알고리즘에 직접 공급하는 것이 얼마나 "나쁜"가? 행렬의 희소성을 유지 하면서이 데이터를 확장 할 수있는 효율적인 방법이 있습니까?


/ edit : B_miner가 A에게 주목 한 "PC"는 다음과 같아야합니다.

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

또한 whuber의 답변은 crossprod희소 행렬에서 매우 빠른 함수 를 통해 구현하기가 매우 쉽다고 생각합니다 .

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

이제 means에서 빼기 전에 벡터에 대해 무엇을 해야할지 잘 모르겠지만 알아 M_Mt낸 즉시 게시합니다.


/ edit3 : 프로세스의 각 단계마다 희소 행렬 연산을 사용하여 수정 된 버전의 whuber 코드가 있습니다. 전체 희소 행렬을 메모리에 저장할 수 있으면 매우 빠르게 작동합니다.

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

열 수를 10,000으로 설정하고 주요 구성 요소 수를 25로 설정하면, irlba기반 PCA는 대략적인 주요 구성 요소 50 개를 계산하는 데 약 17 분이 걸리고 약 6GB의 RAM을 소비하므로 이는 나쁘지 않습니다.


Zach,이 문제를 해결 한 게 궁금합니다.
B_Miner

@ B_Miner : 기본적으로, 나는 희소 행렬을 고밀도 행렬로 변환하지 않고이를 수행하는 좋은 방법을 찾지 못했기 때문에 먼저 중심이나 스케일링에 신경 쓰지 않고 SVD를 수행했습니다. svd의 V 성분의 원래 행렬 % * %는 "주요 성분"을 제공합니다. 때로는 고유 값 (예 : v % * % diag (d))을 "폴딩"하면 더 나은 결과를 얻을 수 있습니다. 여기서 d는 SVD의 고유 값 벡터입니다.
Zach

v % * % diag (d)를 자체적으로 처리하거나 여전히 원래 행렬 X를 곱한 값으로 취급합니까 (예 : X % * % v % * % diag (d)). 위의 구성 요소 점수로 u 행렬을 사용하고 있습니까?
B_Miner

사용 X %*% v %*% diag(d, ncol=length(d))합니다. svd의 v 행렬은 prcomp객체 의 "rotation"요소 와 동일 X %*% v하거나 객체 X %*% v %*% diag(d, ncol=length(d))x요소를 나타냅니다 prcomp. 보세요 stats:::prcomp.default.
Zach

예, X % * % v는 prcomp의 x 요소입니다. 질문과 같이 u 행렬을 사용할 때 실제로 X % * % v % * % diag (1 / d)를 사용하는 것처럼 보입니다.
B_Miner

답변:


37

우선, 당신은 정말로 데이터를 중심에두고 싶습니다 . 그렇지 않은 경우, PCA기하학적 해석은 첫 번째 주요 구성 요소가 평균 벡터에 가깝고 모든 후속 PC가 그에 직교하여 첫 번째 벡터에 근접한 PC를 근사화 하지 못함을 보여줍니다. 우리는 대부분의 최신 PC가 대략 정확하기를 희망하지만 그 가치는 처음 몇 대의 PC (가장 중요한 PC)가 상당히 잘못되었을 가능성에 의문의 여지가 있습니다.

그래서 뭐 할까? PCA는 행렬 의 특이 값 분해에 의해 진행된다 . 필수 정보는 에 포함되며,이 경우 x 행렬입니다. 관리 할 수 ​​있습니다. 그 계산에는 한 열의 내적과 다음 열의 약 5 천만 번의 계산이 포함됩니다.XXX1000010000

그런 다음 와 라는 두 개의 열을 고려하십시오 (각각 하나는 벡터이며이 차원을 ). 그들의 평균을 각각 및 로하십시오. 당신이 원하는 계산을하는 것은, 쓰기, 에 대한 의 - 벡터 의,YZ500000nmYmZ1n1

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

때문에 과 .m Z = 1Z / nmY=1Y/nmZ=1Z/n

이를 통해 희소 행렬 기법을 사용하여 를 계산할 수 있습니다.이 의 항목은 값을 제공 하고 열 평균을 기반으로 계수를 조정할 수 있습니다. 가 매우 희박한 것 같지 않으므로 조정이 손상되지 않아야합니다 . Y Z 10000 X X 'XXYZ10000XX


다음 R코드는이 방법을 보여줍니다. 스텁을 사용하여 get.col실제로 외부 데이터 소스에서 한 번에 한 열의 열을 읽을 수 있으므로 필요한 RAM 양을 계산할 수 있습니다 (물론 계산 속도는 약간의 비용이 듭니다). 이전 구성에 적용된 SVD를 통해 직접을 사용하여 두 가지 방법으로 PCA를 계산합니다 . 그런 다음 두 가지 접근 방식의 출력을 비교합니다. 계산 시간은 100 개의 열에 대해 약 50 초이며 대략 2 차로 확장됩니다. 10K x 10K 행렬에서 SVD를 수행 할 때까지 기다릴 준비를하십시오!Xprcomp

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

자세한 답변 감사합니다. 장점 중 하나는 알고리즘을 첫 번째 n 개의 주요 구성 요소로 제한하도록 irlba지정할 수 있다는 것 입니다. 이 nu알고리즘은 효능을 크게 높이고 XX '행렬의 계산을 무시합니다.
Zach

1
그러나 무엇을 다루고 싶습니까? A는 스파 스 에 의해 와 매트릭스 당신이 해결해야 할 문제, 또는 나타내지 않는 계수 에 의하여 에 당신이 해결하려는 문제를 나타냅니다 않습니다 계수를? 어쨌든 첫 번째 몇 가지 주요 구성 요소를 얻기 위해 후자에 적용될 수 있으므로 두 세계를 모두 활용할 수 있습니다. 500000 5 × 10 9 10000 10000 10 8100005000005×1091000010000108irlba
whuber

나는 후자를 가정한다. =). 따라서 희소 행렬의 각 열 쌍에 대한 내적을 계산하고 내적 행렬에서 희소 행렬의 빼기를 빼고 colMeans결과에서 irlba를 실행해야합니까?
Zach

거의 : 열 평균 자체가 아니라 열 평균 제품을 빼고 있음을 주목하십시오. 추상적으로 계산하고 있지만 행렬 곱셈을 수행하기 위해 를 실제로 만들고 싶지 않기 때문에 알고리즘의 공식은 훌륭 합니다. 대신 RAM이 실제로 제한되어 있으면 한 번에 열의 하위 집합을 읽어 열 도트 제품을 일괄 적으로 수행 할 수 있습니다. 처음에는 훨씬 작은 행렬을 실험 해 보는 것이 좋습니다 :-). X 'XXRX
whuber

5
설명하기 위해 코드를 추가했습니다.
whuber
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.